package rootstate import ( "encoding/hex" "errors" "fmt" "os" "strconv" "strings" "unsafe" "golang.org/x/sys/unix" ) func PersistentIdentity(f *os.File) (FileIdentity, error) { id, err := ObjectIdentity(f) if err != nil { return FileIdentity{}, err } if id.UID != 0 || id.Mode&0022 != 0 || id.Handle == "" { return FileIdentity{}, os.ErrPermission } return id, nil } func ObjectIdentity(f *os.File) (FileIdentity, error) { var id FileIdentity fd := int(f.Fd()) var st unix.Stat_t var fs unix.Statfs_t var sx unix.Statx_t if err := unix.Fstat(fd, &st); err != nil { return id, err } if err := unix.Fstatfs(fd, &fs); err != nil { return id, err } if err := unix.Statx(fd, "", unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW, unix.STATX_MNT_ID, &sx); err != nil { return id, err } handle, mount, err := unix.NameToHandleAt(fd, "", unix.AT_EMPTY_PATH) if err != nil && !errors.Is(err, unix.EOPNOTSUPP) { return id, err } if err != nil { handle = unix.NewFileHandle(0, nil) } if sx.Mask&unix.STATX_MNT_ID == 0 || (err == nil && (len(handle.Bytes()) == 0 || sx.Mnt_id != uint64(mount))) { 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) } boot, err := os.ReadFile("/proc/sys/kernel/random/boot_id") if err != nil { return id, err } mounts, err := os.ReadFile("/proc/self/mountinfo") if err != nil { return id, err } namespace, err := os.Readlink("/proc/self/ns/mnt") if err != nil || namespace == "" { return id, os.ErrPermission } 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} id.MountUnique, id.NamespaceUnique, err = uniqueMountIdentity(f) if err != nil { return FileIdentity{}, err } for _, line := range strings.Split(string(mounts), "\n") { fields := strings.Fields(line) if len(fields) >= 10 && fields[0] == strconv.FormatUint(id.Mount, 10) { id.MountRoot, id.MountPoint = fields[3], fields[4] break } } id.MountLineage, err = persistentMountLineage(string(mounts), id.Mount) if err != nil { return FileIdentity{}, err } if id.Boot == "" || id.MountRoot == "" || id.MountPoint == "" { return FileIdentity{}, os.ErrPermission } return id, nil } func uniqueMountIdentity(f *os.File) (uint64, uint64, error) { var sx unix.Statx_t if err := unix.Statx(int(f.Fd()), "", unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW, unix.STATX_MNT_ID_UNIQUE, &sx); err != nil { return 0, 0, err } if sx.Mask&unix.STATX_MNT_ID_UNIQUE == 0 || sx.Mnt_id == 0 { return 0, 0, os.ErrPermission } ns, err := os.Open("/proc/self/ns/mnt") if err != nil { return 0, 0, err } defer ns.Close() var id uint64 _, _, errno := unix.Syscall(unix.SYS_IOCTL, ns.Fd(), unix.NS_GET_MNTNS_ID, uintptr(unsafe.Pointer(&id))) if errno != 0 { return 0, 0, errno } if id == 0 { return 0, 0, os.ErrPermission } return sx.Mnt_id, id, nil } func persistentMountLineage(mounts string, mount uint64) (string, error) { rows := make(map[string][]string) for _, line := range strings.Split(mounts, "\n") { fields := strings.Fields(line) if len(fields) >= 10 { rows[fields[0]] = fields } } var lineage strings.Builder seen := make(map[string]bool) for next := strconv.FormatUint(mount, 10); ; { row, ok := rows[next] if !ok || seen[next] { return "", os.ErrPermission } seen[next] = true lineage.WriteString(row[3] + "\t" + row[4] + "\n") if _, present := rows[row[1]]; !present { if row[4] != "/" { anchor, err := persistentMountBoundary(row[1]) if err != nil { return "", err } lineage.WriteString(anchor) } return lineage.String(), nil } next = row[1] } } func persistentMountBoundary(parent string) (string, error) { fd, err := unix.Open("/", unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) if err != nil { return "", err } defer unix.Close(fd) var sx unix.Statx_t if err := unix.Statx(fd, "", unix.AT_EMPTY_PATH, unix.STATX_MNT_ID, &sx); err != nil { return "", err } if sx.Mask&unix.STATX_MNT_ID == 0 || parent != strconv.FormatUint(sx.Mnt_id, 10) { return "", os.ErrPermission } var st unix.Stat_t var fs unix.Statfs_t if err := unix.Fstat(fd, &st); err != nil { return "", err } if err := unix.Fstatfs(fd, &fs); err != nil { return "", err } handle, mount, err := unix.NameToHandleAt(fd, "", unix.AT_EMPTY_PATH) if err != nil { return "", err } if len(handle.Bytes()) == 0 || uint64(mount) != sx.Mnt_id { return "", os.ErrPermission } 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 }