core/syscall_filter.sg (sigil-kernel 5299736) adds a per-process syscall allowlist to the sigilOS kernel — a flat 256-byte table per process slot, where byte N = 1 if syscall N is allowed. sflt_check() gates kernel dispatch before capability checks: if a process calls a syscall not in its allowlist, the call is denied before the cap layer even evaluates. Five built-in profiles ship: browser (all syscalls), renderer (minimal: write+fb+srdx_raw), ext (renderer+net), net_proc (write+net), gpu (write+fb+srdx_raw). QEMU PASS: SFT init=1 rnd=1 prf=1 K.
Filter table layout
sflt_slot(pid) = SFLT_BASE + pid × 256. Byte at sflt_slot(pid) + syscall_num = 1 if allowed, 0 if denied. Simple poke8/peek8 — no bitfield math, no overflow hazard. pid 0–7 matches the slot assignments in renderer_spawn.sg + ext_zone.sg.SYS_EPERM to the process — no cap check, no error from the cap layer. Defense in depth: cap checks still run on allowed syscalls.sflt_slot(pid) via poke8 for each syscall in the list. Existing slot contents are zeroed first — no profile leakage from a previous occupant of the same pid slot. Called from renderer_spawn.sg during process setup.Built-in profiles
Five profiles ship in syscall_filter.sg, covering the process types in the sigilOS browser architecture. Each profile is a fixed allowlist — a process type only needs the syscalls relevant to its role.
| Profile | Syscalls allowed | Use |
|---|---|---|
SFLT_BROWSER (0) |
All 256 | Orchestrator (PID 0) — full kernel access |
SFLT_RENDERER (1) |
write, fb_blit, srdx_raw |
Per-tab renderer (PID 1+tab_id) — CAP_IPC only |
SFLT_EXT (2) |
renderer + net syscalls | Extension zone (V2/V3) — renderer + net access |
SFLT_NET_PROC (3) |
write + net |
Net process (PID 3) — networking only |
SFLT_GPU (4) |
write, fb_blit, srdx_raw |
GPU process (PID 2) — display output only |
The browser orchestrator (PID 0) holds the full allowlist because it must coordinate all subsystems. Every other process type is restricted to the minimum surface it needs. A renderer tab with SFLT_RENDERER cannot call net syscalls, cannot call FS syscalls — the filter removes those paths entirely before the cap layer even sees the call.
Relation to cap checks
The syscall filter is not a replacement for capabilities. It is an outer ring: a process that holds no caps but calls write on an allowed syscall will still fail at the cap check. A process with a write cap that calls fs_open on a denied syscall fails before the cap check. Both layers are required and both are active.
This matches the sigilOS security model precisely. Capability isolation is the primary boundary — it governs what an authorized process may do with a given resource. The syscall filter is a secondary ring that narrows what any given process type can even attempt. A compromised renderer tab cannot exfiltrate data over the network even if it somehow acquired a network cap, because net_send is not in SFLT_RENDERER — the call is blocked at the dispatch gate before the cap is consulted.
The two rings compose: cap checks narrow by resource authorization; the syscall filter narrows by process role. Neither alone is sufficient. Together they enforce the principle of least privilege at both the process and the resource level.
QEMU test: syscall_filter_test.sg
syscall_filter_test.sg runs three test vectors (SFT) in ring 0, using sflt_slot() directly:
SFLT_BASE range.sflt_profile_apply on a randomly selected pid; then reads back each byte in the slot and verifies it matches the expected profile allowlist. Confirms write correctness and that zeroing of prior contents is clean.sflt_check is called on boundary cases for each: an allowed syscall returns 1, a denied syscall returns 0. Covers allowed/denied boundary for every profile in a single pass.All three pass on QEMU x86. The K suffix = kernel-level test (runs in ring 0, uses sflt_slot() directly, no userland indirection). Result: SFT init=1 rnd=1 prf=1 K.