authorgravatar for dev@sgregoratto.meStephen Gregoratto <dev@sgregoratto.me> 2022-01-29 13:32:04+11:00
committergravatar for dev@sgregoratto.meStephen Gregoratto <dev@sgregoratto.me> 2022-03-09 18:49:36+11:00
logee32d11252b5558fe156e40874c78fb3bfe38566
tree4273e66cff7334600992918bf2c50786c3d833cc
parenta49f2d9f8d45ea5d556ebcb24c6f38a13ec3e94b

Add bits for the Linux Secure Computing facility


2 files changed, 217 insertions(+), 0 deletions(-)

lib/std/os/linux.zig+5
......@@ -91,6 +91,7 @@ pub const tls = @import("linux/tls.zig");
9191pub const pie = @import("linux/start_pie.zig");
9292pub const BPF = @import("linux/bpf.zig");
9393pub const IOCTL = @import("linux/ioctl.zig");
94pub const SECCOMP = @import("linux/seccomp.zig");
9495
9596pub const MAP = struct {
9697 pub usingnamespace arch_bits.MAP;
......@@ -1667,6 +1668,10 @@ pub fn perf_event_open(
16671668 );
16681669}
16691670
1671pub fn seccomp(operation: u32, flags: u32, args: ?*const anyopaque) usize {
1672 return syscall3(.seccomp, operation, flags, @ptrToInt(args));
1673}
1674
16701675pub const E = switch (native_arch) {
16711676 .mips, .mipsel => @import("linux/errno/mips.zig").E,
16721677 .sparc, .sparcel, .sparcv9 => @import("linux/errno/sparc.zig").E,
lib/std/os/linux/seccomp.zig created+212
......@@ -0,0 +1,212 @@
1//! API bits for the Secure Computing facility in the Linux kernel, which allows
2//! processes to restrict access to the system call API.
3//!
4//! Seccomp started life with a single "strict" mode, which only allowed calls
5//! to read(2), write(2), _exit(2) and sigreturn(2). It turns out that this
6//! isn't that useful for general-purpose applications, and so a mode that
7//! utilizes user-supplied filters mode was added.
8//!
9//! Seccomp filters are classic BPF programs, which means that all the
10//! information under `std.x.net.bpf` applies here as well. Conceptually, a
11//! seccomp program is attached to the kernel and is executed on each syscall.
12//! The "packet" being validated is the `data` structure, and the verdict is an
13//! action that the kernel performs on the calling process. The actions are
14//! variations on a "pass" or "fail" result, where a pass allows the syscall to
15//! continue and a fail blocks the syscall and returns some sort of error value.
16//! See the full list of actions under ::RET for more information. Finally, only
17//! word-sized, absolute loads (`ld [k]`) are supported to read from the `data`
18//! structure.
19//!
20//! There are some issues with the filter API that have traditionally made
21//! writing them a pain:
22//!
23//! 1. Each CPU architecture supported by Linux has its own unique ABI and
24//! syscall API. It is not guaranteed that the syscall numbers and arguments
25//! are the same across architectures, or that they're even implemted. Thus,
26//! filters cannot be assumed to be portable without consulting documentation
27//! like syscalls(2) and testing on target hardware. This also requires
28//! checking the value of `data.arch` to make sure that a filter was compiled
29//! for the correct architecture.
30//! 2. Many syscalls take an `unsigned long` or `size_t` argument, the size of
31//! which is dependant on the ABI. Since BPF programs execute in a 32-bit
32//! machine, validation of 64-bit arguments necessitates two load-and-compare
33//! instructions for the upper and lower words.
34//! 3. A further wrinkle to the above is endianess. Unlike network packets,
35//! syscall data shares the endianess of the target machine. A filter
36//! compiled on a little-endian machine will not work on a big-endian one,
37//! and vice-versa. For example: Checking the upper 32-bits of `data.arg1`
38//! requires a load at `@offsetOf(data, "arg1") + 4` on big-endian systems
39//! and `@offsetOf(data, "arg1")` on little-endian systems. Endian-portable
40//! filters require adjusting these offsets at compile time, similar to how
41//! e.g. OpenSSH does[1].
42//! 4. Syscalls with userspace implementations via the vDSO cannot be traced or
43//! filtered. The vDSO can be disabled or just ignored, which must be taken
44//! into account when writing filters.
45//! 5. Software libraries - especially dynamically loaded ones - tend to use
46//! more of the syscall API over time, thus filters must evolve with them.
47//! Static filters can result in reduced or even broken functionality when
48//! calling newer code from these libraries. This is known to happen with
49//! critical libraries like glibc[2].
50//!
51//! Some of these issues can be mitigated with help from Zig and the standard
52//! library. Since the target CPU is known at compile time, the proper syscall
53//! numbers are mixed into the `os` namespace under `std.os.SYS (see the code
54//! for `arch_bits` in `os/linux.zig`). Referencing an unimplemented syscall
55//! would be a compile error. Endian offsets can also be defined in a similar
56//! manner to the OpenSSH example:
57//!
58//! ```zig
59//! const offset = if (native_endian == .Little) struct {
60//! pub const low = 0;
61//! pub const high = @sizeOf(u32);
62//! } else struct {
63//! pub const low = @sizeOf(u32);
64//! pub const high = 0;
65//! };
66//! ```
67//!
68//! Unfortunately, there is no easy solution for issue 5. The most reliable
69//! strategy is to keep testing; test newer Zig versions, different libcs,
70//! different distros, and design your filter to accomidate all of them.
71//! Alternatively, you could inject a filter at runtime. Since filters are
72//! preserved across execve(2), a filter could be setup before executing your
73//! program, without your program having any knowledge of this happening. This
74//! is the method used by systemd[3] and Cloudflare's sandbox library[4].
75//!
76//! [1]: https://github.com/openssh/openssh-portable/blob/master/sandbox-seccomp-filter.c#L81
77//! [2]: https://sourceware.org/legacy-ml/libc-alpha/2017-11/msg00246.html
78//! [3]: https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=
79//! [4]: https://github.com/cloudflare/sandbox
80//!
81//! See Also
82//! - seccomp(2), seccomp_unotify(2)
83//! - https://www.kernel.org/doc/html/latest/userspace-api/seccomp_filter.html
84const IOCTL = @import("ioctl.zig");
85
86// Modes for the prctl(2) form `prctl(PR_SET_SECCOMP, mode)`
87pub const MODE = struct {
88 /// Seccomp not in use.
89 pub const DISABLED = 0;
90 /// Uses a hard-coded filter.
91 pub const STRICT = 1;
92 /// Uses a user-supplied filter.
93 pub const FILTER = 2;
94};
95
96// Operations for the seccomp(2) form `seccomp(operation, flags, args)`
97pub const SET_MODE_STRICT = 0;
98pub const SET_MODE_FILTER = 1;
99pub const GET_ACTION_AVAIL = 2;
100pub const GET_NOTIF_SIZES = 3;
101
102/// Bitflags for the SET_MODE_FILTER operation.
103pub const FILTER_FLAG = struct {
104 pub const TSYNC = 1 << 0;
105 pub const LOG = 1 << 1;
106 pub const SPEC_ALLOW = 1 << 2;
107 pub const NEW_LISTENER = 1 << 3;
108 pub const TSYNC_ESRCH = 1 << 4;
109};
110
111/// Action values for seccomp BPF programs.
112/// The lower 16-bits are for optional return data.
113/// The upper 16-bits are ordered from least permissive values to most.
114pub const RET = struct {
115 /// Kill the process.
116 pub const KILL_PROCESS = 0x80000000;
117 /// Kill the thread.
118 pub const KILL_THREAD = 0x00000000;
119 pub const KILL = KILL_THREAD;
120 /// Disallow and force a SIGSYS.
121 pub const TRAP = 0x00030000;
122 /// Return an errno.
123 pub const ERRNO = 0x00050000;
124 /// Forward the syscall to a userspace supervisor to make a decision.
125 pub const USER_NOTIF = 0x7fc00000;
126 /// Pass to a tracer or disallow.
127 pub const TRACE = 0x7ff00000;
128 /// Allow after logging.
129 pub const LOG = 0x7ffc0000;
130 /// Allow.
131 pub const ALLOW = 0x7fff0000;
132
133 // Masks for the return value sections.
134 pub const ACTION_FULL = 0xffff0000;
135 pub const ACTION = 0x7fff0000;
136 pub const DATA = 0x0000ffff;
137};
138
139pub const IOCTL_NOTIF = struct {
140 pub const RECV = IOCTL.IOWR('!', 0, notif);
141 pub const SEND = IOCTL.IOWR('!', 1, notif_resp);
142 pub const ID_VALID = IOCTL.IOW('!', 2, u64);
143 pub const ADDFD = IOCTL.IOW('!', 3, notif_addfd);
144};
145
146/// Tells the kernel that the supervisor allows the syscall to continue.
147pub const USER_NOTIF_FLAG_CONTINUE = 1 << 0;
148
149/// See seccomp_unotify(2).
150pub const ADDFD_FLAG = struct {
151 pub const SETFD = 1 << 0;
152 pub const SEND = 1 << 1;
153};
154
155pub const data = extern struct {
156 /// The system call number.
157 nr: c_int,
158 /// The CPU architecture/system call convention.
159 /// One of the values defined in `std.os.linux.AUDIT`.
160 arch: u32,
161 instruction_pointer: u64,
162 arg0: u64,
163 arg1: u64,
164 arg2: u64,
165 arg3: u64,
166 arg4: u64,
167 arg5: u64,
168};
169
170/// Used with the ::GET_NOTIF_SIZES command to check if the kernel structures
171/// have changed.
172pub const notif_sizes = extern struct {
173 /// Size of ::notif.
174 notif: u16,
175 /// Size of ::resp.
176 notif_resp: u16,
177 /// Size of ::data.
178 data: u16,
179};
180
181pub const notif = extern struct {
182 /// Unique notification cookie for each filter.
183 id: u64,
184 /// ID of the thread that triggered the notification.
185 pid: u32,
186 /// Bitmask for event information. Currently set to zero.
187 flags: u32,
188 /// The current system call data.
189 data: data,
190};
191
192/// The decision payload the supervisor process sends to the kernel.
193pub const notif_resp = extern struct {
194 /// The filter cookie.
195 id: u64,
196 /// The return value for a spoofed syscall.
197 val: i64,
198 /// Set to zero for a spoofed success or a negative error number for a
199 /// failure.
200 @"error": i32,
201 /// Bitmask containing the decision. Either USER_NOTIF_FLAG_CONTINUE to
202 /// allow the syscall or zero to spoof the return values.
203 flags: u32,
204};
205
206pub const notif_addfd = extern struct {
207 id: u64,
208 flags: u32,
209 srcfd: u32,
210 newfd: u32,
211 newfd_flags: u32,
212};