@amritk/nish-x86_64-linux 0.27.2 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,470 @@
1
+ /* Nish runtime, the sandbox half: `--sandbox` (docs/wp37-os-sandbox.md)
2
+ * confines a native program to the capabilities its `main` reaches, and this
3
+ * file is the part that asks the kernel to.
4
+ *
5
+ * A translation unit of its own for the reason the other halves are one: each
6
+ * file carries its own measured `.text*` ceiling in tests/runtime-budgets.js,
7
+ * and runtime-os.c had 6 bytes of its ceiling left. scripts/build.sh pairs it
8
+ * with runtime.c like the others, and a program built without `--sandbox`
9
+ * calls nothing here, so `--gc-sections` drops all of it.
10
+ *
11
+ * The compiler emits one call, from an entry in `.preinit_array`, so it runs
12
+ * before any constructor in the executable, an `ffi` object or a shared
13
+ * library:
14
+ *
15
+ * nish_sandbox(caps, mode, read_dirs, write_dirs)
16
+ *
17
+ * `caps` is `main`'s capability mask (src/capabilities.ts numbers the bits),
18
+ * `mode` is 1 for `--sandbox` and 2 for `--sandbox=best-effort`. The two
19
+ * directory lists are the `--allow fs.read=<dir>` and `--allow fs.write=<dir>`
20
+ * scopes, each a run of NUL-terminated absolute paths ended by an empty one,
21
+ * or null when the capability is granted whole or not at all; a list moves
22
+ * its capability's rights from the rule on `/` to one rule per directory.
23
+ *
24
+ * What it does, in the order of the note's section 3, each step before the
25
+ * next because each needs the one before it:
26
+ *
27
+ * 1. prime the thread pool's ceiling, which reads /proc and /sys files that
28
+ * step 4 may close;
29
+ * 2. `PR_SET_NO_NEW_PRIVS`, which Landlock and seccomp need and which keeps
30
+ * an `exec` from gaining a capability back;
31
+ * 3. drop every capability, and read each set back;
32
+ * 4. Landlock: handle every filesystem right the running ABI knows, then
33
+ * grant back beneath `/` exactly what the mask grants whole, and beneath
34
+ * each scope directory what its capability grants;
35
+ * 5. the seccomp filter of the note's section 4, a denylist;
36
+ * 6. set the flag `nish_sandbox_check` reads at the top of `main`, so a C
37
+ * library that never ran `.preinit_array` (musl) refuses to run rather
38
+ * than running unconfined.
39
+ *
40
+ * A failure prints one line, `nish: --sandbox: <what> (<errno name>)`, and
41
+ * ends the process with `_exit(126)`, the shell's "found but cannot run", in
42
+ * both modes. Best-effort differs only where the kernel lacks a feature: no
43
+ * Landlock, or a Landlock too old to withhold a right; it then says what it
44
+ * could not withhold, once, and runs. No environment variable, file or
45
+ * argument is read that could widen any of it: step 1 reaches
46
+ * `getenv("NISH_NUM_THREADS")` through `nish_cpu_online`, which can only
47
+ * lower the pool's ceiling, and confines nothing. The one knob,
48
+ * `-DNISH_SANDBOX_SIMULATE_ABI=<n>`, makes the ABI probe answer `n` in a
49
+ * test-only build of this file; scripts/build.sh never passes it.
50
+ *
51
+ * Messages go through write(2) rather than stdio: nothing here may allocate or
52
+ * touch a `FILE` before libc has finished its own start-up. */
53
+ #define _GNU_SOURCE
54
+ #include "nish.h"
55
+
56
+ #include <unistd.h>
57
+
58
+ #ifdef __linux__
59
+
60
+ #include <errno.h>
61
+ #include <fcntl.h>
62
+ #include <stddef.h>
63
+ #include <string.h>
64
+ #include <sys/prctl.h>
65
+ #include <sys/syscall.h>
66
+ #include <linux/audit.h>
67
+ #include <linux/capability.h>
68
+ #include <linux/filter.h>
69
+ #include <linux/seccomp.h>
70
+
71
+ /* The capability bits of src/capabilities.ts this file reads. */
72
+ #define CAP_BIT_FS_READ (1 << 5)
73
+ #define CAP_BIT_FS_WRITE (1 << 6)
74
+ #define CAP_BIT_NET (1 << 7)
75
+ #define CAP_BIT_SPAWN (1 << 8)
76
+
77
+ /* Landlock's numbers, spelled here rather than taken from <linux/landlock.h>,
78
+ * which an older toolchain lacks and whose newest rights it lacks too. */
79
+ #define LL_CREATE 444
80
+ #define LL_ADD_RULE 445
81
+ #define LL_RESTRICT 446
82
+ #define LL_CREATE_RULESET_VERSION 1u
83
+ #define LL_RULE_PATH_BENEATH 1
84
+ #define LL_EXECUTE (1ull << 0)
85
+ #define LL_WRITE_FILE (1ull << 1)
86
+ #define LL_READ_FILE (1ull << 2)
87
+ #define LL_READ_DIR (1ull << 3)
88
+ #define LL_REFER (1ull << 13)
89
+ #define LL_TRUNCATE (1ull << 14)
90
+ #define LL_IOCTL_DEV (1ull << 15)
91
+ /* REMOVE_DIR through MAKE_SYM, bits 4 to 12: every right that makes or removes
92
+ * a name. */
93
+ #define LL_MAKE_REMOVE (((1ull << 13) - 1) & ~(LL_EXECUTE | LL_WRITE_FILE | LL_READ_FILE | LL_READ_DIR))
94
+ #define LL_ABI1 ((1ull << 13) - 1)
95
+ #define LL_SCOPE_ALL 3ull /* ABSTRACT_UNIX_SOCKET | SIGNAL, ABI 6 */
96
+
97
+ struct nish_ll_ruleset {
98
+ uint64_t handled_access_fs;
99
+ uint64_t handled_access_net;
100
+ uint64_t scoped;
101
+ };
102
+
103
+ struct nish_ll_path_beneath {
104
+ uint64_t allowed_access;
105
+ int32_t parent_fd;
106
+ } __attribute__((packed));
107
+
108
+ #ifndef SECCOMP_RET_KILL_PROCESS
109
+ #define SECCOMP_RET_KILL_PROCESS 0x80000000u
110
+ #endif
111
+ #ifndef SECCOMP_GET_ACTION_AVAIL
112
+ #define SECCOMP_GET_ACTION_AVAIL 2
113
+ #endif
114
+ #ifndef SECCOMP_FILTER_FLAG_TSYNC
115
+ #define SECCOMP_FILTER_FLAG_TSYNC 1
116
+ #endif
117
+ #ifndef SYS_seccomp
118
+ #if defined(__x86_64__)
119
+ #define SYS_seccomp 317
120
+ #else
121
+ #define SYS_seccomp 277
122
+ #endif
123
+ #endif
124
+ #ifndef PR_CAP_AMBIENT
125
+ #define PR_CAP_AMBIENT 47
126
+ #define PR_CAP_AMBIENT_IS_SET 1
127
+ #define PR_CAP_AMBIENT_CLEAR_ALL 4
128
+ #endif
129
+
130
+ #define KILL SECCOMP_RET_KILL_PROCESS
131
+ #define CLONE_THREAD_FLAG 0x00010000u
132
+ #define EPERM_RET (SECCOMP_RET_ERRNO | EPERM)
133
+ #define ENOSYS_RET (SECCOMP_RET_ERRNO | ENOSYS)
134
+
135
+ /* The syscall numbers each row of the note's section 4 names, per
136
+ * architecture. A number the running kernel does not have matches nothing.
137
+ * AArch64 has no `fork`, `vfork`, `chmod`, `chown`, `lchown`, `utime`,
138
+ * `utimes` or `futimesat`: only the `*at` and `f` forms. */
139
+ #if defined(__x86_64__)
140
+ #define NISH_SB_ARCH AUDIT_ARCH_X86_64
141
+ static const uint16_t nish_sb_always[] = {101, 310, 311, 438}; /* ptrace, process_vm_readv/writev, pidfd_getfd */
142
+ static const uint16_t nish_sb_net[] = {41, 53, 42, 49, 50, 43, 288}; /* socket ... accept4 */
143
+ static const uint16_t nish_sb_spawn[] = {59, 322, 57, 58}; /* execve, execveat, fork, vfork */
144
+ #define NISH_SB_CLONE 56
145
+ static const uint16_t nish_sb_meta[] = {
146
+ 90, 91, 268, 452, /* chmod, fchmod, fchmodat, fchmodat2 */
147
+ 92, 93, 94, 260, /* chown, fchown, lchown, fchownat */
148
+ 132, 235, 261, 280, /* utime, utimes, futimesat, utimensat */
149
+ 188, 189, 190, 463, /* setxattr, lsetxattr, fsetxattr, setxattrat */
150
+ 197, 198, 199, 466, /* removexattr, lremovexattr, fremovexattr, removexattrat */
151
+ };
152
+ #elif defined(__aarch64__)
153
+ #define NISH_SB_ARCH AUDIT_ARCH_AARCH64
154
+ static const uint16_t nish_sb_always[] = {117, 270, 271, 438};
155
+ static const uint16_t nish_sb_net[] = {198, 199, 203, 200, 201, 202, 242};
156
+ static const uint16_t nish_sb_spawn[] = {221, 281};
157
+ #define NISH_SB_CLONE 220
158
+ static const uint16_t nish_sb_meta[] = {
159
+ 52, 53, 452, /* fchmod, fchmodat, fchmodat2 */
160
+ 55, 54, /* fchown, fchownat */
161
+ 88, /* utimensat */
162
+ 5, 6, 7, 463, /* setxattr, lsetxattr, fsetxattr, setxattrat */
163
+ 14, 15, 16, 466, /* removexattr, lremovexattr, fremovexattr, removexattrat */
164
+ };
165
+ #endif
166
+ static const uint16_t nish_sb_uring[] = {425, 426, 427}; /* io_uring_setup, _enter, _register */
167
+ #define NISH_SB_CLONE3 435
168
+
169
+ /* Set by step 6 and read by `nish_sandbox_check`. */
170
+ static int nish_sb_applied = 0;
171
+
172
+ /* The errno names a failure prints; a number for any other. */
173
+ static const char *nish_sb_errno_name(int e, char *buf) {
174
+ switch (e) {
175
+ case EPERM: return "EPERM";
176
+ case ENOSYS: return "ENOSYS";
177
+ case EOPNOTSUPP: return "EOPNOTSUPP";
178
+ case EINVAL: return "EINVAL";
179
+ case EACCES: return "EACCES";
180
+ case ENOENT: return "ENOENT";
181
+ case EFAULT: return "EFAULT";
182
+ case E2BIG: return "E2BIG";
183
+ case ENOMEM: return "ENOMEM";
184
+ case EBUSY: return "EBUSY";
185
+ }
186
+ char digits[12];
187
+ int n = 0;
188
+ unsigned u = (unsigned)e;
189
+ do digits[n++] = (char)('0' + u % 10); while ((u /= 10) != 0 && n < 11);
190
+ memcpy(buf, "errno ", 6);
191
+ for (int i = 0; i < n; i++) buf[6 + i] = digits[n - 1 - i];
192
+ buf[6 + n] = 0;
193
+ return buf;
194
+ }
195
+
196
+ /* One line on stderr from up to three pieces; a null piece is left out. */
197
+ static void nish_sb_say(const char *a, const char *b, const char *c) {
198
+ const char *parts[4] = {"nish: --sandbox: ", a, b, c};
199
+ for (int i = 0; i < 4; i++)
200
+ if (parts[i]) (void)!write(2, parts[i], strlen(parts[i]));
201
+ (void)!write(2, "\n", 1);
202
+ }
203
+
204
+ /* The one way out on a failure, in both modes; `path`, when not null, is
205
+ * named after `what`. */
206
+ static void nish_sb_fail_at(const char *what, const char *path) {
207
+ char buf[24];
208
+ char tail[40] = " (";
209
+ const char *name = nish_sb_errno_name(errno, buf);
210
+ size_t len = strlen(name);
211
+ memcpy(tail + 2, name, len);
212
+ memcpy(tail + 2 + len, ")", 2);
213
+ nish_sb_say(what, path, tail);
214
+ _exit(126);
215
+ }
216
+
217
+ static void nish_sb_fail(const char *what) { nish_sb_fail_at(what, 0); }
218
+
219
+ /* Step 3. `capset` with every set empty needs no privilege; the bounding set
220
+ * needs CAP_SETPCAP to drop, and need not be dropped without it, because with
221
+ * NO_NEW_PRIVS and an empty permitted set nothing it allows can be gained.
222
+ * Each set is read back rather than trusted, since a call that answered 0
223
+ * and left a capability would otherwise run the program privileged. */
224
+ static void nish_sb_drop_capabilities(void) {
225
+ struct __user_cap_header_struct head = {_LINUX_CAPABILITY_VERSION_3, 0};
226
+ struct __user_cap_data_struct data[2];
227
+ if (syscall(SYS_capget, &head, data) != 0) nish_sb_fail("cannot read the capability sets");
228
+ int setpcap = (data[0].effective >> CAP_SETPCAP) & 1;
229
+ if (setpcap)
230
+ for (int cap = 0; cap < 64; cap++)
231
+ if (prctl(PR_CAPBSET_DROP, cap, 0, 0, 0) != 0 && errno != EINVAL) nish_sb_fail("cannot empty the bounding set");
232
+ /* EINVAL is a kernel older than 4.3, which has no ambient set to clear. */
233
+ if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0) != 0 && errno != EINVAL)
234
+ nish_sb_fail("cannot clear the ambient set");
235
+ memset(data, 0, sizeof data);
236
+ if (syscall(SYS_capset, &head, data) != 0) nish_sb_fail("cannot drop the capabilities");
237
+ if (syscall(SYS_capget, &head, data) != 0) nish_sb_fail("cannot read the capability sets");
238
+ errno = EPERM;
239
+ for (int i = 0; i < 2; i++)
240
+ if (data[i].effective | data[i].permitted | data[i].inheritable) nish_sb_fail("a capability survived the drop");
241
+ for (int cap = 0; cap < 64; cap++) {
242
+ if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, cap, 0, 0) == 1) nish_sb_fail("an ambient capability survived the drop");
243
+ if (setpcap && prctl(PR_CAPBSET_READ, cap, 0, 0, 0) == 1) nish_sb_fail("a bounding capability survived the drop");
244
+ }
245
+ }
246
+
247
+ /* The Landlock ABI the kernel answers, or -1 with errno set. */
248
+ static int nish_sb_landlock_abi(void) {
249
+ #ifdef NISH_SANDBOX_SIMULATE_ABI
250
+ if (NISH_SANDBOX_SIMULATE_ABI == 0) {
251
+ errno = ENOSYS;
252
+ return -1;
253
+ }
254
+ return NISH_SANDBOX_SIMULATE_ABI;
255
+ #else
256
+ return (int)syscall(LL_CREATE, NULL, 0, LL_CREATE_RULESET_VERSION);
257
+ #endif
258
+ }
259
+
260
+ /* The rights the mask grants, whatever the ABI knows. `process.spawn` grants
261
+ * EXECUTE and READ_FILE beneath `/` whatever `fs.read` says, since a child
262
+ * must read and execute its binary and libraries wherever PATH finds them,
263
+ * and IOCTL_DEV, since a device's ioctl can act like a spawn (TIOCSTI). */
264
+ static uint64_t nish_sb_granted(int32_t caps) {
265
+ uint64_t rights = 0;
266
+ if (caps & CAP_BIT_FS_READ) rights |= LL_READ_FILE | LL_READ_DIR;
267
+ if (caps & CAP_BIT_FS_WRITE) rights |= LL_WRITE_FILE | LL_MAKE_REMOVE | LL_REFER | LL_TRUNCATE;
268
+ if (caps & CAP_BIT_SPAWN) rights |= LL_EXECUTE | LL_READ_FILE | LL_IOCTL_DEV;
269
+ return rights;
270
+ }
271
+
272
+ /* Appends to the best-effort line the name of each right the ABI could not
273
+ * withhold. REFER is never named: below ABI 2 the kernel refuses every
274
+ * rename or link across directories, which is stricter, not weaker. */
275
+ static size_t nish_sb_name(char *line, size_t at, const char *name) {
276
+ size_t len = strlen(name);
277
+ if (at) memcpy(line + at, ", ", 2), at += 2;
278
+ memcpy(line + at, name, len);
279
+ return at + len;
280
+ }
281
+
282
+ /* One path_beneath rule granting `rights` beneath `path`. A rule granting
283
+ * nothing is refused (ENOMSG), and nothing is what it would add, so it is
284
+ * left out. A scope directory gone since the build refuses strict; under
285
+ * best-effort it is named and its rule left out, which withholds the right
286
+ * there too, so the program loses the directory rather than gaining more. */
287
+ static void nish_sb_rule(int ruleset, const char *path, uint64_t rights, int32_t mode) {
288
+ struct nish_ll_path_beneath rule = {rights, -1};
289
+ if (!rights) return;
290
+ rule.parent_fd = open(path, O_PATH | O_CLOEXEC | O_DIRECTORY);
291
+ if (rule.parent_fd < 0) {
292
+ if (mode != 2) nish_sb_fail_at("cannot open the directory ", path);
293
+ nish_sb_say("the scope directory ", path, " cannot be opened, so the program gets no access beneath it");
294
+ return;
295
+ }
296
+ if (syscall(LL_ADD_RULE, ruleset, LL_RULE_PATH_BENEATH, &rule, 0) != 0) nish_sb_fail("cannot add the Landlock rule");
297
+ close(rule.parent_fd);
298
+ }
299
+
300
+ /* One rule per directory of a scope list, each granting `rights`. */
301
+ static void nish_sb_scope(int ruleset, const char *dirs, uint64_t rights, int32_t mode) {
302
+ for (; dirs && *dirs; dirs += strlen(dirs) + 1) nish_sb_rule(ruleset, dirs, rights, mode);
303
+ }
304
+
305
+ /* Step 4. Every right the ABI knows is handled, and one path_beneath rule on
306
+ * `/` grants back what the mask grants whole, so an unscoped right is withheld
307
+ * everywhere or nowhere; a scoped capability's rights are granted beneath its
308
+ * directories alone. Returns with nothing done when there is no Landlock and
309
+ * the mode is best-effort. */
310
+ static void nish_sb_landlock(int32_t caps, int32_t mode, const char *read_dirs, const char *write_dirs) {
311
+ int abi = nish_sb_landlock_abi();
312
+ if (abi < 0) {
313
+ /* EPERM is a container runtime's own seccomp profile refusing the call;
314
+ * EOPNOTSUPP is Landlock built but not in the lsm= list. */
315
+ if (errno != ENOSYS && errno != EOPNOTSUPP && errno != EPERM) nish_sb_fail("cannot read the Landlock ABI");
316
+ if (mode != 2) {
317
+ nish_sb_say("this kernel has no Landlock; build with --sandbox=best-effort to run with seccomp alone", 0, 0);
318
+ _exit(126);
319
+ }
320
+ nish_sb_say("no Landlock, files are not confined", 0, 0);
321
+ return;
322
+ }
323
+ uint64_t known = LL_ABI1;
324
+ if (abi >= 2) known |= LL_REFER;
325
+ if (abi >= 3) known |= LL_TRUNCATE;
326
+ if (abi >= 5) known |= LL_IOCTL_DEV;
327
+ /* A scoped capability grants nothing beneath `/`, so a scoped fs.write
328
+ * below ABI 3 loses TRUNCATE everywhere and refuses strict like any other
329
+ * build that cannot withhold it. */
330
+ int32_t whole = caps;
331
+ if (read_dirs) whole &= ~CAP_BIT_FS_READ;
332
+ if (write_dirs) whole &= ~CAP_BIT_FS_WRITE;
333
+ uint64_t granted = nish_sb_granted(whole);
334
+ uint64_t lost = ~granted & ~known & (LL_TRUNCATE | LL_IOCTL_DEV);
335
+ int unscoped = abi < 6;
336
+ /* Strict runs without IOCTL_DEV and without the ABI 6 scopes: a terminal
337
+ * already on fd 0 gives the first on every ABI, and the second is no Nish
338
+ * builtin's (open questions 8 and 12). Only TRUNCATE refuses it. */
339
+ if (mode != 2 && (lost & LL_TRUNCATE)) {
340
+ nish_sb_say("this kernel's Landlock cannot withhold TRUNCATE; build with --sandbox=best-effort to run without it", 0, 0);
341
+ _exit(126);
342
+ }
343
+ if (mode == 2 && (lost || unscoped)) {
344
+ char line[80];
345
+ size_t at = 0;
346
+ if (lost & LL_TRUNCATE) at = nish_sb_name(line, at, "TRUNCATE");
347
+ if (lost & LL_IOCTL_DEV) at = nish_sb_name(line, at, "IOCTL_DEV");
348
+ if (unscoped) at = nish_sb_name(line, at, "the signal and abstract socket scopes");
349
+ line[at] = 0;
350
+ nish_sb_say("this kernel's Landlock cannot withhold ", line, ", so they are not confined");
351
+ }
352
+ struct nish_ll_ruleset attr = {known, 0, abi >= 6 ? LL_SCOPE_ALL : 0};
353
+ int ruleset = (int)syscall(LL_CREATE, &attr, sizeof attr, 0);
354
+ if (ruleset < 0) nish_sb_fail("cannot create the Landlock ruleset");
355
+ nish_sb_rule(ruleset, "/", granted & known, 1);
356
+ if (read_dirs && (caps & CAP_BIT_FS_READ)) nish_sb_scope(ruleset, read_dirs, nish_sb_granted(CAP_BIT_FS_READ) & known, mode);
357
+ if (write_dirs && (caps & CAP_BIT_FS_WRITE)) nish_sb_scope(ruleset, write_dirs, nish_sb_granted(CAP_BIT_FS_WRITE) & known, mode);
358
+ if (syscall(LL_RESTRICT, ruleset, 0) != 0) nish_sb_fail("cannot apply the Landlock ruleset");
359
+ close(ruleset);
360
+ }
361
+
362
+ /* Appends `JEQ nr; RET action` for each number to the program at `at`. */
363
+ static int nish_sb_rows(struct sock_filter *prog, int at, const uint16_t *nrs, int count, uint32_t action) {
364
+ for (int i = 0; i < count; i++) {
365
+ prog[at++] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, nrs[i], 0, 1);
366
+ prog[at++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, action);
367
+ }
368
+ return at;
369
+ }
370
+
371
+ #define NISH_SB_ROWS(prog, at, nrs, action) nish_sb_rows(prog, at, nrs, (int)(sizeof nrs / sizeof nrs[0]), action)
372
+
373
+ /* The low word of `clone`'s flags, where CLONE_THREAD lives. */
374
+ #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
375
+ #define NISH_SB_ARG0_LOW (offsetof(struct seccomp_data, args) + 4)
376
+ #else
377
+ #define NISH_SB_ARG0_LOW offsetof(struct seccomp_data, args)
378
+ #endif
379
+
380
+ /* Step 5: a denylist (open question 4), each row of the note's section 4.
381
+ * The arch check comes first and kills on a mismatch, so a 32-bit entry
382
+ * cannot step around the 64-bit numbers; on x86-64 the next instruction
383
+ * kills any number with bit 30 set, since an x32 call reports
384
+ * AUDIT_ARCH_X86_64 and would match none of them. */
385
+ static void nish_sb_seccomp(int32_t caps) {
386
+ uint32_t action = SECCOMP_RET_KILL_PROCESS;
387
+ if (syscall(SYS_seccomp, SECCOMP_GET_ACTION_AVAIL, 0, &action) != 0)
388
+ nish_sb_fail("this kernel has no seccomp filter that kills the process");
389
+ struct sock_filter prog[96];
390
+ int at = 0;
391
+ prog[at++] = (struct sock_filter)BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, arch));
392
+ prog[at++] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, NISH_SB_ARCH, 1, 0);
393
+ prog[at++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, KILL);
394
+ prog[at++] = (struct sock_filter)BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr));
395
+ #if defined(__x86_64__)
396
+ prog[at++] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, 0x40000000, 0, 1);
397
+ prog[at++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, KILL);
398
+ #endif
399
+ at = NISH_SB_ROWS(prog, at, nish_sb_always, KILL);
400
+ if (!(caps & CAP_BIT_NET)) at = NISH_SB_ROWS(prog, at, nish_sb_net, KILL);
401
+ /* io_uring opens, connects and accepts sockets without those calls. */
402
+ if ((caps & (CAP_BIT_NET | CAP_BIT_SPAWN)) != (CAP_BIT_NET | CAP_BIT_SPAWN))
403
+ at = NISH_SB_ROWS(prog, at, nish_sb_uring, KILL);
404
+ if (!(caps & CAP_BIT_FS_WRITE)) at = NISH_SB_ROWS(prog, at, nish_sb_meta, EPERM_RET);
405
+ if (!(caps & CAP_BIT_SPAWN)) {
406
+ at = NISH_SB_ROWS(prog, at, nish_sb_spawn, KILL);
407
+ /* clone3's flags are in a struct seccomp cannot read, so it answers
408
+ * ENOSYS and glibc falls back to clone, where they are visible. */
409
+ static const uint16_t clone3[] = {NISH_SB_CLONE3};
410
+ at = NISH_SB_ROWS(prog, at, clone3, ENOSYS_RET);
411
+ /* clone is a thread when CLONE_THREAD is set and a process otherwise.
412
+ * The accumulator is overwritten, so this is the last row. */
413
+ prog[at++] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, NISH_SB_CLONE, 0, 4);
414
+ prog[at++] = (struct sock_filter)BPF_STMT(BPF_LD | BPF_W | BPF_ABS, NISH_SB_ARG0_LOW);
415
+ prog[at++] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, CLONE_THREAD_FLAG, 0, 1);
416
+ prog[at++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);
417
+ prog[at++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, KILL);
418
+ }
419
+ prog[at++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);
420
+ struct sock_fprog fprog = {(unsigned short)at, prog};
421
+ /* Under TSYNC a thread that cannot take the filter is answered with its id,
422
+ * not -1, and errno says nothing, so that failure names no errno. */
423
+ long loaded = syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, &fprog);
424
+ if (loaded > 0) {
425
+ nish_sb_say("cannot load the seccomp filter: a thread could not take it", 0, 0);
426
+ _exit(126);
427
+ }
428
+ if (loaded != 0) nish_sb_fail("cannot load the seccomp filter");
429
+ }
430
+
431
+ void nish_sandbox(int32_t caps, int32_t mode, const char *read_dirs, const char *write_dirs) {
432
+ nish_cpu_prime();
433
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) nish_sb_fail("cannot set no_new_privs");
434
+ nish_sb_drop_capabilities();
435
+ nish_sb_landlock(caps, mode, read_dirs, write_dirs);
436
+ /* Metadata writes stay EPERM inside a scoped write directory (open
437
+ * question 1): Landlock has no right for chmod, chown or xattrs. */
438
+ nish_sb_seccomp(write_dirs ? caps & ~CAP_BIT_FS_WRITE : caps);
439
+ __atomic_store_n(&nish_sb_applied, 1, __ATOMIC_RELEASE);
440
+ }
441
+
442
+ void nish_sandbox_check(void) {
443
+ if (__atomic_load_n(&nish_sb_applied, __ATOMIC_ACQUIRE)) return;
444
+ nish_sb_say("the C library did not run .preinit_array, so the program was not confined", 0, 0);
445
+ _exit(126);
446
+ }
447
+
448
+ #else
449
+
450
+ /* Every other platform: a target-neutral `.ll` built with `--sandbox` and
451
+ * linked by hand here refuses to run rather than running unconfined. Both
452
+ * symbols refuse, because a platform that never runs `.preinit_array` reaches
453
+ * only the check. */
454
+ static void nish_sb_none(void) {
455
+ static const char line[] = "nish: --sandbox: this platform has no sandbox\n";
456
+ (void)!write(2, line, sizeof line - 1);
457
+ _exit(126);
458
+ }
459
+
460
+ void nish_sandbox(int32_t caps, int32_t mode, const char *read_dirs, const char *write_dirs) {
461
+ (void)caps;
462
+ (void)mode;
463
+ (void)read_dirs;
464
+ (void)write_dirs;
465
+ nish_sb_none();
466
+ }
467
+
468
+ void nish_sandbox_check(void) { nish_sb_none(); }
469
+
470
+ #endif