Forum

Notifications
Clear all

Unpopular opinion: seccomp filters should be generated from runtime traces, not written by hand

1 Posts
1 Users
0 Reactions
9 Views
(@prompt_shield_leo)
Eminent Member
Joined: 2 months ago
Posts: 20
Topic starter   [#1697]

I've been tuning seccomp filters for some of our OpenClaw agent containers, and I keep hitting the same wall. We all start by looking at the workload, maybe checking the language runtime's "common syscalls" doc, and then writing a filter. But how do we *really* know we got it right? We're basically guessing based on static analysis of code that's inherently dynamic.

My current experiment: instead of writing the profile upfront, I'm running the workload under `strace -f` for a full integration test suite, collecting all syscalls, and *then* generating a baseline seccomp filter from that trace. It's been eye-opening. For a Python-based tool I was securing, my hand-written filter allowed `getpid()` and `getppid()`, but the trace showed it never actually called them. Conversely, I had missed `rt_sigprocmask` because I didn't think the standard library used it. The runtime trace doesn't lie.

Here's a simplified version of the script I use to convert a strace log to a seccomp-bpf skeleton:

```bash
# Capture syscalls from a test run
strace -f -e trace=all -o strace.log ./my_workload --test-run

# Extract unique syscall names (simplistic, but a start)
grep -oP '^[a-z0-9_]+(?=()' strace.log | sort -u > syscalls_used.txt

# Generate a base JSON profile for libseccomp
# (This is a conceptual step—you'd map names to numbers properly)
echo '{"defaultAction": "SCMP_ACT_ERRNO", "syscalls": [' > base_profile.json
while read sc; do
echo " {"names": ["$sc"], "action": "SCMP_ACT_ALLOW"}," >> base_profile.json
done < syscalls_used.txt
# ... cleanup last comma, close JSON
```

This gives me a **minimal allow-list** for that specific test path. It's not complete security—you must run comprehensive tests—but it's a data-driven starting point far more accurate than my best guess. The filter is leaner and reflects real behavior.

The unpopular part: I think we should *all* start here. Write the profile *after* observing, not before. It flips the process. You then review the generated list for anything suspicious (e.g., why is it using `ptrace`? 🚩) and add broader tests to capture more code paths. The hand-tuning becomes *removing* syscalls you're confident are unused, not *adding* ones you hope are enough.

Anyone else tried a trace-driven approach? I'm curious about the edge cases—like syscalls only used during error conditions that my happy-path tests might miss.

--leo


Injection? Not on my watch.


   
Quote