Skip to content

fix: block Shocker container-escape syscalls in seccomp profile (CVE-2014-9357)#2276

Merged
lpcox merged 2 commits intomainfrom
fix/seccomp-shocker-syscalls
Apr 28, 2026
Merged

fix: block Shocker container-escape syscalls in seccomp profile (CVE-2014-9357)#2276
lpcox merged 2 commits intomainfrom
fix/seccomp-shocker-syscalls

Conversation

@lpcox
Copy link
Copy Markdown
Collaborator

@lpcox lpcox commented Apr 28, 2026

Summary

Removes name_to_handle_at (NR 303) and open_by_handle_at (NR 304) from the seccomp ALLOW list and adds them to the ERRNO deny list, matching Docker's default seccomp hardening posture.

Problem

The Shocker container-escape attack (CVE-2014-9357) uses name_to_handle_at to obtain a raw inode handle and open_by_handle_at to open it in a privileged context, escaping the container namespace. Docker's default seccomp profile blocks both syscalls for exactly this reason.

Our custom seccomp profile previously allowed both syscalls, relying solely on the capability bounding set (missing CAP_DAC_READ_SEARCH) as a single defense layer. While effective today, any future configuration drift — capability grant, privilege escalation, or kernel bypass — would leave the escape path fully open.

Changes

  • containers/agent/seccomp-profile.json: Moved name_to_handle_at and open_by_handle_at from SCMP_ACT_ALLOW to SCMP_ACT_ERRNO
  • src/seccomp-profile.test.ts: Added dedicated test for Shocker syscalls + added both to the existing dangerous-syscalls assertions

Defense-in-depth

With this change, the Shocker attack chain is blocked at two independent layers:

  1. Seccomp — kernel refuses the syscalls (EPERM)
  2. CapabilitiesCAP_DAC_READ_SEARCH absent from bounding set

Closes #2265

…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>
Copilot AI review requested due to automatic review settings April 28, 2026 19:06
@lpcox lpcox requested a review from Mossaka as a code owner April 28, 2026 19:06
This was referenced Apr 28, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 28, 2026

✅ Coverage Check Passed

Overall Coverage

