//go:build linux package hostbox import ( "errors" "io" "os" "path/filepath" "strings" "golang.org/x/sys/unix" ) // checkTrustedJournalDir walks an absolute directory path from / without // following any symlink, optionally creating missing components 0700. The // final directory must belong to this process's effective UID and be // writable by no group or other principal. Root's journals therefore never // live in, or are written through, a directory another principal controls. func checkTrustedJournalDir(path string, create bool) error { if !filepath.IsAbs(path) || filepath.Clean(path) != path || path == "/" { return ErrTypedJournal } fd, err := unix.Open("/", unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) if err != nil { return err } defer func() { unix.Close(fd) }() for _, part := range strings.Split(strings.TrimPrefix(path, "/"), "/") { next, e := unix.Openat(fd, part, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) if errors.Is(e, unix.ENOENT) && create { if e = unix.Mkdirat(fd, part, 0700); e != nil && !errors.Is(e, unix.EEXIST) { return e } next, e = unix.Openat(fd, part, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) } if errors.Is(e, unix.ELOOP) || errors.Is(e, unix.ENOTDIR) { return ErrTypedJournal } if e != nil { return e } unix.Close(fd) fd = next } var st unix.Stat_t if err = unix.Fstat(fd, &st); err != nil { return err } if int(st.Uid) != os.Geteuid() || st.Mode&0022 != 0 { return ErrTypedJournal } return nil } // readTrustedJournal reads one journal file from a trusted directory. The // file must be a single-link regular file owned by this process's UID. func readTrustedJournal(path string) ([]byte, error) { if err := checkTrustedJournalDir(filepath.Dir(path), false); err != nil { return nil, err } fd, err := unix.Open(path, unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_NONBLOCK|unix.O_CLOEXEC, 0) if errors.Is(err, unix.ENOENT) { return nil, os.ErrNotExist } if errors.Is(err, unix.ELOOP) { return nil, ErrTypedJournal } if err != nil { return nil, err } f := os.NewFile(uintptr(fd), path) defer f.Close() var st unix.Stat_t if err = unix.Fstat(fd, &st); err != nil { return nil, err } if st.Mode&unix.S_IFMT != unix.S_IFREG || st.Nlink != 1 || int(st.Uid) != os.Geteuid() || st.Size > 4<<20 { return nil, ErrTypedJournal } return io.ReadAll(io.LimitReader(f, 4<<20)) } // readTrustedJournalDir lists a trusted journal directory. A missing // directory has no entries. func readTrustedJournalDir(path string) ([]os.DirEntry, error) { if err := checkTrustedJournalDir(path, false); err != nil { if errors.Is(err, unix.ENOENT) { return nil, nil } return nil, err } return os.ReadDir(path) }