| 1 | //go:build linux |
| 2 | |
| 3 | package hostbox |
| 4 | |
| 5 | import ( |
| 6 | "os" |
| 7 | "path/filepath" |
| 8 | "strings" |
| 9 | |
| 10 | "github.com/carlosframework/platform/internal/rootstate" |
| 11 | "golang.org/x/sys/unix" |
| 12 | ) |
| 13 | |
| 14 | func CaptureInventoryIdentity(root, name string) (rootstate.FileIdentity, error) { |
| 15 | if !filepath.IsAbs(root) || filepath.Clean(root) != root || !managedName(name) { |
| 16 | return rootstate.FileIdentity{}, ErrTypedInventory |
| 17 | } |
| 18 | fd, err := unix.Openat2(unix.AT_FDCWD, root, &unix.OpenHow{Flags: unix.O_PATH | unix.O_DIRECTORY | unix.O_CLOEXEC, Resolve: unix.RESOLVE_NO_SYMLINKS}) |
| 19 | if err != nil { |
| 20 | return rootstate.FileIdentity{}, err |
| 21 | } |
| 22 | defer unix.Close(fd) |
| 23 | return inventoryIdentityAt(fd, name, false) |
| 24 | } |
| 25 | |
| 26 | func inventoryIdentityAt(parent int, name string, symlink bool) (rootstate.FileIdentity, error) { |
| 27 | flags := uint64(unix.O_PATH | unix.O_CLOEXEC) |
| 28 | if symlink { |
| 29 | flags |= unix.O_NOFOLLOW |
| 30 | } |
| 31 | fd, err := unix.Openat2(parent, name, &unix.OpenHow{Flags: flags, Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS}) |
| 32 | if err != nil { |
| 33 | return rootstate.FileIdentity{}, err |
| 34 | } |
| 35 | f := os.NewFile(uintptr(fd), name) |
| 36 | defer f.Close() |
| 37 | var st unix.Stat_t |
| 38 | if err := unix.Fstat(fd, &st); err != nil { |
| 39 | return rootstate.FileIdentity{}, err |
| 40 | } |
| 41 | kind := st.Mode & unix.S_IFMT |
| 42 | if kind != unix.S_IFDIR && kind != unix.S_IFREG && !(symlink && kind == unix.S_IFLNK) { |
| 43 | return rootstate.FileIdentity{}, ErrTypedInventory |
| 44 | } |
| 45 | if kind == unix.S_IFREG && st.Nlink != 1 { |
| 46 | return rootstate.FileIdentity{}, ErrTypedInventory |
| 47 | } |
| 48 | return rootstate.ObjectIdentity(f) |
| 49 | } |
| 50 | |
| 51 | func VerifyInventoryIdentity(root string, e StopInventoryEntry) error { |
| 52 | return VerifyInventoryIdentityForOwner(root, e, 0) |
| 53 | } |
| 54 | |
| 55 | func VerifyInventoryIdentityForOwner(root string, e StopInventoryEntry, owner uint32) error { |
| 56 | id, err := CaptureInventoryIdentity(root, e.RelativePath) |
| 57 | if err != nil { |
| 58 | return err |
| 59 | } |
| 60 | if e.Identity.Device != e.Device || e.Identity.Inode != e.Inode || e.Identity.Mode&unix.S_IFMT != e.Type || !sameInventoryIdentity(e.Identity, id) { |
| 61 | return ErrChanged |
| 62 | } |
| 63 | if e.Identity.Boot != id.Boot { |
| 64 | return checkedInventoryAncestryOwner(root, owner) |
| 65 | } |
| 66 | return nil |
| 67 | } |
| 68 | |
| 69 | func checkedInventoryAncestry(root string) error { |
| 70 | return checkedInventoryAncestryOwner(root, 0) |
| 71 | } |
| 72 | |
| 73 | func checkedInventoryAncestryOwner(root string, owner uint32) error { |
| 74 | if !filepath.IsAbs(root) || filepath.Clean(root) != root { |
| 75 | return ErrTypedInventory |
| 76 | } |
| 77 | fd, err := unix.Open("/", unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) |
| 78 | if err != nil { |
| 79 | return err |
| 80 | } |
| 81 | defer func() { unix.Close(fd) }() |
| 82 | parts := append([]string{""}, strings.Split(strings.TrimPrefix(root, "/"), "/")...) |
| 83 | for index, part := range parts { |
| 84 | if part != "" { |
| 85 | next, err := unix.Openat(fd, part, unix.O_PATH|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) |
| 86 | if err != nil { |
| 87 | return err |
| 88 | } |
| 89 | unix.Close(fd) |
| 90 | fd = next |
| 91 | } |
| 92 | var st unix.Stat_t |
| 93 | if err := unix.Fstat(fd, &st); err != nil { |
| 94 | return err |
| 95 | } |
| 96 | allowedOwner := st.Uid == 0 || (index == len(parts)-1 && st.Uid == owner) |
| 97 | if !allowedOwner || st.Mode&0022 != 0 { |
| 98 | return os.ErrPermission |
| 99 | } |
| 100 | } |
| 101 | return nil |
| 102 | } |
| 103 | |