-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathseccomp.c
More file actions
228 lines (215 loc) · 7.58 KB
/
Copy pathseccomp.c
File metadata and controls
228 lines (215 loc) · 7.58 KB
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
/*
* SPDX-License-Identifier: GPL-2.0
* Copyright (c) 2026 Ant Group Corporation.
*/
#include <linux/seccomp.h>
#include <linux/sched.h>
#include <linux/compiler.h>
#include <linux/filter.h>
#include <linux/audit.h>
#include <linux/types.h>
#include <linux/version.h>
#include <linux/bpf.h>
#include "seccomp.h"
#include "slimvm.h"
#include "compat.h"
#ifdef CONFIG_SECCOMP_FILTER
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)
#ifdef SECCOMP_ARCH_NATIVE
/**
* struct action_cache - per-filter cache of seccomp actions per
* arch/syscall pair
*
* @allow_native: A bitmap where each bit represents whether the
* filter will always allow the syscall, for the
* native architecture.
* @allow_compat: A bitmap where each bit represents whether the
* filter will always allow the syscall, for the
* compat architecture.
*/
struct action_cache {
DECLARE_BITMAP(allow_native, SECCOMP_ARCH_NATIVE_NR);
#ifdef SECCOMP_ARCH_COMPAT
DECLARE_BITMAP(allow_compat, SECCOMP_ARCH_COMPAT_NR);
#endif
};
#else
struct action_cache { };
#endif
/**
* struct seccomp_filter - container for seccomp BPF programs
*
* @refs: Reference count to manage the object lifetime.
* A filter's reference count is incremented for each directly
* attached task, once for the dependent filter, and if
* requested for the user notifier. When @refs reaches zero,
* the filter can be freed.
* @users: A filter's @users count is incremented for each directly
* attached task (filter installation, fork(), thread_sync),
* and once for the dependent filter (tracked in filter->prev).
* When it reaches zero it indicates that no direct or indirect
* users of that filter exist. No new tasks can get associated with
* this filter after reaching 0. The @users count is always smaller
* or equal to @refs. Hence, reaching 0 for @users does not mean
* the filter can be freed.
* @cache: cache of arch/syscall mappings to actions
* @log: true if all actions except for SECCOMP_RET_ALLOW should be logged
* @wait_killable_recv: Put notifying process in killable state once the
* notification is received by the userspace listener.
* @prev: points to a previously installed, or inherited, filter
* @prog: the BPF program to evaluate
* @notif: the struct that holds all notification related information
* @notify_lock: A lock for all notification-related accesses.
* @wqh: A wait queue for poll if a notifier is in use.
*
* seccomp_filter objects are organized in a tree linked via the @prev
* pointer. For any task, it appears to be a singly-linked list starting
* with current->seccomp.filter, the most recently attached or inherited filter.
* However, multiple filters may share a @prev node, by way of fork(), which
* results in a unidirectional tree existing in memory. This is similar to
* how namespaces work.
*
* seccomp_filter objects should never be modified after being attached
* to a task_struct (other than @refs).
*/
struct seccomp_filter {
refcount_t refs;
refcount_t users;
bool log;
bool wait_killable_recv;
struct action_cache cache;
struct seccomp_filter *prev;
struct bpf_prog *prog;
struct notification *notif;
struct mutex notify_lock;
wait_queue_head_t wqh;
};
#else
/**
* struct seccomp_filter - container for seccomp BPF programs
*
* @refs: Reference count to manage the object lifetime.
* A filter's reference count is incremented for each directly
* attached task, once for the dependent filter, and if
* requested for the user notifier. When @refs reaches zero,
* the filter can be freed.
* @users: A filter's @users count is incremented for each directly
* attached task (filter installation, fork(), thread_sync),
* and once for the dependent filter (tracked in filter->prev).
* When it reaches zero it indicates that no direct or indirect
* users of that filter exist. No new tasks can get associated with
* this filter after reaching 0. The @users count is always smaller
* or equal to @refs. Hence, reaching 0 for @users does not mean
* the filter can be freed.
* @log: true if all actions except for SECCOMP_RET_ALLOW should be logged
* @prev: points to a previously installed, or inherited, filter
* @prog: the BPF program to evaluate
* @notif: the struct that holds all notification related information
* @notify_lock: A lock for all notification-related accesses.
* @wqh: A wait queue for poll if a notifier is in use.
*
* seccomp_filter objects are organized in a tree linked via the @prev
* pointer. For any task, it appears to be a singly-linked list starting
* with current->seccomp.filter, the most recently attached or inherited filter.
* However, multiple filters may share a @prev node, by way of fork(), which
* results in a unidirectional tree existing in memory. This is similar to
* how namespaces work.
*
* seccomp_filter objects should never be modified after being attached
* to a task_struct (other than @refs).
*/
struct seccomp_filter {
refcount_t refs;
refcount_t users;
bool log;
struct seccomp_filter *prev;
struct bpf_prog *prog;
struct notification *notif;
struct mutex notify_lock;
wait_queue_head_t wqh;
};
#endif
#endif
/* do_seccomp_filter - evaluates all seccomp filters against vmcall. */
int do_seccomp_filter(u64 *regs)
{
#ifdef CONFIG_SECCOMP_FILTER
struct seccomp_filter *f;
struct seccomp_data sd;
u32 ret = SECCOMP_RET_ALLOW;
/*
* Make sure that initialization or any changes to seccomp filter
* have been seen.
*/
rmb();
#ifdef lockless_dereference
f = lockless_dereference(current->seccomp.filter);
#else
/* Since this commit (4.15),
* - "Add implicit smp_read_barrier_depends() to READ_ONCE()",
* lockless_dereference is removed, and we use READ_ONCE() instead.
*/
f = READ_ONCE(current->seccomp.filter);
#endif
if (unlikely(f == NULL))
return 0;
/*
* Populate seccomp data as the format that BPF program executes
* over. The syscall number and syscall arguments are from GR0.
*
* __NR_restart_syscall may be reset during HR0 syscall.
*/
sd.nr = regs[VCPU_REGS_RAX];
if (unlikely(sd.nr >= NR_syscalls ||
sd.nr == __NR_restart_syscall))
return 0;
sd.instruction_pointer = regs[VCPU_REGS_RIP];
sd.arch = syscall_get_arch(current);
sd.args[0] = regs[VCPU_REGS_RDI];
sd.args[1] = regs[VCPU_REGS_RSI];
sd.args[2] = regs[VCPU_REGS_RDX];
sd.args[3] = regs[VCPU_REGS_R10];
sd.args[4] = regs[VCPU_REGS_R8];
sd.args[5] = regs[VCPU_REGS_R9];
/*
* All filters in the list are evaluated and the lowest BPF return
* value always takes priority (ignoring the DATA).
*/
for (; f; f = f->prev) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)
u32 cur_ret = bpf_prog_run(f->prog, (void *)&sd);
#else
u32 cur_ret = BPF_PROG_RUN(f->prog, (void *)&sd);
#endif
if ((cur_ret & SECCOMP_RET_ACTION) < (ret & SECCOMP_RET_ACTION))
ret = cur_ret;
}
/*
* BPF returns one of the following values,
* SECCOMP_RET_KILL [disallow] - kill the task immediately
* SECCOMP_RET_TRAP [disallow] - disallow and force a SIGSYS
* SECCOMP_RET_TRACE [disallow] - pass to a tracer or disallow
* SECCOMP_RET_ERRNO [allow] - returns an errno
* SECCOMP_RET_ALLOW [allow] - allow
*
* For SECCOMP_RET_ERRNO, update syscall number to NR_syscalls,
* then handle it as an invalid syscall later.
*/
switch (ret) {
case SECCOMP_RET_ERRNO:
regs[VCPU_REGS_RAX] = NR_syscalls;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)
fallthrough;
#endif
/* fallthrough */
case SECCOMP_RET_ALLOW:
return 0;
case SECCOMP_RET_KILL:
case SECCOMP_RET_TRAP:
case SECCOMP_RET_TRACE:
slimvm_error("seccomp: syscall %d not allowed", sd.nr);
return -EINVAL;
};
#endif
return 0;
}