Files
netwatch/internal/reportbuf/datadir_linux.go
T
sneak 54e3e83888
check / check (push) Waiting to run
prepare-data-dir leaves files with a second name to their owner (closes #143)
A hard link in /data shares its owner with a file that can be anywhere
on the same filesystem, so chowning it gave that file to the netwatch
user. A regular file or symbolic link with more than one name now
keeps its owner and its path is logged.

Owners are now set on the opened directory, file or link after
checking it, as chmodDir sets modes, so a hard link swapped in
afterwards is caught. A symbolic link is opened without following it
and given to the netwatch user itself. Setting the owner on an open
file or link needs Linux, the image's system; elsewhere it returns an
error. Other files, such as devices, keep their owner.

Model: opus-5-5
2026-10-18 21:48:41 +00:00

51 lines
1.5 KiB
Go

//go:build linux
package reportbuf
import (
"log/slog"
"os"
"syscall"
"golang.org/x/sys/unix"
)
// chownFile gives name, a regular file or symbolic link inside root, to
// uid and gid, unless it has a second name (hard link): all the names
// of a file share its owner, and the other could be outside root. It
// opens name through root with O_PATH, which reads nothing from the
// file; os.Root adds O_NOFOLLOW, and the two together open a symbolic
// link itself, never what it points to. It reads the number of names
// from the open file and gives that file to them, so a hard link
// swapped in at name is caught too. It logs a file it leaves.
//
// This file is built only on Linux, because O_PATH and AT_EMPTY_PATH
// exist only there; prepare-data-dir runs only in the Linux image. On
// any other system chownFile returns an error (see datadir_other.go).
func chownFile(root *os.Root, name string, uid, gid int) error {
file, err := root.OpenFile(name, unix.O_PATH, 0)
if err != nil {
return err
}
defer func() { _ = file.Close() }()
info, err := file.Stat()
if err != nil {
return err
}
// File.Chown would fail: fchown refuses a file opened with O_PATH.
// Fchownat with an empty name and AT_EMPTY_PATH acts on the open
// file instead.
stat, ok := info.Sys().(*syscall.Stat_t)
if ok && stat.Nlink == 1 {
return unix.Fchownat(int(file.Fd()), "", uid, gid, unix.AT_EMPTY_PATH)
}
slog.Warn("owner not changed: file has a second name (hard link)",
"path", file.Name())
return nil
}