carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package rootstate
2
3import (
4 "encoding/hex"
5 "errors"
6 "fmt"
7 "os"
8 "strconv"
9 "strings"
10 "unsafe"
11
12 "golang.org/x/sys/unix"
13)
14
15func PersistentIdentity(f *os.File) (FileIdentity, error) {
16 id, err := ObjectIdentity(f)
17 if err != nil {
18 return FileIdentity{}, err
19 }
20 if id.UID != 0 || id.Mode&0022 != 0 || id.Handle == "" {
21 return FileIdentity{}, os.ErrPermission
22 }
23 return id, nil
24}
25
26func ObjectIdentity(f *os.File) (FileIdentity, error) {
27 var id FileIdentity
28 fd := int(f.Fd())
29 var st unix.Stat_t
30 var fs unix.Statfs_t
31 var sx unix.Statx_t
32 if err := unix.Fstat(fd, &st); err != nil {
33 return id, err
34 }
35 if err := unix.Fstatfs(fd, &fs); err != nil {
36 return id, err
37 }
38 if err := unix.Statx(fd, "", unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW, unix.STATX_MNT_ID, &sx); err != nil {
39 return id, err
40 }
41 handle, mount, err := unix.NameToHandleAt(fd, "", unix.AT_EMPTY_PATH)
42 if err != nil && !errors.Is(err, unix.EOPNOTSUPP) {
43 return id, err
44 }
45 if err != nil {
46 handle = unix.NewFileHandle(0, nil)
47 }
48 if sx.Mask&unix.STATX_MNT_ID == 0 || (err == nil && (len(handle.Bytes()) == 0 || sx.Mnt_id != uint64(mount))) {
49 return id, fmt.Errorf("persistent identity: uid=%d mode=%o handle=%d mount=%d/%d mask=%x: %w", st.Uid, st.Mode, len(handle.Bytes()), sx.Mnt_id, mount, sx.Mask, os.ErrPermission)
50 }
51 boot, err := os.ReadFile("/proc/sys/kernel/random/boot_id")
52 if err != nil {
53 return id, err
54 }
55 mounts, err := os.ReadFile("/proc/self/mountinfo")
56 if err != nil {
57 return id, err
58 }
59 namespace, err := os.Readlink("/proc/self/ns/mnt")
60 if err != nil || namespace == "" {
61 return id, os.ErrPermission
62 }
63 id = FileIdentity{MountNamespace: namespace, Device: uint64(st.Dev), Inode: st.Ino, Boot: strings.TrimSpace(string(boot)), Filesystem: fmt.Sprintf("%x:%x", fs.Type, fs.Fsid.Val), Handle: hex.EncodeToString(handle.Bytes()), HandleType: handle.Type(), Mount: sx.Mnt_id, UID: st.Uid, GID: st.Gid, Mode: st.Mode}
64 id.MountUnique, id.NamespaceUnique, err = uniqueMountIdentity(f)
65 if err != nil {
66 return FileIdentity{}, err
67 }
68 for _, line := range strings.Split(string(mounts), "\n") {
69 fields := strings.Fields(line)
70 if len(fields) >= 10 && fields[0] == strconv.FormatUint(id.Mount, 10) {
71 id.MountRoot, id.MountPoint = fields[3], fields[4]
72 break
73 }
74 }
75 id.MountLineage, err = persistentMountLineage(string(mounts), id.Mount)
76 if err != nil {
77 return FileIdentity{}, err
78 }
79 if id.Boot == "" || id.MountRoot == "" || id.MountPoint == "" {
80 return FileIdentity{}, os.ErrPermission
81 }
82 return id, nil
83}
84
85func uniqueMountIdentity(f *os.File) (uint64, uint64, error) {
86 var sx unix.Statx_t
87 if err := unix.Statx(int(f.Fd()), "", unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW, unix.STATX_MNT_ID_UNIQUE, &sx); err != nil {
88 return 0, 0, err
89 }
90 if sx.Mask&unix.STATX_MNT_ID_UNIQUE == 0 || sx.Mnt_id == 0 {
91 return 0, 0, os.ErrPermission
92 }
93 ns, err := os.Open("/proc/self/ns/mnt")
94 if err != nil {
95 return 0, 0, err
96 }
97 defer ns.Close()
98 var id uint64
99 _, _, errno := unix.Syscall(unix.SYS_IOCTL, ns.Fd(), unix.NS_GET_MNTNS_ID, uintptr(unsafe.Pointer(&id)))
100 if errno != 0 {
101 return 0, 0, errno
102 }
103 if id == 0 {
104 return 0, 0, os.ErrPermission
105 }
106 return sx.Mnt_id, id, nil
107}
108
109func persistentMountLineage(mounts string, mount uint64) (string, error) {
110 rows := make(map[string][]string)
111 for _, line := range strings.Split(mounts, "\n") {
112 fields := strings.Fields(line)
113 if len(fields) >= 10 {
114 rows[fields[0]] = fields
115 }
116 }
117 var lineage strings.Builder
118 seen := make(map[string]bool)
119 for next := strconv.FormatUint(mount, 10); ; {
120 row, ok := rows[next]
121 if !ok || seen[next] {
122 return "", os.ErrPermission
123 }
124 seen[next] = true
125 lineage.WriteString(row[3] + "\t" + row[4] + "\n")
126 if _, present := rows[row[1]]; !present {
127 if row[4] != "/" {
128 anchor, err := persistentMountBoundary(row[1])
129 if err != nil {
130 return "", err
131 }
132 lineage.WriteString(anchor)
133 }
134 return lineage.String(), nil
135 }
136 next = row[1]
137 }
138}
139
140func persistentMountBoundary(parent string) (string, error) {
141 fd, err := unix.Open("/", unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
142 if err != nil {
143 return "", err
144 }
145 defer unix.Close(fd)
146 var sx unix.Statx_t
147 if err := unix.Statx(fd, "", unix.AT_EMPTY_PATH, unix.STATX_MNT_ID, &sx); err != nil {
148 return "", err
149 }
150 if sx.Mask&unix.STATX_MNT_ID == 0 || parent != strconv.FormatUint(sx.Mnt_id, 10) {
151 return "", os.ErrPermission
152 }
153 var st unix.Stat_t
154 var fs unix.Statfs_t
155 if err := unix.Fstat(fd, &st); err != nil {
156 return "", err
157 }
158 if err := unix.Fstatfs(fd, &fs); err != nil {
159 return "", err
160 }
161 handle, mount, err := unix.NameToHandleAt(fd, "", unix.AT_EMPTY_PATH)
162 if err != nil {
163 return "", err
164 }
165 if len(handle.Bytes()) == 0 || uint64(mount) != sx.Mnt_id {
166 return "", os.ErrPermission
167 }
168 return fmt.Sprintf("@root:%x:%x:%d:%d:%x:%d:%d:%o\t/\n", fs.Type, fs.Fsid.Val, st.Ino, handle.Type(), handle.Bytes(), st.Uid, st.Gid, st.Mode), nil
169}
170