carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package hostbox
4
5import (
6 "os"
7 "path/filepath"
8 "strings"
9
10 "github.com/carlosframework/platform/internal/rootstate"
11 "golang.org/x/sys/unix"
12)
13
14func 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
26func 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
51func VerifyInventoryIdentity(root string, e StopInventoryEntry) error {
52 return VerifyInventoryIdentityForOwner(root, e, 0)
53}
54
55func 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
69func checkedInventoryAncestry(root string) error {
70 return checkedInventoryAncestryOwner(root, 0)
71}
72
73func 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