diff --git a/src/action_runner.zig b/src/action_runner.zig index 0ecbb14..3b912cb 100644 --- a/src/action_runner.zig +++ b/src/action_runner.zig @@ -820,24 +820,20 @@ fn actionAuditArchitecture() u32 { }; } -fn actionSeccompFilterInstructions() [23]SeccompFilterInstruction { +fn actionSeccompFilterInstructions() [19]SeccompFilterInstruction { const linux = std.os.linux; return .{ .{ .code = classic_bpf_load_absolute_word, .jump_true = 0, .jump_false = 0, .data = 4 }, - .{ .code = classic_bpf_jump_equal_constant, .jump_true = 0, .jump_false = 19, .data = actionAuditArchitecture() }, + .{ .code = classic_bpf_jump_equal_constant, .jump_true = 0, .jump_false = 15, .data = actionAuditArchitecture() }, .{ .code = classic_bpf_load_absolute_word, .jump_true = 0, .jump_false = 0, .data = 0 }, .{ .code = classic_bpf_bitwise_and_constant, .jump_true = 0, .jump_false = 0, .data = ~x86_x32_syscall_bit }, - .{ .code = classic_bpf_jump_equal_constant, .jump_true = 6, .jump_false = 0, .data = @intFromEnum(linux.SYS.socket) }, - .{ .code = classic_bpf_jump_equal_constant, .jump_true = 14, .jump_false = 0, .data = @intFromEnum(linux.SYS.io_uring_setup) }, - .{ .code = classic_bpf_jump_equal_constant, .jump_true = 13, .jump_false = 0, .data = @intFromEnum(linux.SYS.seccomp) }, - .{ .code = classic_bpf_jump_equal_constant, .jump_true = 12, .jump_false = 0, .data = @intFromEnum(linux.SYS.bpf) }, - .{ .code = classic_bpf_jump_equal_constant, .jump_true = 4, .jump_false = 0, .data = @intFromEnum(linux.SYS.prctl) }, - .{ .code = classic_bpf_jump_equal_constant, .jump_true = 5, .jump_false = 0, .data = @intFromEnum(linux.SYS.setsockopt) }, + .{ .code = classic_bpf_jump_equal_constant, .jump_true = 4, .jump_false = 0, .data = @intFromEnum(linux.SYS.socket) }, + .{ .code = classic_bpf_jump_equal_constant, .jump_true = 10, .jump_false = 0, .data = @intFromEnum(linux.SYS.io_uring_setup) }, + .{ .code = classic_bpf_jump_equal_constant, .jump_true = 9, .jump_false = 0, .data = @intFromEnum(linux.SYS.bpf) }, + .{ .code = classic_bpf_jump_equal_constant, .jump_true = 3, .jump_false = 0, .data = @intFromEnum(linux.SYS.setsockopt) }, .{ .code = classic_bpf_return_constant, .jump_true = 0, .jump_false = 0, .data = linux.SECCOMP.RET.ALLOW }, .{ .code = classic_bpf_load_absolute_word, .jump_true = 0, .jump_false = 0, .data = 16 }, - .{ .code = classic_bpf_jump_equal_constant, .jump_true = 7, .jump_false = 9, .data = linux.AF.VSOCK }, - .{ .code = classic_bpf_load_absolute_word, .jump_true = 0, .jump_false = 0, .data = 16 }, - .{ .code = classic_bpf_jump_equal_constant, .jump_true = 5, .jump_false = 7, .data = @intCast(@intFromEnum(linux.PR.SET_SECCOMP)) }, + .{ .code = classic_bpf_jump_equal_constant, .jump_true = 5, .jump_false = 7, .data = linux.AF.VSOCK }, .{ .code = classic_bpf_load_absolute_word, .jump_true = 0, .jump_false = 0, .data = 24 }, .{ .code = classic_bpf_jump_equal_constant, .jump_true = 0, .jump_false = 5, .data = linux.SOL.SOCKET }, .{ .code = classic_bpf_load_absolute_word, .jump_true = 0, .jump_false = 0, .data = 32 }, @@ -1554,25 +1550,15 @@ test "sandbox seccomp rejects AF_VSOCK and io_uring_setup" { )); } -test "sandbox seccomp rejects untrusted classic BPF JIT entry points" { +test "sandbox seccomp rejects untrusted BPF and socket filter entry points" { const linux = std.os.linux; const denied = @as(u32, linux.SECCOMP.RET.ERRNO) | @as(u32, @intFromEnum(std.posix.E.PERM)); - try std.testing.expectEqual(denied, try evaluateActionSeccompFilterForTest( - actionAuditArchitecture(), - @intFromEnum(linux.SYS.seccomp), - .{ 0, 0, 0, 0, 0, 0 }, - )); try std.testing.expectEqual(denied, try evaluateActionSeccompFilterForTest( actionAuditArchitecture(), @intFromEnum(linux.SYS.bpf), .{ 0, 0, 0, 0, 0, 0 }, )); - try std.testing.expectEqual(denied, try evaluateActionSeccompFilterForTest( - actionAuditArchitecture(), - @intFromEnum(linux.SYS.prctl), - .{ @intCast(@intFromEnum(linux.PR.SET_SECCOMP)), 0, 0, 0, 0, 0 }, - )); for ([_]u32{ linux.SO.ATTACH_FILTER, linux.SO.ATTACH_REUSEPORT_CBPF }) |option| { try std.testing.expectEqual(denied, try evaluateActionSeccompFilterForTest( actionAuditArchitecture(), @@ -1582,6 +1568,22 @@ test "sandbox seccomp rejects untrusted classic BPF JIT entry points" { } } +test "sandbox seccomp allows additional restrictive filters" { + const linux = std.os.linux; + const allowed: u32 = linux.SECCOMP.RET.ALLOW; + + try std.testing.expectEqual(allowed, try evaluateActionSeccompFilterForTest( + actionAuditArchitecture(), + @intFromEnum(linux.SYS.seccomp), + .{ linux.SECCOMP.SET_MODE_FILTER, 0, 0, 0, 0, 0 }, + )); + try std.testing.expectEqual(allowed, try evaluateActionSeccompFilterForTest( + actionAuditArchitecture(), + @intFromEnum(linux.SYS.prctl), + .{ @intCast(@intFromEnum(linux.PR.SET_SECCOMP)), linux.SECCOMP.MODE.FILTER, 0, 0, 0, 0 }, + )); +} + test "sandbox seccomp allows unrelated socket options and prctl operations" { const linux = std.os.linux; const allowed: u32 = linux.SECCOMP.RET.ALLOW; diff --git a/tools/e2e_action_tool.zig b/tools/e2e_action_tool.zig index 0d67b86..9a2797c 100644 --- a/tools/e2e_action_tool.zig +++ b/tools/e2e_action_tool.zig @@ -259,37 +259,12 @@ fn expectBpfProgramsBlocked() !void { return error.SeccompCheckFailed; } - const blocked_calls = [_]struct { name: []const u8, result: usize }{ - .{ - .name = "seccomp", - .result = linux.seccomp(linux.SECCOMP.SET_MODE_FILTER, 0, null), - }, - .{ - .name = "prctl(PR_SET_SECCOMP)", - .result = linux.prctl( - @intFromEnum(linux.PR.SET_SECCOMP), - linux.SECCOMP.MODE.FILTER, - 0, - 0, - 0, - ), - }, - .{ - .name = "bpf", - .result = linux.syscall3(.bpf, 0, 0, 0), + switch (linux.errno(linux.syscall3(.bpf, 0, 0, 0))) { + .PERM, .ACCES => {}, + else => |err| { + std.debug.print("sandboxed action unexpectedly reached bpf: {s}\n", .{@tagName(err)}); + return error.SeccompCheckFailed; }, - }; - for (blocked_calls) |blocked_call| { - switch (linux.errno(blocked_call.result)) { - .PERM, .ACCES => {}, - else => |err| { - std.debug.print( - "sandboxed action unexpectedly reached {s}: {s}\n", - .{ blocked_call.name, @tagName(err) }, - ); - return error.SeccompCheckFailed; - }, - } } const socket_rc = linux.socket(linux.AF.INET, linux.SOCK.STREAM | linux.SOCK.CLOEXEC, 0); @@ -333,6 +308,27 @@ fn expectBpfProgramsBlocked() !void { std.debug.print("sandbox seccomp filter rejected SO_REUSEADDR\n", .{}); return error.SeccompCheckFailed; } + + const Instruction = extern struct { code: u16, jump_true: u8, jump_false: u8, data: u32 }; + const Program = extern struct { length: u16, instructions: [*]const Instruction }; + const instructions = [_]Instruction{.{ + .code = 0x06, + .jump_true = 0, + .jump_false = 0, + .data = linux.SECCOMP.RET.ALLOW, + }}; + const program = Program{ .length = instructions.len, .instructions = &instructions }; + const nested = linux.prctl( + @intFromEnum(linux.PR.SET_SECCOMP), + linux.SECCOMP.MODE.FILTER, + @intFromPtr(&program), + 0, + 0, + ); + if (linux.errno(nested) != .SUCCESS) { + std.debug.print("sandbox rejected an additional seccomp filter: {s}\n", .{@tagName(linux.errno(nested))}); + return error.SeccompCheckFailed; + } } fn expectCgroupLimits(io: std.Io) !void { diff --git a/vm/linux.config b/vm/linux.config index 69abcd9..cd44f7d 100644 --- a/vm/linux.config +++ b/vm/linux.config @@ -7,7 +7,7 @@ CONFIG_NR_CPUS=64 CONFIG_BINFMT_ELF=y CONFIG_BINFMT_SCRIPT=y CONFIG_BPF=y -CONFIG_BPF_JIT=y +# CONFIG_BPF_JIT is not set CONFIG_BLOCK=y CONFIG_BLK_DEV=y CONFIG_BLK_DEV_SD=y