mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 12:54:07 -07:00
Linux sandbox seccomp (#22815)
Co-authored-by: Gal Zahavi <38544478+galz10@users.noreply.github.com>
This commit is contained in:
@@ -22,8 +22,16 @@ describe('LinuxSandboxManager', () => {
|
|||||||
|
|
||||||
const result = await manager.prepareCommand(req);
|
const result = await manager.prepareCommand(req);
|
||||||
|
|
||||||
expect(result.program).toBe('bwrap');
|
expect(result.program).toBe('sh');
|
||||||
expect(result.args).toEqual([
|
expect(result.args[0]).toBe('-c');
|
||||||
|
expect(result.args[1]).toBe(
|
||||||
|
'bpf_path="$1"; shift; exec bwrap "$@" 9< "$bpf_path"',
|
||||||
|
);
|
||||||
|
expect(result.args[2]).toBe('_');
|
||||||
|
expect(result.args[3]).toMatch(/gemini-cli-seccomp-.*\.bpf$/);
|
||||||
|
|
||||||
|
const bwrapArgs = result.args.slice(4);
|
||||||
|
expect(bwrapArgs).toEqual([
|
||||||
'--unshare-all',
|
'--unshare-all',
|
||||||
'--new-session',
|
'--new-session',
|
||||||
'--die-with-parent',
|
'--die-with-parent',
|
||||||
@@ -39,6 +47,8 @@ describe('LinuxSandboxManager', () => {
|
|||||||
'--bind',
|
'--bind',
|
||||||
workspace,
|
workspace,
|
||||||
workspace,
|
workspace,
|
||||||
|
'--seccomp',
|
||||||
|
'9',
|
||||||
'--',
|
'--',
|
||||||
'ls',
|
'ls',
|
||||||
'-la',
|
'-la',
|
||||||
@@ -59,8 +69,16 @@ describe('LinuxSandboxManager', () => {
|
|||||||
|
|
||||||
const result = await manager.prepareCommand(req);
|
const result = await manager.prepareCommand(req);
|
||||||
|
|
||||||
expect(result.program).toBe('bwrap');
|
expect(result.program).toBe('sh');
|
||||||
expect(result.args).toEqual([
|
expect(result.args[0]).toBe('-c');
|
||||||
|
expect(result.args[1]).toBe(
|
||||||
|
'bpf_path="$1"; shift; exec bwrap "$@" 9< "$bpf_path"',
|
||||||
|
);
|
||||||
|
expect(result.args[2]).toBe('_');
|
||||||
|
expect(result.args[3]).toMatch(/gemini-cli-seccomp-.*\.bpf$/);
|
||||||
|
|
||||||
|
const bwrapArgs = result.args.slice(4);
|
||||||
|
expect(bwrapArgs).toEqual([
|
||||||
'--unshare-all',
|
'--unshare-all',
|
||||||
'--new-session',
|
'--new-session',
|
||||||
'--die-with-parent',
|
'--die-with-parent',
|
||||||
@@ -82,6 +100,8 @@ describe('LinuxSandboxManager', () => {
|
|||||||
'--bind',
|
'--bind',
|
||||||
'/opt/tools',
|
'/opt/tools',
|
||||||
'/opt/tools',
|
'/opt/tools',
|
||||||
|
'--seccomp',
|
||||||
|
'9',
|
||||||
'--',
|
'--',
|
||||||
'node',
|
'node',
|
||||||
'script.js',
|
'script.js',
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { writeFileSync } from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
import {
|
import {
|
||||||
type SandboxManager,
|
type SandboxManager,
|
||||||
type SandboxRequest,
|
type SandboxRequest,
|
||||||
@@ -15,6 +18,64 @@ import {
|
|||||||
type EnvironmentSanitizationConfig,
|
type EnvironmentSanitizationConfig,
|
||||||
} from '../../services/environmentSanitization.js';
|
} from '../../services/environmentSanitization.js';
|
||||||
|
|
||||||
|
let cachedBpfPath: string | undefined;
|
||||||
|
|
||||||
|
function getSeccompBpfPath(): string {
|
||||||
|
if (cachedBpfPath) return cachedBpfPath;
|
||||||
|
|
||||||
|
const arch = os.arch();
|
||||||
|
let AUDIT_ARCH: number;
|
||||||
|
let SYS_ptrace: number;
|
||||||
|
|
||||||
|
if (arch === 'x64') {
|
||||||
|
AUDIT_ARCH = 0xc000003e; // AUDIT_ARCH_X86_64
|
||||||
|
SYS_ptrace = 101;
|
||||||
|
} else if (arch === 'arm64') {
|
||||||
|
AUDIT_ARCH = 0xc00000b7; // AUDIT_ARCH_AARCH64
|
||||||
|
SYS_ptrace = 117;
|
||||||
|
} else if (arch === 'arm') {
|
||||||
|
AUDIT_ARCH = 0x40000028; // AUDIT_ARCH_ARM
|
||||||
|
SYS_ptrace = 26;
|
||||||
|
} else if (arch === 'ia32') {
|
||||||
|
AUDIT_ARCH = 0x40000003; // AUDIT_ARCH_I386
|
||||||
|
SYS_ptrace = 26;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unsupported architecture for seccomp filter: ${arch}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const EPERM = 1;
|
||||||
|
const SECCOMP_RET_KILL_PROCESS = 0x80000000;
|
||||||
|
const SECCOMP_RET_ERRNO = 0x00050000;
|
||||||
|
const SECCOMP_RET_ALLOW = 0x7fff0000;
|
||||||
|
|
||||||
|
const instructions = [
|
||||||
|
{ code: 0x20, jt: 0, jf: 0, k: 4 }, // Load arch
|
||||||
|
{ code: 0x15, jt: 1, jf: 0, k: AUDIT_ARCH }, // Jump to kill if arch != native arch
|
||||||
|
{ code: 0x06, jt: 0, jf: 0, k: SECCOMP_RET_KILL_PROCESS }, // Kill
|
||||||
|
|
||||||
|
{ code: 0x20, jt: 0, jf: 0, k: 0 }, // Load nr
|
||||||
|
{ code: 0x15, jt: 0, jf: 1, k: SYS_ptrace }, // If ptrace, jump to ERRNO
|
||||||
|
{ code: 0x06, jt: 0, jf: 0, k: SECCOMP_RET_ERRNO | EPERM }, // ERRNO
|
||||||
|
|
||||||
|
{ code: 0x06, jt: 0, jf: 0, k: SECCOMP_RET_ALLOW }, // Allow
|
||||||
|
];
|
||||||
|
|
||||||
|
const buf = Buffer.alloc(8 * instructions.length);
|
||||||
|
for (let i = 0; i < instructions.length; i++) {
|
||||||
|
const inst = instructions[i];
|
||||||
|
const offset = i * 8;
|
||||||
|
buf.writeUInt16LE(inst.code, offset);
|
||||||
|
buf.writeUInt8(inst.jt, offset + 2);
|
||||||
|
buf.writeUInt8(inst.jf, offset + 3);
|
||||||
|
buf.writeUInt32LE(inst.k, offset + 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bpfPath = join(os.tmpdir(), `gemini-cli-seccomp-${process.pid}.bpf`);
|
||||||
|
writeFileSync(bpfPath, buf);
|
||||||
|
cachedBpfPath = bpfPath;
|
||||||
|
return bpfPath;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for configuring the LinuxSandboxManager.
|
* Options for configuring the LinuxSandboxManager.
|
||||||
*/
|
*/
|
||||||
@@ -67,11 +128,22 @@ export class LinuxSandboxManager implements SandboxManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bpfPath = getSeccompBpfPath();
|
||||||
|
|
||||||
|
bwrapArgs.push('--seccomp', '9');
|
||||||
bwrapArgs.push('--', req.command, ...req.args);
|
bwrapArgs.push('--', req.command, ...req.args);
|
||||||
|
|
||||||
|
const shArgs = [
|
||||||
|
'-c',
|
||||||
|
'bpf_path="$1"; shift; exec bwrap "$@" 9< "$bpf_path"',
|
||||||
|
'_',
|
||||||
|
bpfPath,
|
||||||
|
...bwrapArgs,
|
||||||
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
program: 'bwrap',
|
program: 'sh',
|
||||||
args: bwrapArgs,
|
args: shArgs,
|
||||||
env: sanitizedEnv,
|
env: sanitizedEnv,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,11 +163,7 @@ export class FolderTrustDiscoveryService {
|
|||||||
for (const event of Object.values(hooksConfig)) {
|
for (const event of Object.values(hooksConfig)) {
|
||||||
if (!Array.isArray(event)) continue;
|
if (!Array.isArray(event)) continue;
|
||||||
for (const hook of event) {
|
for (const hook of event) {
|
||||||
if (
|
if (this.isRecord(hook) && typeof hook['command'] === 'string') {
|
||||||
this.isRecord(hook) &&
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
typeof hook['command'] === 'string'
|
|
||||||
) {
|
|
||||||
hooks.add(hook['command']);
|
hooks.add(hook['command']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user