Metric Base PR Delta
Lines 86.08% 86.16% 📈 +0.08%
Statements 86.08% 86.15% 📈 +0.07%
Functions 88.18% 88.18% ➡️ +0.00%
Branches 80.27% 80.31% 📈 +0.04%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/docker-manager.ts 87.4% → 87.7% (+0.29%) 87.0% → 87.3% (+0.27%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the agent container’s custom seccomp profile against the Shocker container-escape chain (CVE-2014-9357) by ensuring name_to_handle_at and open_by_handle_at are blocked consistent with Docker’s default posture.

Changes:

  • Moved name_to_handle_at and open_by_handle_at from the seccomp allowlist to the SCMP_ACT_ERRNO deny rules (EPERM).
  • Extended the existing “dangerous syscalls” test coverage to include these syscalls.
  • Added a dedicated regression test asserting both syscalls are explicitly denied and not allowed.
Show a summary per file
File Description
containers/agent/seccomp-profile.json Removes the two syscalls from SCMP_ACT_ALLOW and adds them to the explicit SCMP_ACT_ERRNO deny list.
src/seccomp-profile.test.ts Adds assertions/tests to ensure the profile blocks the Shocker-related syscalls.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 1

Comment thread src/seccomp-profile.test.ts Outdated
Comment on lines +136 to +140
// 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);
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment "must be explicitly denied, not just absent from allow list" is misleading here: the profile is already deny-by-default (defaultAction: SCMP_ACT_ERRNO), so simply omitting these syscalls from the allow rules would still block them. Consider rewording to reflect the actual intent (e.g., ensuring they remain explicitly listed in the ERRNO rules for defense-in-depth / regression protection) to avoid implying the allowlist omission would be insufficient.

Copilot uses AI. Check for mistakes.
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@lpcox
Copy link
Copy Markdown
Collaborator Author

lpcox commented Apr 28, 2026

@copilot address the review feedback

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 28, 2026

@copilot address the review feedback

Done in 43a6711. Updated the comment to clarify that the explicit ERRNO listing serves defense-in-depth and regression protection purposes — since the profile is already deny-by-default (SCMP_ACT_ERRNO), absence alone would block these syscalls, but the explicit rule makes the intent unambiguous and guards against accidental future inclusion in the allowlist.

@github-actions
Copy link
Copy Markdown
Contributor

Smoke Test: Copilot BYOK — PASS ✅

Test Result
GitHub MCP (list PRs) ✅ PR #2273 "fix: remove sandbox.mcp.version from strict-mode workflows" returned
GitHub.com connectivity ⚠️ Pre-step vars not interpolated (workflow template issue)
File write/read ⚠️ Pre-step vars not interpolated (workflow template issue)
BYOK inference (api-proxy → api.githubcopilot.com) ✅ Responding now

Running in BYOK offline mode (COPILOT_OFFLINE=true) via api-proxy → api.githubcopilot.com.

PR by @lpcox · reviewer: @Mossaka

🔑 BYOK report filed by Smoke Copilot BYOK

@github-actions
Copy link
Copy Markdown
Contributor

🔥 Smoke Test Results — PASS

Test Result
GitHub MCP connectivity
GitHub.com HTTP ✅ (200)
File write/read (smoke-test-copilot-25072868917.txt)

PR: fix: block Shocker container-escape syscalls in seccomp profile (CVE-2014-9357)
Author: @lpcox · Reviewer: @Mossaka

Overall: PASS

📰 BREAKING: Report filed by Smoke Copilot

@github-actions
Copy link
Copy Markdown
Contributor

Chroot Version Comparison Results

Runtime Host Version Chroot Version Match?
Python Python 3.12.13 Python 3.12.3
Node.js v24.14.1 v20.20.2
Go go1.22.12 go1.22.12

Result: FAILED — Python and Node.js versions differ between host and chroot environments.

Tested by Smoke Chroot

@github-actions
Copy link
Copy Markdown
Contributor

Smoke Test Results ✅ PASS

  • ✅ GitHub CLI: Last 2 merged PRs retrieved (2271, 2268)
  • ✅ Playwright: Page title verified at github.com
  • ✅ File Writing: Test file created successfully
  • ✅ Bash Tool: File verified with content

💥 [THE END] — Illustrated by Smoke Claude

@github-actions
Copy link
Copy Markdown
Contributor

Recent merged PRs:

  • chore: upgrade to firewall v0.25.29 and mcpg v0.3.1
  • [docs] docs: document /reflect endpoint in api-proxy-sidecar

GitHub MCP review: ❌
safeinputs-gh PR query: ❌
Playwright GitHub title: ✅
Tavily search: ❌
File write + cat: ✅
Build (npm ci && npm run build): ✅
Overall: FAIL

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • registry.npmjs.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "registry.npmjs.org"

See Network Configuration for more information.

🔮 The oracle has spoken through Smoke Codex

@github-actions
Copy link
Copy Markdown
Contributor

🏗️ Build Test Suite Results

Ecosystem Project Build/Install Tests Status
Bun elysia 1/1 passed ✅ PASS
Bun hono 1/1 passed ✅ PASS
C++ fmt N/A ✅ PASS
C++ json N/A ✅ PASS
Deno oak N/A 1/1 passed ✅ PASS
Deno std N/A 1/1 passed ✅ PASS
.NET hello-world N/A ✅ PASS
.NET json-parse N/A ✅ PASS
Go color 1/1 passed ✅ PASS
Go env 1/1 passed ✅ PASS
Go uuid 1/1 passed ✅ PASS
Java gson 1/1 passed ✅ PASS
Java caffeine 1/1 passed ✅ PASS
Node.js clsx all passed ✅ PASS
Node.js execa all passed ✅ PASS
Node.js p-limit all passed ✅ PASS
Rust fd 1/1 passed ✅ PASS
Rust zoxide 1/1 passed ✅ PASS

Overall: 8/8 ecosystems passed — ✅ PASS

Generated by Build Test Suite for issue #2276 · ● 923.5K ·

@lpcox lpcox merged commit 78eb7db into main Apr 28, 2026
64 of 68 checks passed
@lpcox lpcox deleted the fix/seccomp-shocker-syscalls branch April 28, 2026 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants