forked from containers/common
-
Notifications
You must be signed in to change notification settings - Fork 1
/
filter.go
241 lines (204 loc) · 6.92 KB
/
filter.go
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
//go:build seccomp
// +build seccomp
// NOTE: this package has originally been copied from
// github.com/opencontainers/runc and modified to work for other use cases
package seccomp
import (
"errors"
"fmt"
specs "github.com/opencontainers/runtime-spec/specs-go"
libseccomp "github.com/seccomp/libseccomp-golang"
"golang.org/x/sys/unix"
)
// NOTE: this package has originally been copied from
// github.com/opencontainers/runc and modified to work for other use cases
var (
// ErrSpecNil is a possible return error from BuildFilter() and occurs if
// the provided spec is nil.
ErrSpecNil = errors.New("spec is nil")
// ErrSpecEmpty is a possible return error from BuildFilter() and occurs if
// the provided spec has neither a DefaultAction nor any syscalls.
ErrSpecEmpty = errors.New("spec contains neither a default action nor any syscalls")
)
// BuildFilter does a basic validation for the provided seccomp profile
// string and returns a filter for it.
func BuildFilter(spec *specs.LinuxSeccomp) (*libseccomp.ScmpFilter, error) {
// Sanity checking to allow consumers to act accordingly
if spec == nil {
return nil, ErrSpecNil
}
if spec.DefaultAction == "" && len(spec.Syscalls) == 0 {
return nil, ErrSpecEmpty
}
profile, err := specToSeccomp(spec)
if err != nil {
return nil, fmt.Errorf("convert spec to seccomp profile: %w", err)
}
defaultAction, err := toAction(profile.DefaultAction, profile.DefaultErrnoRet)
if err != nil {
return nil, fmt.Errorf("convert default action %s: %w", profile.DefaultAction, err)
}
filter, err := libseccomp.NewFilter(defaultAction)
if err != nil {
return nil, fmt.Errorf("create filter for default action %s: %w", defaultAction, err)
}
// Add extra architectures
for _, arch := range spec.Architectures {
libseccompArch, err := specArchToLibseccompArch(arch)
if err != nil {
return nil, fmt.Errorf("convert spec arch: %w", err)
}
scmpArch, err := libseccomp.GetArchFromString(libseccompArch)
if err != nil {
return nil, fmt.Errorf("validate Seccomp architecture %s: %w", arch, err)
}
if err := filter.AddArch(scmpArch); err != nil {
return nil, fmt.Errorf("add architecture to seccomp filter: %w", err)
}
}
// Unset no new privs bit
if err := filter.SetNoNewPrivsBit(false); err != nil {
return nil, fmt.Errorf("set no new privileges flag: %w", err)
}
// Add a rule for each syscall
for _, call := range profile.Syscalls {
if call == nil {
return nil, errors.New("encountered nil syscall while initializing seccomp")
}
if err = matchSyscall(filter, call); err != nil {
return nil, fmt.Errorf("filter matches syscall: %w", err)
}
}
return filter, nil
}
func matchSyscall(filter *libseccomp.ScmpFilter, call *Syscall) error {
if call == nil || filter == nil {
return errors.New("cannot use nil as syscall to block")
}
if call.Name == "" {
return errors.New("empty string is not a valid syscall")
}
// If we can't resolve the syscall, assume it's not supported on this kernel
// Ignore it, don't error out
callNum, err := libseccomp.GetSyscallFromName(call.Name)
if err != nil {
return nil
}
// Convert the call's action to the libseccomp equivalent
callAct, err := toAction(call.Action, call.ErrnoRet)
if err != nil {
return fmt.Errorf("convert action %s: %w", call.Action, err)
}
// Unconditional match - just add the rule
if len(call.Args) == 0 {
if err = filter.AddRule(callNum, callAct); err != nil {
return fmt.Errorf("add seccomp filter rule for syscall %s: %w", call.Name, err)
}
} else {
// Linux system calls can have at most 6 arguments
const syscallMaxArguments int = 6
// If two or more arguments have the same condition,
// Revert to old behavior, adding each condition as a separate rule
argCounts := make([]uint, syscallMaxArguments)
conditions := []libseccomp.ScmpCondition{}
for _, cond := range call.Args {
newCond, err := toCondition(cond)
if err != nil {
return fmt.Errorf("create seccomp syscall condition for syscall %s: %w", call.Name, err)
}
argCounts[cond.Index]++
conditions = append(conditions, newCond)
}
hasMultipleArgs := false
for _, count := range argCounts {
if count > 1 {
hasMultipleArgs = true
break
}
}
if hasMultipleArgs {
// Revert to old behavior
// Add each condition attached to a separate rule
for _, cond := range conditions {
condArr := []libseccomp.ScmpCondition{cond}
if err = filter.AddRuleConditional(callNum, callAct, condArr); err != nil {
return fmt.Errorf("add seccomp rule for syscall %s: %w", call.Name, err)
}
}
} else if err = filter.AddRuleConditional(callNum, callAct, conditions); err != nil {
// No conditions share same argument
// Use new, proper behavior
return fmt.Errorf("add seccomp rule for syscall %s: %w", call.Name, err)
}
}
return nil
}
// toAction converts an internal `Action` type to a `libseccomp.ScmpAction`
// type.
func toAction(act Action, errnoRet *uint) (libseccomp.ScmpAction, error) {
switch act {
case ActKill:
// lint was not passing until this was changed from ActKill to ActKilThread.
return libseccomp.ActKillThread, nil
case ActKillProcess:
return libseccomp.ActKillProcess, nil
case ActErrno:
if errnoRet != nil {
return libseccomp.ActErrno.SetReturnCode(int16(*errnoRet)), nil
}
return libseccomp.ActErrno.SetReturnCode(int16(unix.EPERM)), nil
case ActTrap:
return libseccomp.ActTrap, nil
case ActAllow:
return libseccomp.ActAllow, nil
case ActTrace:
if errnoRet != nil {
return libseccomp.ActTrace.SetReturnCode(int16(*errnoRet)), nil
}
return libseccomp.ActTrace.SetReturnCode(int16(unix.EPERM)), nil
case ActLog:
return libseccomp.ActLog, nil
default:
return libseccomp.ActInvalid, fmt.Errorf("invalid action %s", act)
}
}
// toCondition converts an internal `Arg` type to a `libseccomp.ScmpCondition`
// type.
func toCondition(arg *Arg) (cond libseccomp.ScmpCondition, err error) {
if arg == nil {
return cond, errors.New("cannot convert nil to syscall condition")
}
op, err := toCompareOp(arg.Op)
if err != nil {
return cond, fmt.Errorf("convert compare operator: %w", err)
}
condition, err := libseccomp.MakeCondition(
arg.Index, op, arg.Value, arg.ValueTwo,
)
if err != nil {
return cond, fmt.Errorf("make condition: %w", err)
}
return condition, nil
}
// toCompareOp converts an internal `Operator` type to a
// `libseccomp.ScmpCompareOp`.
func toCompareOp(op Operator) (libseccomp.ScmpCompareOp, error) {
switch op {
case OpEqualTo:
return libseccomp.CompareEqual, nil
case OpNotEqual:
return libseccomp.CompareNotEqual, nil
case OpGreaterThan:
return libseccomp.CompareGreater, nil
case OpGreaterEqual:
return libseccomp.CompareGreaterEqual, nil
case OpLessThan:
return libseccomp.CompareLess, nil
case OpLessEqual:
return libseccomp.CompareLessOrEqual, nil
case OpMaskedEqual:
return libseccomp.CompareMaskedEqual, nil
default:
return libseccomp.CompareInvalid, fmt.Errorf("invalid operator %s", op)
}
}