Lab 03 — Syscall Policy Synthesizer
Difficulty: 3/5 | Runs locally: yes
Pairs with the Phase 01 WARMUP Chapter 8 (the syscall boundary) and foreshadows the Phase 06 seccomp compiler lab.
Why This Lab Exists (Purpose & Goal)
The system-call interface is the security boundary of an operating system — everything dangerous a process can do, it does through a syscall. If you can restrict which syscalls a process may make, you shrink what a compromised process can do even when its code is fully attacker-controlled. The goal of this lab is to learn how a least-privilege syscall allowlist is actually built: by observing the calls a program makes across many legitimate paths, then turning those observations into a candidate policy — without ever blindly trusting the observation as if it were proof.
The Concept, In the Weeds
The naive approach — "trace the program once, allow whatever it called" — is dangerously incomplete, and this lab is designed to make you feel why:
- One trace under-counts. A single happy-path run misses the syscalls used on error paths, in locale handling, in DNS resolution, in thread startup, in the allocator's slow path, and after a dependency upgrade. An allowlist built from one trace will break the service in production the first time it hits an unobserved path.
- Observation is a starting point, not authorization. Just because a program did call
ptraceonce does not mean it should be allowed to — it might be debug code that should never ship. So the synthesizer produces candidate additions for human review and separately flags forbidden calls (ptrace,mount, kernel-module administration, raw networking) that signal something is wrong, but it never auto-approves. - seccomp is not semantic authorization. Allowing
openatdoes not decide which file; the path is a pointer seccomp can't dereference (you'll prove this in Phase 06). So the allowlist controls the verbs, while filesystem/LSM policy controls the targets.
Why This Matters for Protecting the Company
Syscall confinement is what turns a remote code execution into a contained, low-value foothold instead of a host takeover. It is the workhorse behind container runtimes, browser sandboxes, and serverless microVM jailers. Knowing how to generate and review a tight policy — rather than shipping the permissive default or, worse, disabling it because it "broke something" — is directly how you reduce the blast radius of the next bug in a service the company runs. The "observe, then review, never auto-approve" discipline is also how you avoid the opposite failure: a policy so tight it causes outages and gets ripped out.
How This Is Used on the Job
This is the on-ramp to writing real seccomp profiles and systemd SystemCallFilter directives
(Phase 05/06). In production you'd collect observations with strace -f, seccomp audit logs, or
libseccomp's logging action, across startup/steady-state/error/shutdown, then synthesize and review
the allowlist, then regression-test it after every dependency bump (the sibling Phase 06
seccomp-regression lab).
Attack / Failure Fixtures
ptrace (read/write another process's memory — sandbox escape), mount (filesystem manipulation),
kernel-module administration, and raw networking — each a syscall whose appearance in a normal web
service is a signal, not a default to allow.
Build It
Implement the synthesizer: compare observed syscalls from multiple legitimate execution paths against an approved baseline; emit candidate additions and forbidden calls separately; never auto-approve.
LAB_MODULE=solution pytest -q
# On Linux: collect observations with `strace` from OWNED binaries across multiple paths.
Validation — What You Should Be Able to Do Now
- Explain why an allowlist built from a single trace breaks in production, and which paths you must exercise to build a complete one.
- Articulate why "the program called it" is not "the program may call it," and design a human-review step into policy synthesis.
- Explain why seccomp controls verbs but not targets, and what layer enforces the target.
The Broader Perspective
You practiced a pattern that recurs everywhere in security automation: observe behavior to propose a policy, but keep a human in the loop for authorization. The same shape appears in detection engineering (a candidate detection from observed telemetry, reviewed before it pages anyone), in least-privilege IAM (proposed roles from access logs, reviewed before grant), and in AI agents (proposed actions from a model, decided by deterministic policy). Automation accelerates the proposal; judgment owns the grant. Confusing the two is how both over-permissioned and outage-causing policies get shipped.
Interview Angle
- "How do you build a seccomp allowlist for a service?" — Observe its real syscalls across startup, steady state, error, locale, DNS, and shutdown; synthesize a default-deny allowlist; review it (don't auto-approve); pin the architecture; regression-test it after upgrades. Targets (paths, destinations) are enforced by the filesystem/LSM/network, not seccomp.
References
- Phase 01 WARMUP Chapter 8;
seccomp(2),strace(1)documentation. - Phase 06 seccomp compiler lab.