check / check (push) Waiting to run
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
51 lines
1.5 KiB
Go
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
|
|
}
|