返回 DeepSeek-Reasonix
strip_windows_test.go
根目录 / internal / packagegrant / strip_windows_test.go
1 //go:build windows
2
3 package packagegrant
4
5 import (
6 "encoding/binary"
7 "os"
8 "os/exec"
9 "path/filepath"
10 "slices"
11 "testing"
12 "unsafe"
13
14 "golang.org/x/sys/windows"
15 )
16
17 const testPackageSID = "S-1-15-2-1658335055-2853777128-3014724382-1760870658-2056114279-2893136470-138490047"
18
19 func icacls(t *testing.T, args ...string) {
20 t.Helper()
21 if out, err := exec.Command("icacls", args...).CombinedOutput(); err != nil {
22 t.Fatalf("icacls %v: %v\n%s", args, err, out)
23 }
24 }
25
26 // entries lists what path's DACL says about the test package, as "allow",
27 // "allow-inherited" or "deny", plus whether the DACL is protected.
28 func entries(t *testing.T, path string) ([]string, bool) {
29 t.Helper()
30 sd, err := windows.GetNamedSecurityInfo(path, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION)
31 if err != nil {
32 t.Fatalf("read %s: %v", path, err)
33 }
34 dacl, _, err := sd.DACL()
35 if err != nil {
36 t.Fatalf("dacl %s: %v", path, err)
37 }
38 control, _, _ := sd.Control()
39 want, _ := windows.StringToSid(testPackageSID)
40 var got []string
41 for i := range uint32(dacl.AceCount) {
42 var entry *windows.ACCESS_ALLOWED_ACE
43 if err := windows.GetAce(dacl, i, &entry); err != nil {
44 t.Fatalf("ace %d: %v", i, err)
45 }
46 if !(*windows.SID)(unsafe.Pointer(&entry.SidStart)).Equals(want) {
47 continue
48 }
49 switch {
50 case entry.Header.AceType == aceTypeAccessAllowed && entry.Header.AceFlags&aceFlagInherited != 0:
51 got = append(got, "allow-inherited")
52 case entry.Header.AceType == aceTypeAccessAllowed:
53 got = append(got, "allow")
54 default:
55 got = append(got, "deny")
56 }
57 }
58 return got, control&windows.SE_DACL_PROTECTED != 0
59 }
60
61 func tree(t *testing.T) (parent, root, dll string) {
62 parent = t.TempDir()
63 root = filepath.Join(parent, "app")
64 dll = filepath.Join(root, "ffmpeg.dll")
65 if err := os.MkdirAll(filepath.Join(root, "resources"), 0o755); err != nil {
66 t.Fatal(err)
67 }
68 if err := os.WriteFile(dll, []byte("x"), 0o644); err != nil {
69 t.Fatal(err)
70 }
71 return parent, root, dll
72 }
73
74 func TestAGrantOnTheRootLeavesEveryObjectUnderIt(t *testing.T) {
75 _, root, dll := tree(t)
76 icacls(t, root, "/grant", "*"+testPackageSID+":(OI)(CI)(RX)")
77 if got, _ := entries(t, dll); !slices.Equal(got, []string{"allow-inherited"}) {
78 t.Fatalf("setup: the dll carries %v", got)
79 }
80
81 report, err := Strip(root)
82 if err != nil || len(report.Refused) != 0 {
83 t.Fatalf("strip: %+v, %v", report, err)
84 }
85 for _, path := range []string{root, filepath.Join(root, "resources"), dll} {
86 if got, protected := entries(t, path); len(got) != 0 || protected {
87 t.Errorf("%s still carries %v (protected=%v)", path, got, protected)
88 }
89 }
90 if !slices.Equal(report.Stripped, []string{root}) {
91 t.Errorf("stripped %v, want only the root that held the entry", report.Stripped)
92 }
93 if _, err := os.ReadFile(dll); err != nil {
94 t.Fatalf("the pass took this account's own access: %v", err)
95 }
96 if again, err := Strip(root); err != nil || len(again.Stripped) != 0 {
97 t.Fatalf("a second pass changed %v, %v", again.Stripped, err)
98 }
99 }
100
101 func TestAGrantFromAboveTheTreeIsCutAtTheRootAndTheParentIsUntouched(t *testing.T) {
102 parent, root, dll := tree(t)
103 icacls(t, parent, "/grant", "*"+testPackageSID+":(OI)(CI)(RX)")
104
105 if _, err := Strip(root); err != nil {
106 t.Fatal(err)
107 }
108 if got, _ := entries(t, parent); !slices.Equal(got, []string{"allow"}) {
109 t.Fatalf("the parent is not this tree's to change, and now carries %v", got)
110 }
111 if got, protected := entries(t, root); len(got) != 0 || !protected {
112 t.Fatalf("root carries %v, protected=%v", got, protected)
113 }
114 if got, _ := entries(t, dll); len(got) != 0 {
115 t.Fatalf("dll still carries %v", got)
116 }
117 if _, err := os.ReadFile(dll); err != nil {
118 t.Fatalf("the entries root inherited were not kept as its own: %v", err)
119 }
120 }
121
122 func TestAGrantOnOneFileIsRemovedAndADenyStays(t *testing.T) {
123 _, root, dll := tree(t)
124 other := filepath.Join(root, "resources", "other.dll")
125 if err := os.WriteFile(other, []byte("x"), 0o644); err != nil {
126 t.Fatal(err)
127 }
128 icacls(t, dll, "/grant", "*"+testPackageSID+":(RX)")
129 icacls(t, other, "/deny", "*"+testPackageSID+":(RX)")
130
131 report, err := Strip(root)
132 if err != nil {
133 t.Fatal(err)
134 }
135 if !slices.Equal(report.Stripped, []string{dll}) {
136 t.Fatalf("stripped %v, want only %s", report.Stripped, dll)
137 }
138 if got, protected := entries(t, dll); len(got) != 0 || protected {
139 t.Fatalf("dll carries %v, protected=%v", got, protected)
140 }
141 if got, _ := entries(t, other); !slices.Equal(got, []string{"deny"}) {
142 t.Fatalf("a deny narrows access and must stay; other.dll carries %v", got)
143 }
144 }
145
146 func TestStripRefusesSomethingThatIsNotADirectory(t *testing.T) {
147 _, _, dll := tree(t)
148 if _, err := Strip(dll); err == nil {
149 t.Fatal("a file was accepted as the tree to strip")
150 }
151 }
152
153 func TestTheTestSIDIsAPackageByTheJudgement(t *testing.T) {
154 s, err := windows.StringToSid(testPackageSID)
155 if err != nil {
156 t.Fatal(err)
157 }
158 raw := unsafe.Slice((*byte)(unsafe.Pointer(s)), windows.GetLengthSid(s))
159 entry := binary.LittleEndian.AppendUint32([]byte{aceTypeAccessAllowed, 0, 0, 0}, 0x1200a9)
160 entry = append(entry, raw...)
161 binary.LittleEndian.PutUint16(entry[2:4], uint16(len(entry)))
162 if !grantsSpecificPackage(entry) {
163 t.Fatal("the SID these tests grant is not one the judgement recognises, so they prove nothing")
164 }
165 }
166
166 lines GO