carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package hostbox
4
5import (
6 "errors"
7 "io"
8 "os"
9 "path/filepath"
10 "strings"
11
12 "golang.org/x/sys/unix"
13)
14
15// checkTrustedJournalDir walks an absolute directory path from / without
16// following any symlink, optionally creating missing components 0700. The
17// final directory must belong to this process's effective UID and be
18// writable by no group or other principal. Root's journals therefore never
19// live in, or are written through, a directory another principal controls.
20func checkTrustedJournalDir(path string, create bool) error {
21 if !filepath.IsAbs(path) || filepath.Clean(path) != path || path == "/" {
22 return ErrTypedJournal
23 }
24 fd, err := unix.Open("/", unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
25 if err != nil {
26 return err
27 }
28 defer func() { unix.Close(fd) }()
29 for _, part := range strings.Split(strings.TrimPrefix(path, "/"), "/") {
30 next, e := unix.Openat(fd, part, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
31 if errors.Is(e, unix.ENOENT) && create {
32 if e = unix.Mkdirat(fd, part, 0700); e != nil && !errors.Is(e, unix.EEXIST) {
33 return e
34 }
35 next, e = unix.Openat(fd, part, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
36 }
37 if errors.Is(e, unix.ELOOP) || errors.Is(e, unix.ENOTDIR) {
38 return ErrTypedJournal
39 }
40 if e != nil {
41 return e
42 }
43 unix.Close(fd)
44 fd = next
45 }
46 var st unix.Stat_t
47 if err = unix.Fstat(fd, &st); err != nil {
48 return err
49 }
50 if int(st.Uid) != os.Geteuid() || st.Mode&0022 != 0 {
51 return ErrTypedJournal
52 }
53 return nil
54}
55
56// readTrustedJournal reads one journal file from a trusted directory. The
57// file must be a single-link regular file owned by this process's UID.
58func readTrustedJournal(path string) ([]byte, error) {
59 if err := checkTrustedJournalDir(filepath.Dir(path), false); err != nil {
60 return nil, err
61 }
62 fd, err := unix.Open(path, unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_NONBLOCK|unix.O_CLOEXEC, 0)
63 if errors.Is(err, unix.ENOENT) {
64 return nil, os.ErrNotExist
65 }
66 if errors.Is(err, unix.ELOOP) {
67 return nil, ErrTypedJournal
68 }
69 if err != nil {
70 return nil, err
71 }
72 f := os.NewFile(uintptr(fd), path)
73 defer f.Close()
74 var st unix.Stat_t
75 if err = unix.Fstat(fd, &st); err != nil {
76 return nil, err
77 }
78 if st.Mode&unix.S_IFMT != unix.S_IFREG || st.Nlink != 1 || int(st.Uid) != os.Geteuid() || st.Size > 4<<20 {
79 return nil, ErrTypedJournal
80 }
81 return io.ReadAll(io.LimitReader(f, 4<<20))
82}
83
84// readTrustedJournalDir lists a trusted journal directory. A missing
85// directory has no entries.
86func readTrustedJournalDir(path string) ([]os.DirEntry, error) {
87 if err := checkTrustedJournalDir(path, false); err != nil {
88 if errors.Is(err, unix.ENOENT) {
89 return nil, nil
90 }
91 return nil, err
92 }
93 return os.ReadDir(path)
94}
95