//go:build linux package reportbuf import ( "io/fs" "log/slog" "os" "syscall" "golang.org/x/sys/unix" ) // chownFile gives name, a file inside root that is not a directory, to // uid and gid if it is a regular file or symbolic link with one name. // It leaves, and logs, a file of any kind with a second name (hard // link): all the names of a file share its owner, and the other could // be outside root. It leaves any other file with one name, such as a // device, named pipe or socket: the server needs to own none of them. // It opens name through root with O_PATH, which reads nothing from the // file, so opening a device or named pipe neither waits nor acts on // it; os.Root adds O_NOFOLLOW, and the two together open a symbolic // link itself, never what it points to. It reads the kind of file and // its number of names from the open file and gives that file to them, // so a hard link swapped in at name is caught too. // // This file is built only on Linux: prepare-data-dir runs only in the // Linux image, and macOS, where the backend is also built for // development, has neither O_PATH nor AT_EMPTY_PATH. 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 } stat, ok := info.Sys().(*syscall.Stat_t) if !ok || stat.Nlink > 1 { slog.Warn("owner not changed: file has a second name (hard link)", "path", file.Name()) return nil } if !info.Mode().IsRegular() && info.Mode().Type() != fs.ModeSymlink { return nil } // 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. err = unix.Fchownat(int(file.Fd()), "", uid, gid, unix.AT_EMPTY_PATH) if err != nil { return &fs.PathError{Op: "chown", Path: file.Name(), Err: err} } return nil }