Sandbox: seccomp user-notify fallback for Termux and kernels without Landlock

- Child installs a seccomp filter with a listener; a supervisor thread
  resolves the path of write-type syscalls (cwd, dirfd, .., symlinks,
  /proc/self, /dev/stdout, both ends of rename/link) and answers
  CONTINUE or EACCES; IPv4/IPv6 sockets are refused
- Probe order Landlock -> seccomp -> none, checked in a throwaway child;
  CST_SANDBOX=landlock|seccomp|off
- curl connect failures count as sandbox refusals
- test_sandbox runs the detected sandbox and forced seccomp, with escape,
  symlink, rename, link, chmod, stdout, ENOENT, background and network cases
- Docs: platform matrix, limits (not a security boundary, Yama and
  background jobs)
This commit is contained in:
WHC
2026-10-06 00:25:24 +08:00
parent 7d3ec9fcb9
commit 8efd63bb31
6 changed files with 933 additions and 28 deletions
+15 -4
View File
@@ -6,6 +6,11 @@
* are limited to the workspace, the temp dir, /dev/null and /dev/tty, and
* the network is cut where the OS can do it.
* Linux Landlock (network needs ABI 4, kernel 6.7+)
* Linux seccomp user-notify when Landlock is missing (Android/Termux,
* older kernels): a supervisor thread checks the path of every
* write-type syscall and IPv4/IPv6 sockets are refused. Guards
* against mistakes, not hostile code (the path is checked before
* the kernel reads it again).
* macOS sandbox_init (Seatbelt profile)
* other none: callers fall back to rule-based approval
*/
@@ -21,12 +26,17 @@ extern "C" {
typedef enum {
CST_SANDBOX_NONE = 0,
CST_SANDBOX_LANDLOCK,
CST_SANDBOX_SEATBELT
CST_SANDBOX_SEATBELT,
CST_SANDBOX_SECCOMP
} cst_sandbox_kind_t;
/* What this process can use; cached. CST_SANDBOX=0 forces NONE. */
/* What this process can use; cached. CST_SANDBOX=off|0|none forces NONE,
* CST_SANDBOX=landlock|seccomp allows only that kind. */
int cst_sandbox_probe(void);
const char *cst_sandbox_name(int kind); /* "landlock" | "seatbelt" | "none" */
/* Drops the cache and probes again (tests that change CST_SANDBOX). */
int cst_sandbox_reprobe(void);
/* "landlock" | "seatbelt" | "seccomp" | "none" */
const char *cst_sandbox_name(int kind);
/* Landlock ABI version (0 when unavailable); ≥4 also blocks TCP. */
int cst_sandbox_landlock_abi(void);
@@ -34,13 +44,14 @@ typedef struct {
int kind;
int ruleset_fd; /* Landlock, built in the parent */
char *profile; /* Seatbelt */
void *sc; /* seccomp: filter program and supervisor thread */
} cst_sandbox_plan_t;
/* Parent side: build the policy. Returns 0, or -1 (plan->kind = NONE). */
int cst_sandbox_prepare(const char *workspace, const char *tmpdir,
cst_sandbox_plan_t *plan);
/* Child side, after fork and before exec (os_run_capture_ex hook):
* async-signal-safe for Landlock. Returns 0 when confined. */
* async-signal-safe. Returns 0 when confined. */
int cst_sandbox_enter(void *plan);
void cst_sandbox_release(cst_sandbox_plan_t *plan);