//go:build linux package host import ( "errors" "fmt" "log" "os" "path/filepath" "strings" "golang.org/x/sys/unix" ) func bootstrapOpenDir(path string) (int, error) { return unix.Openat2(unix.AT_FDCWD, path, &unix.OpenHow{ Flags: unix.O_RDONLY | unix.O_DIRECTORY | unix.O_CLOEXEC | unix.O_NOFOLLOW, Resolve: unix.RESOLVE_NO_SYMLINKS, }) } // bootstrapMakeDir anchors every component before creating or changing it. func bootstrapMakeDir(path string, mode os.FileMode) error { absolute, err := filepath.Abs(path) if err != nil { return err } fd, err := bootstrapOpenDir("/") if err != nil { return err } defer func() { unix.Close(fd) }() parts := strings.Split(strings.TrimPrefix(absolute, "/"), "/") for _, name := range parts { if name == "" { continue } next, err := unix.Openat2(fd, name, &unix.OpenHow{Flags: unix.O_RDONLY | unix.O_DIRECTORY | unix.O_CLOEXEC | unix.O_NOFOLLOW, Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS}) if errors.Is(err, unix.ENOENT) { if err = unix.Mkdirat(fd, name, uint32(mode.Perm())); err != nil && !errors.Is(err, unix.EEXIST) { return err } next, err = unix.Openat2(fd, name, &unix.OpenHow{Flags: unix.O_RDONLY | unix.O_DIRECTORY | unix.O_CLOEXEC | unix.O_NOFOLLOW, Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS}) } if err != nil { return err } unix.Close(fd) fd = next } bits := uint32(mode.Perm()) if mode&os.ModeSetgid != 0 { bits |= unix.S_ISGID } if mode&os.ModeSetuid != 0 { bits |= unix.S_ISUID } if mode&os.ModeSticky != 0 { bits |= unix.S_ISVTX } return unix.Fchmod(fd, bits) } func bootstrapCreateRegistry(path string) (bool, error) { parent, err := bootstrapOpenDir(filepath.Dir(path)) if err != nil { return false, err } defer unix.Close(parent) fd, err := unix.Openat2(parent, filepath.Base(path), &unix.OpenHow{ Flags: unix.O_CREAT | unix.O_EXCL | unix.O_WRONLY | unix.O_NOFOLLOW | unix.O_CLOEXEC, Mode: 0o640, Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_XDEV, }) if errors.Is(err, unix.EEXIST) { var st unix.Stat_t if err := unix.Fstatat(parent, filepath.Base(path), &st, unix.AT_SYMLINK_NOFOLLOW); err != nil { return false, err } if st.Mode&unix.S_IFMT != unix.S_IFREG || st.Nlink != 1 { log.Printf("ALERT: bootstrap: refusing unsafe registry file %s", path) } return false, nil } if err != nil { return false, err } defer unix.Close(fd) var st unix.Stat_t if err := unix.Fstat(fd, &st); err != nil { return false, err } if st.Mode&unix.S_IFMT != unix.S_IFREG || st.Nlink != 1 { return false, fmt.Errorf("unsafe registry file %s", path) } return true, nil } func bootstrapChownTree(path string, uid, gid int) error { fd, err := bootstrapOpenDir(path) if err != nil { return err } return bootstrapChownDir(os.NewFile(uintptr(fd), path), uid, gid) } func bootstrapChownDir(dir *os.File, uid, gid int) error { defer dir.Close() // The name '.' is resolved against the opened directory, not its mutable parent. if err := unix.Fchownat(int(dir.Fd()), ".", uid, gid, unix.AT_SYMLINK_NOFOLLOW); err != nil { return err } entries, err := dir.ReadDir(-1) if err != nil { return err } for _, entry := range entries { path := filepath.Join(dir.Name(), entry.Name()) // NO_XDEV refuses bind mounts as well as different-device mounts. fd, err := unix.Openat2(int(dir.Fd()), entry.Name(), &unix.OpenHow{ Flags: unix.O_PATH | unix.O_NOFOLLOW | unix.O_CLOEXEC, Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_XDEV, }) if err != nil { if errors.Is(err, unix.ELOOP) || errors.Is(err, unix.EXDEV) || errors.Is(err, unix.ENOENT) || errors.Is(err, unix.ENXIO) { log.Printf("ALERT: bootstrap: skipping unsafe ownership path %s: %v", path, err) continue } return err } f := os.NewFile(uintptr(fd), path) var st unix.Stat_t if err := unix.Fstat(fd, &st); err != nil { f.Close() return err } switch st.Mode & unix.S_IFMT { case unix.S_IFDIR: child, err := unix.Openat2(fd, ".", &unix.OpenHow{Flags: unix.O_RDONLY | unix.O_DIRECTORY | unix.O_CLOEXEC | unix.O_NOFOLLOW, Resolve: unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_XDEV}) f.Close() if err != nil { return err } if err := bootstrapChownDir(os.NewFile(uintptr(child), path), uid, gid); err != nil { return err } case unix.S_IFREG: if st.Nlink != 1 { log.Printf("ALERT: bootstrap: skipping unsafe ownership path %s: multiple links", path) f.Close() continue } err := unix.Fchownat(fd, "", uid, gid, unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW) f.Close() if err != nil { return err } default: log.Printf("ALERT: bootstrap: skipping unsafe ownership path %s: special file", path) f.Close() } } return nil }