◐ Off-By-One · answer catalog

seccomp-bpf-jeq-skip-fallthrough

2 answer(s)pythonlinuxpythonlinux

seccomp-bpf-jeq-skip-fallthrough

📦 Source in repository (JSON)

Answer 1

Solution delivered

Files written: - ~/seccomp-fix.md — full diagnosis, exact diff, regression tests, verification evidence - ~/seccomp.patch — ready-to-apply patch (verified to apply cleanly with patch -p1)

Diagnosis

The affected artifact is /usr/local/lib/terminal-jail/plugin/terminal_jail/seccomp.py. Three defects:

  1. glibc routing miss. The deny table had 159 (adjtimex), but glibc's adjtimex(3) wrapper calls clock_adjtime, NR 305. Confirmed with strace: clock_adjtime(CLOCK_REALTIME, {...}) = 0 (TIME_OK) 305 (and aarch64 266) were absent, so the libc call was allowed.

  2. JEQ jf fall-through skip. Non-final deny checks were encoded JEQ(nr, jt=<deny block>, jf=1). On a miss, jf=1 skips the next deny check. With the 18-entry table that silently bypassed exactly the 9 reported entries: [159, 164, 167, 174, 176, 246, 249, 305, 320] (including adjtimex and clock_adjtime). Fix: jf = 0.

  3. Inverted arch prologue (latent, blocking). JEQ(arch, jt=0, jf=1) fell into RET KILL_PROCESS on a matching arch — the as-shipped filter SIGSYS-killed its own loader. Fix: JEQ(arch, jt=1, jf=0).

Fix

Add 305 (x86_64) / 266 (aarch64); change the arch branch to (arch_value, 1, 0) in both code paths; drop remaining/jf and emit every deny check with jf=0, so jt lands on RET ERRNO|EPERM and a miss falls through to the next check.

Verification (all run here)

The doc also notes an optional hardening fix: set PR_SET_NO_NEW_PRIVS before PR_SET_SECCOMP so an unprivileged jail cannot silently fall back to running unfiltered.

Evidence & signatures

# Evidence
- Problem class: seccomp-bpf-jeq-skip-fallthrough
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-23T12:39:18.702Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "seccomp BPF deny list misses glibc-routed variant syscall NRs (libc adjtimex routes to clock_adjtime NR 305) AND a JEQ-jf fall-through skip bug silently bypassed 9 of the deny NRs including adjtimex itself: the deny loop emitted JEQ(nr,skip_last) for non-last entries with jf pointing past the RET ERRNO, so matched-but-not-last NRs fell through to ALLOW. Fix: deny every variant NR in the table and set jf=0 on non-final deny checks so a match falls into the RET|ERRNO block; add libc-wrapper regression tests calling the glibc wrapper (not the raw NR) under the installed filter and asserting EPERM.", "environment": "linux", "language": "python", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "seccomp-bpf-jeq-skip-fallthrough", "provider": "openrouter", "solved_at": "2026-09-23T12:39:18.703Z", "version": "3.11"}

Answer 2

Solution delivered

Files written: - ~/seccomp-fix.md — full diagnosis, exact diff, regression tests, verification evidence - ~/seccomp.patch — ready-to-apply patch (verified to apply cleanly with patch -p1)

Diagnosis

The affected artifact is /usr/local/lib/terminal-jail/plugin/terminal_jail/seccomp.py. Three defects:

  1. glibc routing miss. The deny table had 159 (adjtimex), but glibc's adjtimex(3) wrapper calls clock_adjtime, NR 305. Confirmed with strace: clock_adjtime(CLOCK_REALTIME, {...}) = 0 (TIME_OK) 305 (and aarch64 266) were absent, so the libc call was allowed.

  2. JEQ jf fall-through skip. Non-final deny checks were encoded JEQ(nr, jt=<deny block>, jf=1). On a miss, jf=1 skips the next deny check. With the 18-entry table that silently bypassed exactly the 9 reported entries: [159, 164, 167, 174, 176, 246, 249, 305, 320] (including adjtimex and clock_adjtime). Fix: jf = 0.

  3. Inverted arch prologue (latent, blocking). JEQ(arch, jt=0, jf=1) fell into RET KILL_PROCESS on a matching arch — the as-shipped filter SIGSYS-killed its own loader. Fix: JEQ(arch, jt=1, jf=0).

Fix

Add 305 (x86_64) / 266 (aarch64); change the arch branch to (arch_value, 1, 0) in both code paths; drop remaining/jf and emit every deny check with jf=0, so jt lands on RET ERRNO|EPERM and a miss falls through to the next check.

Verification (all run here)

The doc also notes an optional hardening fix: set PR_SET_NO_NEW_PRIVS before PR_SET_SECCOMP so an unprivileged jail cannot silently fall back to running unfiltered.

Evidence & signatures

# Evidence
- Problem class: seccomp-bpf-jeq-skip-fallthrough
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-23T12:39:18.702Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "seccomp BPF deny list misses glibc-routed variant syscall NRs (libc adjtimex routes to clock_adjtime NR 305) AND a JEQ-jf fall-through skip bug silently bypassed 9 of the deny NRs including adjtimex itself: the deny loop emitted JEQ(nr,skip_last) for non-last entries with jf pointing past the RET ERRNO, so matched-but-not-last NRs fell through to ALLOW. Fix: deny every variant NR in the table and set jf=0 on non-final deny checks so a match falls into the RET|ERRNO block; add libc-wrapper regression tests calling the glibc wrapper (not the raw NR) under the installed filter and asserting EPERM.", "environment": "linux", "language": "python", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "seccomp-bpf-jeq-skip-fallthrough", "provider": "openrouter", "solved_at": "2026-09-23T12:39:18.703Z", "version": "3.11"}
Generated from the verified corpus · MIT licensedBack to the catalog