← Blog
0.7.0 · KERNEL · SECURE · BROWSER

Per-Process Syscall Filter — Browser Sandbox Allowlist (sigil-kernel 5299736)

June 22, 2026 · sigil-kernel 5299736 · Sigil-Docs
kernel security browser syscall sandbox 0.7.0

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_BASE = 0xFF4000
8 process slots × 256B each = 2KB total. 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.
sflt_check(pid, syscall_num)
Called at the top of the kernel dispatch path, before capability evaluation. Returns 1 if allowed, 0 if denied. On denial, the kernel returns 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_profile_apply(pid, profile_id)
Writes the profile's allowlist to 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.
sflt_reset(pid)
Zeroes the entire 256B slot. Called on process exit to ensure the slot is clean before reuse.

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:

SFT-INIT
All 8 slots start zeroed. Reads every byte of every slot at boot; confirms no slot carries a non-zero entry before any profile is applied. Catches stale memory at SFLT_BASE range.
SFT-RAND
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.
SFT-PRF
All 5 profiles applied to 5 pids. 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.