From ca726da30c27846d47b0dca60d14ba940311a1fc Mon Sep 17 00:00:00 2001 From: "Jiaxiao (mossaka) Zhou" Date: Thu, 5 Mar 2026 19:59:18 +0000 Subject: [PATCH] test: add chroot escape vector test coverage Add 6 new integration tests for container escape prevention: - pivot_root syscall blocked by seccomp profile - mount blocked after capability drop (CAP_SYS_ADMIN dropped) - unshare namespace creation blocked without CAP_SYS_ADMIN - nsenter blocked without CAP_SYS_ADMIN - umount/umount2 blocked by seccomp profile - no-new-privileges flag verified via /proc/self/status Tests are batched into a single container invocation for efficiency. Fixes #762 Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/integration/chroot-edge-cases.test.ts | 63 +++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/integration/chroot-edge-cases.test.ts b/tests/integration/chroot-edge-cases.test.ts index 532aef11..a7ec7950 100644 --- a/tests/integration/chroot-edge-cases.test.ts +++ b/tests/integration/chroot-edge-cases.test.ts @@ -169,6 +169,69 @@ describe('Chroot Edge Cases', () => { }); }); + // ---------- Batch: Container escape vector tests ---------- + describe('Container Escape Prevention (batched)', () => { + let batch: BatchResults; + + beforeAll(async () => { + batch = await runBatch(runner, [ + // pivot_root - blocked in seccomp profile + { name: 'pivot_root', command: 'mkdir -p /tmp/newroot /tmp/putold && pivot_root /tmp/newroot /tmp/putold 2>&1 || unshare --mount pivot_root /tmp/newroot /tmp/putold 2>&1' }, + // mount after capability drop - mount syscall allowed in seccomp but CAP_SYS_ADMIN should be dropped + { name: 'mount_tmpfs', command: 'mount -t tmpfs tmpfs /tmp/test-mount-$$ 2>&1' }, + // unshare namespace creation - requires CAP_SYS_ADMIN + { name: 'unshare_mount', command: 'unshare --mount /bin/true 2>&1' }, + // nsenter - requires CAP_SYS_ADMIN + { name: 'nsenter', command: 'nsenter --mount --target 1 /bin/true 2>&1' }, + // umount - blocked in seccomp profile + { name: 'umount', command: 'umount /tmp 2>&1' }, + // setuid escalation - no-new-privileges should prevent + { name: 'no_new_privs', command: 'cat /proc/self/status | grep NoNewPrivs 2>&1' }, + ], { + allowDomains: ['localhost'], + logLevel: 'debug', + timeout: 120000, + }); + }, 180000); + + test('should block pivot_root syscall', () => { + const r = batch.get('pivot_root'); + expect(r.exitCode).not.toBe(0); + expect(r.stdout).toMatch(/operation not permitted|permission denied|invalid argument|no such file/i); + }); + + test('should block mount after capability drop', () => { + const r = batch.get('mount_tmpfs'); + expect(r.exitCode).not.toBe(0); + expect(r.stdout).toMatch(/operation not permitted|permission denied/i); + }); + + test('should block unshare namespace creation', () => { + const r = batch.get('unshare_mount'); + expect(r.exitCode).not.toBe(0); + expect(r.stdout).toMatch(/operation not permitted|permission denied|cannot change root/i); + }); + + test('should block nsenter', () => { + const r = batch.get('nsenter'); + expect(r.exitCode).not.toBe(0); + expect(r.stdout).toMatch(/operation not permitted|permission denied|no such file/i); + }); + + test('should block umount/umount2', () => { + const r = batch.get('umount'); + expect(r.exitCode).not.toBe(0); + expect(r.stdout).toMatch(/operation not permitted|permission denied|not permitted/i); + }); + + test('should have no-new-privileges set', () => { + const r = batch.get('no_new_privs'); + expect(r.exitCode).toBe(0); + // NoNewPrivs: 1 means no-new-privileges is enforced + expect(r.stdout).toMatch(/NoNewPrivs:\s*1/); + }); + }); + // ---------- Individual: Working directory tests (different containerWorkDir options) ---------- describe('Working Directory Handling', () => { test('should respect container-workdir in chroot mode', async () => {