1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package bpf

import (
	"sort"
	"testing"

	"golang.org/x/sys/unix"
)

// systems exercised by the property/fuzz tests.  All target ABIs are covered
// because the interpreter is arch-agnostic (it just reads seccomp_data.arch).
var testSystems = []string{
	"x86_64-linux",
	"aarch64-linux",
	"mips64-linux",
	"mips64el-linux",
}

func compileOrFatal(t *testing.T, system string) []unix.SockFilter {
	t.Helper()
	prog, err := Compile(system)
	if err != nil {
		t.Fatalf("Compile(%q): %v", system, err)
	}
	return prog
}

// archRules returns this arch profile's resolved (nr, rule) entries, sorted and
// de-duplicated exactly as the compiler does.
func archRules(profile archProfile) []resolvedRule {
	table := syscallTables[profile.name]
	var rules []resolvedRule
	for _, r := range lixRules {
		if nr, ok := table[r.name]; ok {
			rules = append(rules, resolvedRule{nr: nr, rule: r})
		}
	}
	sort.SliceStable(rules, func(a, b int) bool { return rules[a].nr < rules[b].nr })
	return dedupByNr(rules)
}

// TestAllowlistAllowed verifies every allowlisted syscall returns RET_ALLOW.
func TestAllowlistAllowed(t *testing.T) {
	for _, system := range testSystems {
		prog := compileOrFatal(t, system)
		for _, ap := range targetArches[system] {
			for _, rr := range archRules(ap) {
				if rr.rule.kind != kindAllow {
					continue
				}
				got, err := Run(prog, SeccompData{Arch: ap.auditArch, Nr: rr.nr})
				if err != nil {
					t.Fatalf("%s/%s nr=%d: Run: %v", system, ap.name, rr.nr, err)
				}
				if got != RetAllow {
					t.Errorf("%s/%s %s (nr=%d): got 0x%08x, want RET_ALLOW", system, ap.name, rr.rule.name, rr.nr, got)
				}
			}
		}
	}
}

// TestSetxattrDenied verifies the setxattr family returns ENOTSUP.
func TestSetxattrDenied(t *testing.T) {
	for _, system := range testSystems {
		prog := compileOrFatal(t, system)
		for _, ap := range targetArches[system] {
			table := syscallTables[ap.name]
			for _, name := range []string{"setxattr", "lsetxattr", "fsetxattr"} {
				nr, ok := table[name]
				if !ok {
					continue
				}
				got, err := Run(prog, SeccompData{Arch: ap.auditArch, Nr: nr})
				if err != nil {
					t.Fatalf("%s/%s %s: Run: %v", system, ap.name, name, err)
				}
				if want := Errno(errENOTSUP); got != want {
					t.Errorf("%s/%s %s: got 0x%08x, want ENOTSUP 0x%08x", system, ap.name, name, got, want)
				}
			}
		}
	}
}

// TestChmodArgCheck verifies chmod-family setuid/setgid handling.
func TestChmodArgCheck(t *testing.T) {
	for _, system := range testSystems {
		prog := compileOrFatal(t, system)
		for _, ap := range targetArches[system] {
			table := syscallTables[ap.name]
			for _, tc := range []struct {
				name   string
				argIdx int
			}{
				{"chmod", 1}, {"fchmod", 1}, {"fchmodat", 2}, {"fchmodat2", 2},
			} {
				nr, ok := table[tc.name]
				if !ok {
					continue
				}
				// Safe mode 0644 -> allow.
				var safe [6]uint64
				safe[tc.argIdx] = 0o644
				if got, _ := Run(prog, SeccompData{Arch: ap.auditArch, Nr: nr, Args: safe}); got != RetAllow {
					t.Errorf("%s/%s %s(0644): got 0x%08x, want ALLOW", system, ap.name, tc.name, got)
				}
				// setuid mode 04755 -> EPERM.
				var suid [6]uint64
				suid[tc.argIdx] = 0o4755
				if got, _ := Run(prog, SeccompData{Arch: ap.auditArch, Nr: nr, Args: suid}); got != Errno(errEPERM) {
					t.Errorf("%s/%s %s(04755): got 0x%08x, want EPERM", system, ap.name, tc.name, got)
				}
				// setgid mode 02755 -> EPERM.
				var sgid [6]uint64
				sgid[tc.argIdx] = 0o2755
				if got, _ := Run(prog, SeccompData{Arch: ap.auditArch, Nr: nr, Args: sgid}); got != Errno(errEPERM) {
					t.Errorf("%s/%s %s(02755): got 0x%08x, want EPERM", system, ap.name, tc.name, got)
				}
			}
		}
	}
}

// TestUnknownSyscallDenied verifies an unallocated nr returns ENOSYS.
func TestUnknownSyscallDenied(t *testing.T) {
	for _, system := range testSystems {
		prog := compileOrFatal(t, system)
		for _, ap := range targetArches[system] {
			// A very large nr that is not in any table.
			got, err := Run(prog, SeccompData{Arch: ap.auditArch, Nr: 0x7ffffffe})
			if err != nil {
				t.Fatalf("%s/%s: Run: %v", system, ap.name, err)
			}
			if want := Errno(errENOSYS); got != want {
				t.Errorf("%s/%s unknown nr: got 0x%08x, want ENOSYS", system, ap.name, got)
			}
		}
	}
}

// TestUnknownArchKilled verifies a non-target arch is killed.
func TestUnknownArchKilled(t *testing.T) {
	for _, system := range testSystems {
		prog := compileOrFatal(t, system)
		got, err := Run(prog, SeccompData{Arch: 0xdeadbeef, Nr: 0})
		if err != nil {
			t.Fatalf("%s: Run: %v", system, err)
		}
		if got != RetKillProcess {
			t.Errorf("%s unknown arch: got 0x%08x, want KILL_PROCESS", system, got)
		}
	}
}

// TestDifferentialAllNumbers exhaustively checks every nr in a wide range
// against the reference evaluator for each target arch.
func TestDifferentialAllNumbers(t *testing.T) {
	for _, system := range testSystems {
		prog := compileOrFatal(t, system)
		for _, ap := range targetArches[system] {
			// Cover 0..maxNr+padding so both in-table and out-of-table numbers
			// are exercised.  x32 numbers carry the high bit, so test the masked
			// range too.
			maxNr := uint32(0)
			for _, nr := range syscallTables[ap.name] {
				if nr > maxNr {
					maxNr = nr
				}
			}
			_ = maxNr
			rules := archRules(ap)
			// Test each in-table number plus a non-member neighbour.
			for _, rr := range rules {
				for _, nr := range []uint32{rr.nr, rr.nr + 1} {
					var args [6]uint64
					want := referenceAction(system, ap.auditArch, nr, args)
					got, err := Run(prog, SeccompData{Arch: ap.auditArch, Nr: nr, Args: args})
					if err != nil {
						t.Fatalf("%s/%s nr=%d: Run: %v", system, ap.name, nr, err)
					}
					if got != want {
						t.Errorf("%s/%s nr=%d: got 0x%08x want 0x%08x", system, ap.name, nr, got, want)
					}
				}
			}
		}
	}
}