From 1af9200f6950156818e5f8987e9d19e5e23df449 Mon Sep 17 00:00:00 2001 From: Landon Cox Date: Tue, 28 Apr 2026 12:05:38 -0700 Subject: [PATCH 1/2] fix: block Shocker container-escape syscalls in seccomp profile (CVE-2014-9357) Remove name_to_handle_at and open_by_handle_at from the SCMP_ACT_ALLOW list and add them to the SCMP_ACT_ERRNO deny list. These syscalls enable the Shocker container-escape attack chain. Docker's default seccomp profile blocks both; our custom profile previously relied solely on the capability bounding set (missing CAP_DAC_READ_SEARCH) as the single defense layer. This provides defense-in-depth: seccomp blocks the syscalls at the kernel level, regardless of capability configuration. Closes #2265 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- containers/agent/seccomp-profile.json | 6 +++--- src/seccomp-profile.test.ts | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/containers/agent/seccomp-profile.json b/containers/agent/seccomp-profile.json index 6f73f1a9..7c95b1a0 100644 --- a/containers/agent/seccomp-profile.json +++ b/containers/agent/seccomp-profile.json @@ -189,11 +189,9 @@ "munlock", "munlockall", "munmap", - "name_to_handle_at", "nanosleep", "newfstatat", "open", - "open_by_handle_at", "openat", "openat2", "pause", @@ -395,7 +393,9 @@ "get_kernel_syms", "query_module", "create_module", - "nfsservctl" + "nfsservctl", + "name_to_handle_at", + "open_by_handle_at" ], "action": "SCMP_ACT_ERRNO", "errnoRet": 1, diff --git a/src/seccomp-profile.test.ts b/src/seccomp-profile.test.ts index 7f71f9d7..5dddcf9c 100644 --- a/src/seccomp-profile.test.ts +++ b/src/seccomp-profile.test.ts @@ -79,6 +79,7 @@ describe('seccomp-profile', () => { 'pivot_root', 'umount', 'umount2', 'swapon', 'swapoff', 'syslog', 'add_key', 'request_key', 'keyctl', + 'name_to_handle_at', 'open_by_handle_at', ]; for (const syscall of dangerousSyscalls) { @@ -121,6 +122,24 @@ describe('seccomp-profile', () => { } }); + test('should block Shocker container-escape syscalls (CVE-2014-9357)', () => { + const allowedSyscalls = new Set( + profile.syscalls + .filter(r => r.action === 'SCMP_ACT_ALLOW') + .flatMap(r => r.names) + ); + + const blockedSyscalls = profile.syscalls + .filter(r => r.action === 'SCMP_ACT_ERRNO') + .flatMap(r => r.names); + + // Both syscalls must be explicitly denied, not just absent from allow list + expect(blockedSyscalls).toContain('name_to_handle_at'); + expect(blockedSyscalls).toContain('open_by_handle_at'); + expect(allowedSyscalls.has('name_to_handle_at')).toBe(false); + expect(allowedSyscalls.has('open_by_handle_at')).toBe(false); + }); + test('should have valid JSON structure', () => { expect(profile.defaultAction).toBeDefined(); expect(profile.architectures).toBeDefined(); From 43a67116abd93882944b2e7817641ea86a4715ec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 19:19:16 +0000 Subject: [PATCH 2/2] fix: clarify test comment - deny-by-default makes explicit ERRNO for defense-in-depth --- src/seccomp-profile.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/seccomp-profile.test.ts b/src/seccomp-profile.test.ts index 5dddcf9c..c817e1ce 100644 --- a/src/seccomp-profile.test.ts +++ b/src/seccomp-profile.test.ts @@ -133,7 +133,8 @@ describe('seccomp-profile', () => { .filter(r => r.action === 'SCMP_ACT_ERRNO') .flatMap(r => r.names); - // Both syscalls must be explicitly denied, not just absent from allow list + // Explicitly listed in ERRNO rules for defense-in-depth regression protection + // (deny-by-default means absence alone would block them, but explicit denial is more robust) expect(blockedSyscalls).toContain('name_to_handle_at'); expect(blockedSyscalls).toContain('open_by_handle_at'); expect(allowedSyscalls.has('name_to_handle_at')).toBe(false);