//go:build linux package hostbox import ( "os" "path/filepath" "strings" "github.com/carlosframework/platform/internal/rootstate" "golang.org/x/sys/unix" ) func CaptureInventoryIdentity(root, name string) (rootstate.FileIdentity, error) { if !filepath.IsAbs(root) || filepath.Clean(root) != root || !managedName(name) { return rootstate.FileIdentity{}, ErrTypedInventory } 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}) if err != nil { return rootstate.FileIdentity{}, err } defer unix.Close(fd) return inventoryIdentityAt(fd, name, false) } func inventoryIdentityAt(parent int, name string, symlink bool) (rootstate.FileIdentity, error) { flags := uint64(unix.O_PATH | unix.O_CLOEXEC) if symlink { flags |= unix.O_NOFOLLOW } fd, err := unix.Openat2(parent, name, &unix.OpenHow{Flags: flags, Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS}) if err != nil { return rootstate.FileIdentity{}, err } f := os.NewFile(uintptr(fd), name) defer f.Close() var st unix.Stat_t if err := unix.Fstat(fd, &st); err != nil { return rootstate.FileIdentity{}, err } kind := st.Mode & unix.S_IFMT if kind != unix.S_IFDIR && kind != unix.S_IFREG && !(symlink && kind == unix.S_IFLNK) { return rootstate.FileIdentity{}, ErrTypedInventory } if kind == unix.S_IFREG && st.Nlink != 1 { return rootstate.FileIdentity{}, ErrTypedInventory } return rootstate.ObjectIdentity(f) } func VerifyInventoryIdentity(root string, e StopInventoryEntry) error { return VerifyInventoryIdentityForOwner(root, e, 0) } func VerifyInventoryIdentityForOwner(root string, e StopInventoryEntry, owner uint32) error { id, err := CaptureInventoryIdentity(root, e.RelativePath) if err != nil { return err } if e.Identity.Device != e.Device || e.Identity.Inode != e.Inode || e.Identity.Mode&unix.S_IFMT != e.Type || !sameInventoryIdentity(e.Identity, id) { return ErrChanged } if e.Identity.Boot != id.Boot { return checkedInventoryAncestryOwner(root, owner) } return nil } func checkedInventoryAncestry(root string) error { return checkedInventoryAncestryOwner(root, 0) } func checkedInventoryAncestryOwner(root string, owner uint32) error { if !filepath.IsAbs(root) || filepath.Clean(root) != root { return ErrTypedInventory } fd, err := unix.Open("/", unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) if err != nil { return err } defer func() { unix.Close(fd) }() parts := append([]string{""}, strings.Split(strings.TrimPrefix(root, "/"), "/")...) for index, part := range parts { if part != "" { next, err := unix.Openat(fd, part, unix.O_PATH|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) if err != nil { return err } unix.Close(fd) fd = next } var st unix.Stat_t if err := unix.Fstat(fd, &st); err != nil { return err } allowedOwner := st.Uid == 0 || (index == len(parts)-1 && st.Uid == owner) if !allowedOwner || st.Mode&0022 != 0 { return os.ErrPermission } } return nil }