File: filter.go

package info (click to toggle)
golang-github-containers-common 0.50.1%2Bds1-4
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 4,440 kB
  • sloc: makefile: 118; sh: 46
file content (241 lines) | stat: -rw-r--r-- 7,083 bytes parent folder | download | duplicates (2)
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)
	}
}