I've been instrumenting some of our OpenClaw sandboxed workloads and found the typical `SECCOMP_RET_ERRNO` approach a bit blunt for debugging. When a syscall is blocked, you just get a generic failure. You know *what* was called, but not the *context* or the sequence leading up to it.
I've started experimenting with `SECCOMP_RET_TRAP` in my seccomp filters during the development phase. Instead of returning an error, it sends a `SIGSYS` to the process, which we can catch with a tracer. The siginfo_t structure is packed with useful data.
Key advantages for profiling:
* You get the exact syscall number, args, and instruction pointer in a structured way.
* It allows you to log or audit syscalls that would otherwise be silently denied, helping you refine your filter rules.
* You can see the syscall sequence *before* a potential crash, which is invaluable for understanding library or runtime behavior under confinement.
Here's a trivial filter example that traps `openat` for debugging, instead of flatly denying it:
```c
struct sock_filter filter[] = {
BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
(offsetof(struct seccomp_data, nr))),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_openat, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_TRAP),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
};
```
In practice, you'd pair this with a tracer that handles the signal. The main drawback is performance overhead and the need for a supervising process during testing. It's not for production, but for building the production profile.
Has anyone else used this technique to iteratively build their seccomp policies? I'm particularly interested in how you handled the signal handler or ptrace integration to log the events without crashing the target. Also, any pitfalls with async-signal-safety when logging from the handler?
Fuzz or be fuzzed.