1
2
3
4 package patchbpf
5
6 import (
7 "bytes"
8 "encoding/binary"
9 "fmt"
10 "testing"
11
12 "github.com/opencontainers/runc/libcontainer/configs"
13
14 libseccomp "github.com/seccomp/libseccomp-golang"
15 "golang.org/x/net/bpf"
16 )
17
18 type seccompData struct {
19 Syscall uint32
20 Arch uint32
21 IP uint64
22 Args [6]uint64
23 }
24
25
26 func mockSyscallPayload(t *testing.T, sysno libseccomp.ScmpSyscall, arch nativeArch, args ...uint64) []byte {
27 var buf bytes.Buffer
28
29 data := seccompData{
30 Syscall: uint32(sysno),
31 Arch: uint32(arch),
32 IP: 0xDEADBEEFCAFE,
33 }
34
35 copy(data.Args[:], args)
36 if len(args) > 6 {
37 t.Fatalf("bad syscall payload: linux only supports 6-argument syscalls")
38 }
39
40
41
42 if err := binary.Write(&buf, binary.BigEndian, data); err != nil {
43 t.Fatalf("bad syscall payload: cannot write data: %v", err)
44 }
45 return buf.Bytes()
46 }
47
48
49
50 const retFallthrough uint32 = 0xDEADBEEF
51
52
53
54
55 func mockFilter(t *testing.T, config *configs.Seccomp) (*bpf.VM, []bpf.Instruction) {
56 patch, err := generatePatch(config)
57 if err != nil {
58 t.Fatalf("mock filter: generate enosys patch: %v", err)
59 }
60
61 program := append(patch, bpf.RetConstant{Val: retFallthrough})
62
63 vm, err := bpf.NewVM(program)
64 if err != nil {
65 t.Fatalf("mock filter: compile BPF VM: %v", err)
66 }
67 return vm, program
68 }
69
70
71
72 func fakeConfig(defaultAction configs.Action, explicitSyscalls []string, arches []string) *configs.Seccomp {
73 config := configs.Seccomp{
74 DefaultAction: defaultAction,
75 Architectures: arches,
76 }
77 syscallAction := configs.Allow
78 if syscallAction == defaultAction {
79 syscallAction = configs.Kill
80 }
81 for _, syscall := range explicitSyscalls {
82 config.Syscalls = append(config.Syscalls, &configs.Syscall{
83 Name: syscall,
84 Action: syscallAction,
85 })
86 }
87 return &config
88 }
89
90
91 var testArches = []string{
92 "x86",
93 "amd64",
94 "x32",
95 "arm",
96 "arm64",
97 "mips",
98 "mips64",
99 "mips64n32",
100 "mipsel",
101 "mipsel64",
102 "mipsel64n32",
103 "ppc",
104 "ppc64",
105 "ppc64le",
106 "s390",
107 "s390x",
108 }
109
110 func testEnosysStub(t *testing.T, defaultAction configs.Action, arches []string) {
111 explicitSyscalls := []string{
112 "setns",
113 "kcmp",
114 "renameat2",
115 "copy_file_range",
116 }
117
118 implicitSyscalls := []string{
119 "clone",
120 "openat",
121 "read",
122 "write",
123 }
124
125 futureSyscalls := []libseccomp.ScmpSyscall{1000, 7331}
126
127
128 archSet := map[string]bool{}
129 for _, arch := range arches {
130 archSet[arch] = true
131 }
132
133 for _, test := range []struct {
134 start, end int
135 }{
136 {0, 1},
137 {0, 2},
138 {1, 2},
139 {1, 3},
140 {1, 4},
141 {3, 4},
142 } {
143 allowedSyscalls := explicitSyscalls[test.start:test.end]
144 config := fakeConfig(defaultAction, allowedSyscalls, arches)
145 filter, program := mockFilter(t, config)
146
147
148
149 enosysStart := test.end
150
151 for _, arch := range testArches {
152 type syscallTest struct {
153 syscall string
154 sysno libseccomp.ScmpSyscall
155 expected uint32
156 }
157
158 scmpArch, err := libseccomp.GetArchFromString(arch)
159 if err != nil {
160 t.Fatalf("unknown libseccomp architecture %q: %v", arch, err)
161 }
162
163 nativeArch, err := archToNative(scmpArch)
164 if err != nil {
165 t.Fatalf("unknown audit architecture %q: %v", arch, err)
166 }
167
168 var syscallTests []syscallTest
169
170
171
172 for idx, syscall := range explicitSyscalls {
173 expected := retFallthrough
174 if idx >= enosysStart {
175 expected = retErrnoEnosys
176 }
177 sysno, err := libseccomp.GetSyscallFromNameByArch(syscall, scmpArch)
178 if err != nil {
179 t.Fatalf("unknown syscall %q on arch %q: %v", syscall, arch, err)
180 }
181 syscallTests = append(syscallTests, syscallTest{
182 syscall,
183 sysno,
184 expected,
185 })
186 }
187
188
189 for _, syscall := range implicitSyscalls {
190 sysno, err := libseccomp.GetSyscallFromNameByArch(syscall, scmpArch)
191 if err != nil {
192 t.Fatalf("unknown syscall %q on arch %q: %v", syscall, arch, err)
193 }
194 syscallTests = append(syscallTests, syscallTest{
195 sysno: sysno,
196 syscall: syscall,
197 expected: retFallthrough,
198 })
199 }
200
201
202 for _, sysno := range futureSyscalls {
203 baseSysno, err := libseccomp.GetSyscallFromNameByArch("copy_file_range", scmpArch)
204 if err != nil {
205 t.Fatalf("unknown syscall 'copy_file_range' on arch %q: %v", arch, err)
206 }
207 sysno += baseSysno
208
209 syscallTests = append(syscallTests, syscallTest{
210 sysno: sysno,
211 syscall: fmt.Sprintf("syscall_%#x", sysno),
212 expected: retErrnoEnosys,
213 })
214 }
215
216
217
218
219
220 switch scmpArch {
221 case libseccomp.ArchS390, libseccomp.ArchS390X:
222 syscallTests = append(syscallTests, syscallTest{
223 sysno: s390xMultiplexSyscall,
224 syscall: "setup",
225 expected: retErrnoEnosys,
226 })
227 }
228
229
230 for _, test := range syscallTests {
231
232 if !archSet[arch] || isAllowAction(defaultAction) {
233 test.expected = retFallthrough
234 }
235
236 payload := mockSyscallPayload(t, test.sysno, nativeArch, 0x1337, 0xF00BA5)
237
238
239 rawRet, err := filter.Run(payload)
240 if err != nil {
241 t.Fatalf("error running filter: %v", err)
242 }
243 ret := uint32(rawRet)
244 if ret != test.expected {
245 t.Logf("mock filter for %v %v:", arches, allowedSyscalls)
246 for idx, insn := range program {
247 t.Logf(" [%4.1d] %s", idx, insn)
248 }
249 t.Logf("payload: %#v", payload)
250 t.Errorf("filter %s(%d) %q(%d): got %#x, want %#x", arch, nativeArch, test.syscall, test.sysno, ret, test.expected)
251 }
252 }
253 }
254 }
255 }
256
257 var testActions = map[string]configs.Action{
258 "allow": configs.Allow,
259 "log": configs.Log,
260 "errno": configs.Errno,
261 "kill": configs.Kill,
262 }
263
264 func TestEnosysStub_SingleArch(t *testing.T) {
265 for _, arch := range testArches {
266 arches := []string{arch}
267 t.Run("arch="+arch, func(t *testing.T) {
268 for name, action := range testActions {
269 t.Run("action="+name, func(t *testing.T) {
270 testEnosysStub(t, action, arches)
271 })
272 }
273 })
274 }
275 }
276
277 func TestEnosysStub_MultiArch(t *testing.T) {
278 for end := 0; end < len(testArches); end++ {
279 for start := 0; start < end; start++ {
280 arches := testArches[start:end]
281 if len(arches) <= 1 {
282 continue
283 }
284 for _, action := range testActions {
285 testEnosysStub(t, action, arches)
286 }
287 }
288 }
289 }
290
291 func TestDisassembleHugeFilterDoesNotHang(t *testing.T) {
292 hugeFilter, err := libseccomp.NewFilter(libseccomp.ActAllow)
293 if err != nil {
294 t.Fatalf("failed to create seccomp filter: %v", err)
295 }
296
297 for i := 1; i < 10000; i++ {
298 if err := hugeFilter.AddRule(libseccomp.ScmpSyscall(i), libseccomp.ActKillThread); err != nil {
299 t.Fatalf("failed to add rule to filter %d: %v", i, err)
300 }
301 }
302
303 _, err = disassembleFilter(hugeFilter)
304 if err != nil {
305 t.Fatalf("failed to disassembleFilter: %v", err)
306 }
307
308
309 }
310
View as plain text