mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-18 01:51:20 -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);
|
||||
|
||||
expect(result.program).toBe('bwrap');
|
||||
expect(result.args).toEqual([
|
||||
expect(result.program).toBe('sh');
|
||||
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',
|
||||
'--new-session',
|
||||
'--die-with-parent',
|
||||
@@ -39,6 +47,8 @@ describe('LinuxSandboxManager', () => {
|
||||
'--bind',
|
||||
workspace,
|
||||
workspace,
|
||||
'--seccomp',
|
||||
'9',
|
||||
'--',
|
||||
'ls',
|
||||
'-la',
|
||||
@@ -59,8 +69,16 @@ describe('LinuxSandboxManager', () => {
|
||||
|
||||
const result = await manager.prepareCommand(req);
|
||||
|
||||
expect(result.program).toBe('bwrap');
|
||||
expect(result.args).toEqual([
|
||||
expect(result.program).toBe('sh');
|
||||
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',
|
||||
'--new-session',
|
||||
'--die-with-parent',
|
||||
@@ -82,6 +100,8 @@ describe('LinuxSandboxManager', () => {
|
||||
'--bind',
|
||||
'/opt/tools',
|
||||
'/opt/tools',
|
||||
'--seccomp',
|
||||
'9',
|
||||
'--',
|
||||
'node',
|
||||
'script.js',
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { join } from 'node:path';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import {
|
||||
type SandboxManager,
|
||||
type SandboxRequest,
|
||||
@@ -15,6 +18,64 @@ import {
|
||||
type EnvironmentSanitizationConfig,
|
||||
} 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.
|
||||
*/
|
||||
@@ -67,11 +128,22 @@ export class LinuxSandboxManager implements SandboxManager {
|
||||
}
|
||||
}
|
||||
|
||||
const bpfPath = getSeccompBpfPath();
|
||||
|
||||
bwrapArgs.push('--seccomp', '9');
|
||||
bwrapArgs.push('--', req.command, ...req.args);
|
||||
|
||||
const shArgs = [
|
||||
'-c',
|
||||
'bpf_path="$1"; shift; exec bwrap "$@" 9< "$bpf_path"',
|
||||
'_',
|
||||
bpfPath,
|
||||
...bwrapArgs,
|
||||
];
|
||||
|
||||
return {
|
||||
program: 'bwrap',
|
||||
args: bwrapArgs,
|
||||
program: 'sh',
|
||||
args: shArgs,
|
||||
env: sanitizedEnv,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -163,11 +163,7 @@ export class FolderTrustDiscoveryService {
|
||||
for (const event of Object.values(hooksConfig)) {
|
||||
if (!Array.isArray(event)) continue;
|
||||
for (const hook of event) {
|
||||
if (
|
||||
this.isRecord(hook) &&
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
typeof hook['command'] === 'string'
|
||||
) {
|
||||
if (this.isRecord(hook) && typeof hook['command'] === 'string') {
|
||||
hooks.add(hook['command']);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user