| 1 | //go:build linux |
| 2 | |
| 3 | package hostbox |
| 4 | |
| 5 | import ( |
| 6 | "errors" |
| 7 | "golang.org/x/sys/unix" |
| 8 | "os" |
| 9 | "path/filepath" |
| 10 | "strings" |
| 11 | ) |
| 12 | |
| 13 | // PrepareRootJournalDirectory walks from / through root-owned, non-writable |
| 14 | // ancestors using directory descriptors. An untrusted UID cannot rename any |
| 15 | // component, or access the final directory, throughout SQLite's lifetime. |
| 16 | func PrepareRootJournalDirectory(path string) error { return prepareJournalDirectory(path, 0) } |
| 17 | func prepareJournalDirectory(path string, uid uint32) error { |
| 18 | if !filepath.IsAbs(path) || filepath.Clean(path) != path || path == "/" { |
| 19 | return ErrTypedJournal |
| 20 | } |
| 21 | fd, err := unix.Open("/", unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) |
| 22 | if err != nil { |
| 23 | return err |
| 24 | } |
| 25 | defer func() { unix.Close(fd) }() |
| 26 | parts := strings.Split(strings.TrimPrefix(path, "/"), "/") |
| 27 | for i, part := range parts { |
| 28 | var parent unix.Stat_t |
| 29 | if err = unix.Fstat(fd, &parent); err != nil { |
| 30 | return err |
| 31 | } |
| 32 | if parent.Uid != uid || parent.Mode&0022 != 0 { |
| 33 | return ErrTypedJournal |
| 34 | } |
| 35 | next, e := unix.Openat(fd, part, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) |
| 36 | if errors.Is(e, unix.ENOENT) { |
| 37 | if e = unix.Mkdirat(fd, part, 0700); e != nil { |
| 38 | return e |
| 39 | } |
| 40 | if e = unix.Fsync(fd); e != nil { |
| 41 | return e |
| 42 | } |
| 43 | next, e = unix.Openat(fd, part, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) |
| 44 | } |
| 45 | if e != nil { |
| 46 | return e |
| 47 | } |
| 48 | unix.Close(fd) |
| 49 | fd = next |
| 50 | var st unix.Stat_t |
| 51 | if e = unix.Fstat(fd, &st); e != nil { |
| 52 | return e |
| 53 | } |
| 54 | if st.Uid != uid || st.Mode&0022 != 0 { |
| 55 | return ErrTypedJournal |
| 56 | } |
| 57 | if i == len(parts)-1 && st.Mode&0077 != 0 { |
| 58 | return ErrTypedJournal |
| 59 | } |
| 60 | } |
| 61 | entries, err := os.ReadDir(path) |
| 62 | if err != nil { |
| 63 | return err |
| 64 | } |
| 65 | for _, entry := range entries { |
| 66 | var st unix.Stat_t |
| 67 | if err = unix.Fstatat(fd, entry.Name(), &st, unix.AT_SYMLINK_NOFOLLOW); err != nil { |
| 68 | return err |
| 69 | } |
| 70 | 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") { |
| 71 | continue |
| 72 | } |
| 73 | if st.Uid != uid || st.Mode&unix.S_IFMT != unix.S_IFREG || st.Nlink != 1 { |
| 74 | return ErrTypedJournal |
| 75 | } |
| 76 | } |
| 77 | return unix.Fsync(fd) |
| 78 | } |
| 79 | |