| 1 | package rootstate |
| 2 | |
| 3 | import ( |
| 4 | "encoding/hex" |
| 5 | "errors" |
| 6 | "fmt" |
| 7 | "os" |
| 8 | "strconv" |
| 9 | "strings" |
| 10 | "unsafe" |
| 11 | |
| 12 | "golang.org/x/sys/unix" |
| 13 | ) |
| 14 | |
| 15 | func 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 | |
| 26 | func 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 | |
| 85 | func 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 | |
| 109 | func 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 | |
| 140 | func 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 | |