| 1 | package packagegrant |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "encoding/binary" |
| 6 | "errors" |
| 7 | "testing" |
| 8 | ) |
| 9 | |
| 10 | const aceTypeAccessDenied = 0x1 |
| 11 | |
| 12 | // sid encodes S-1-<authority>-<subs...> the way Windows lays one out in an ACE. |
| 13 | func sid(authority byte, subs ...uint32) []byte { |
| 14 | b := []byte{1, byte(len(subs)), 0, 0, 0, 0, 0, authority} |
| 15 | for _, s := range subs { |
| 16 | b = binary.LittleEndian.AppendUint32(b, s) |
| 17 | } |
| 18 | return b |
| 19 | } |
| 20 | |
| 21 | func ace(kind, flags byte, s []byte) []byte { |
| 22 | b := []byte{kind, flags, 0, 0} |
| 23 | b = binary.LittleEndian.AppendUint32(b, 0x1200a9) |
| 24 | b = append(b, s...) |
| 25 | binary.LittleEndian.PutUint16(b[2:4], uint16(len(b))) |
| 26 | return b |
| 27 | } |
| 28 | |
| 29 | func acl(aces ...[]byte) []byte { |
| 30 | b := make([]byte, aclHeaderSize) |
| 31 | b[0] = 2 |
| 32 | for _, a := range aces { |
| 33 | b = append(b, a...) |
| 34 | } |
| 35 | binary.LittleEndian.PutUint16(b[2:4], uint16(len(b))) |
| 36 | binary.LittleEndian.PutUint16(b[4:6], uint16(len(aces))) |
| 37 | return b |
| 38 | } |
| 39 | |
| 40 | var ( |
| 41 | user = sid(5, 21, 1111111111, 2222222222, 3333333333, 1001) |
| 42 | system = sid(5, 18) |
| 43 | allPackages = sid(15, 2, 1) |
| 44 | allRestrict = sid(15, 2, 2) |
| 45 | capability = sid(15, 3, 1024, 1, 2, 3, 4, 5, 6, 7) |
| 46 | livePackage = sid(15, 2, 1658335055, 2853777128, 3014724382, 1760870658, 2056114279, 2893136470, 138490047) |
| 47 | shortPackage = sid(15, 2, 999, 999, 999) |
| 48 | ) |
| 49 | |
| 50 | func TestOnlySpecificPackageAllowEntriesAreRemoved(t *testing.T) { |
| 51 | keep := [][]byte{ |
| 52 | ace(aceTypeAccessAllowed, 0, user), |
| 53 | ace(aceTypeAccessAllowed, 0, system), |
| 54 | ace(aceTypeAccessAllowed, 0, allPackages), |
| 55 | ace(aceTypeAccessAllowed, 0, allRestrict), |
| 56 | ace(aceTypeAccessAllowed, 0, capability), |
| 57 | ace(aceTypeAccessDenied, 0, livePackage), |
| 58 | } |
| 59 | for name, grant := range map[string][]byte{ |
| 60 | "a package whose SID has the full hash": ace(aceTypeAccessAllowed, 0, livePackage), |
| 61 | "a package SID of any other length": ace(aceTypeAccessAllowed, 0, shortPackage), |
| 62 | "a callback allow entry": ace(aceTypeAccessAllowedCallback, 0, livePackage), |
| 63 | } { |
| 64 | in := acl(append(append([][]byte{}, keep...), grant)...) |
| 65 | out, protect, changed, err := rewrite(in, false) |
| 66 | if err != nil || !changed || protect { |
| 67 | t.Fatalf("%s: changed=%v protect=%v err=%v", name, changed, protect, err) |
| 68 | } |
| 69 | if want := acl(keep...); !bytes.Equal(out, want) { |
| 70 | t.Fatalf("%s: kept entries differ\n got %x\nwant %x", name, out, want) |
| 71 | } |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | // A pass narrows access: a deny naming a package stays, and so does every |
| 76 | // entry the judgement has no reason to read. |
| 77 | func TestAnACLWithoutPackageGrantsIsLeftAlone(t *testing.T) { |
| 78 | in := acl( |
| 79 | ace(aceTypeAccessDenied, 0, livePackage), |
| 80 | ace(aceTypeAccessAllowed, aceFlagInherited, allPackages), |
| 81 | ace(aceTypeAccessAllowed, 0, capability), |
| 82 | ace(0x5, 0, livePackage), |
| 83 | ) |
| 84 | out, protect, changed, err := rewrite(in, true) |
| 85 | if err != nil || changed || protect || out != nil { |
| 86 | t.Fatalf("out=%x protect=%v changed=%v err=%v", out, protect, changed, err) |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | func TestAnInheritedGrantProtectsTheObjectAndKeepsWhatItInherited(t *testing.T) { |
| 91 | in := acl( |
| 92 | ace(aceTypeAccessAllowed, 0, user), |
| 93 | ace(aceTypeAccessAllowed, aceFlagInherited|0x3, livePackage), |
| 94 | ace(aceTypeAccessAllowed, aceFlagInherited|0x3, system), |
| 95 | ) |
| 96 | out, protect, changed, err := rewrite(in, false) |
| 97 | if err != nil || !changed || !protect { |
| 98 | t.Fatalf("changed=%v protect=%v err=%v", changed, protect, err) |
| 99 | } |
| 100 | want := acl(ace(aceTypeAccessAllowed, 0, user), ace(aceTypeAccessAllowed, 0x3, system)) |
| 101 | if !bytes.Equal(out, want) { |
| 102 | t.Fatalf("inherited entries were not kept as the object's own\n got %x\nwant %x", out, want) |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | func TestAProtectedDACLStaysProtected(t *testing.T) { |
| 107 | in := acl(ace(aceTypeAccessAllowed, 0, user), ace(aceTypeAccessAllowed, 0, livePackage)) |
| 108 | if _, protect, changed, err := rewrite(in, true); err != nil || !changed || !protect { |
| 109 | t.Fatalf("changed=%v protect=%v err=%v", changed, protect, err) |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | func TestSizesThatDoNotAccountForTheBytesAreRefused(t *testing.T) { |
| 114 | good := acl(ace(aceTypeAccessAllowed, 0, livePackage)) |
| 115 | cases := map[string][]byte{ |
| 116 | "shorter than a header": good[:4], |
| 117 | "size past the buffer": func() []byte { b := bytes.Clone(good); binary.LittleEndian.PutUint16(b[2:4], 0xffff); return b }(), |
| 118 | "count past the size": func() []byte { b := bytes.Clone(good); binary.LittleEndian.PutUint16(b[4:6], 2); return b }(), |
| 119 | "entry past the size": func() []byte { b := bytes.Clone(good); binary.LittleEndian.PutUint16(b[10:12], 0xff); return b }(), |
| 120 | "entry shorter than its header": func() []byte { |
| 121 | b := bytes.Clone(good) |
| 122 | binary.LittleEndian.PutUint16(b[10:12], 2) |
| 123 | return b |
| 124 | }(), |
| 125 | } |
| 126 | for name, in := range cases { |
| 127 | if _, _, _, err := rewrite(in, false); !errors.Is(err, ErrMalformed) { |
| 128 | t.Errorf("%s: err=%v, want ErrMalformed", name, err) |
| 129 | } |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | func TestATruncatedSIDIsNeverAPackage(t *testing.T) { |
| 134 | entry := ace(aceTypeAccessAllowed, 0, livePackage) |
| 135 | truncated := entry[:aceHeaderSize+aceMaskSize+sidMinSize+4] |
| 136 | binary.LittleEndian.PutUint16(truncated[2:4], uint16(len(truncated))) |
| 137 | if grantsSpecificPackage(truncated) { |
| 138 | t.Fatal("a SID shorter than its sub-authority count was read as a package") |
| 139 | } |
| 140 | } |
| 141 |