//go:build linux package hostbox import ( "errors" "golang.org/x/sys/unix" "os" "path/filepath" "strings" ) // PrepareRootJournalDirectory walks from / through root-owned, non-writable // ancestors using directory descriptors. An untrusted UID cannot rename any // component, or access the final directory, throughout SQLite's lifetime. func PrepareRootJournalDirectory(path string) error { return prepareJournalDirectory(path, 0) } func prepareJournalDirectory(path string, uid uint32) 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) }() parts := strings.Split(strings.TrimPrefix(path, "/"), "/") for i, part := range parts { var parent unix.Stat_t if err = unix.Fstat(fd, &parent); err != nil { return err } if parent.Uid != uid || parent.Mode&0022 != 0 { return ErrTypedJournal } 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) { if e = unix.Mkdirat(fd, part, 0700); e != nil { return e } if e = unix.Fsync(fd); e != nil { return e } next, e = unix.Openat(fd, part, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) } if e != nil { return e } unix.Close(fd) fd = next var st unix.Stat_t if e = unix.Fstat(fd, &st); e != nil { return e } if st.Uid != uid || st.Mode&0022 != 0 { return ErrTypedJournal } if i == len(parts)-1 && st.Mode&0077 != 0 { return ErrTypedJournal } } entries, err := os.ReadDir(path) if err != nil { return err } for _, entry := range entries { var st unix.Stat_t if err = unix.Fstatat(fd, entry.Name(), &st, unix.AT_SYMLINK_NOFOLLOW); err != nil { return err } if st.Uid == uid && st.Mode&unix.S_IFMT == unix.S_IFDIR && st.Mode&0077 == 0 && (strings.HasPrefix(entry.Name(), "migration-") || entry.Name() == "retire" || entry.Name() == "authority" || entry.Name() == "held-release" || entry.Name() == "unit-bindings" || entry.Name() == "unit-materializations") { continue } if st.Uid != uid || st.Mode&unix.S_IFMT != unix.S_IFREG || st.Nlink != 1 { return ErrTypedJournal } } return unix.Fsync(fd) }