Add test for bpf disassembler

Also always handle the new return values.

PiperOrigin-RevId: 514698931
Change-Id: Ib4ce06e4f17c438271a0452053d3b0bc368e9970
This commit is contained in:
Wiktor Garbacz 2023-03-07 05:03:22 -08:00 committed by Copybara-Service
parent e46a526865
commit e3b2d232b4
4 changed files with 199 additions and 16 deletions

View File

@ -1020,3 +1020,14 @@ cc_test(
"@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest_main",
], ],
) )
cc_test(
name = "bpfdisassembler_test",
srcs = ["bpfdisassembler_test.cc"],
copts = sapi_platform_copts(),
deps = [
":bpfdisassembler",
"//sandboxed_api/sandbox2/util:bpf_helper",
"@com_google_googletest//:gtest_main",
],
)

View File

@ -32,9 +32,9 @@ add_library(sandbox2_bpfdisassembler ${SAPI_LIB_TYPE}
bpfdisassembler.h bpfdisassembler.h
) )
add_library(sandbox2::bpfdisassembler ALIAS sandbox2_bpfdisassembler) add_library(sandbox2::bpfdisassembler ALIAS sandbox2_bpfdisassembler)
target_link_libraries(sandbox2_bpfdisassembler PRIVATE target_link_libraries(sandbox2_bpfdisassembler
absl::span PUBLIC absl::span
absl::strings PRIVATE absl::strings
sapi::base sapi::base
) )
@ -1121,6 +1121,23 @@ if(BUILD_TESTING AND SAPI_BUILD_TESTING)
ENVIRONMENT "TEST_TMPDIR=/tmp" ENVIRONMENT "TEST_TMPDIR=/tmp"
ENVIRONMENT "TEST_SRCDIR=${PROJECT_BINARY_DIR}" ENVIRONMENT "TEST_SRCDIR=${PROJECT_BINARY_DIR}"
) )
# sandboxed_api/sandbox2:bpfdisassembler_test
add_executable(sandbox2_bpfdisassembler_test
bpfdisassembler_test.cc
)
set_target_properties(sandbox2_bpfdisassembler_test PROPERTIES
OUTPUT_NAME bpfdisassembler_test
)
target_link_libraries(sandbox2_bpfdisassembler_test
PRIVATE sandbox2::bpfdisassembler
sandbox2::bpf_helper
sapi::test_main
)
gtest_discover_tests_xcompile(sandbox2_bpfdisassembler_test PROPERTIES
ENVIRONMENT "TEST_TMPDIR=/tmp"
ENVIRONMENT "TEST_SRCDIR=${PROJECT_BINARY_DIR}"
)
endif() endif()
configure_file( configure_file(

View File

@ -27,6 +27,22 @@
(what) >= offsetof(seccomp_data, field)) && \ (what) >= offsetof(seccomp_data, field)) && \
((what) < (offsetof(seccomp_data, field) + sizeof(seccomp_data::field)))) ((what) < (offsetof(seccomp_data, field) + sizeof(seccomp_data::field))))
#ifndef SECCOMP_RET_USER_NOTIF
#define SECCOMP_RET_USER_NOTIF 0x7fc00000U /* notifies userspace */
#endif
#ifndef SECCOMP_RET_LOG
#define SECCOMP_RET_LOG 0x7ffc0000U /* allow after logging */
#endif
#ifndef SECCOMP_RET_KILL_PROCESS
#define SECCOMP_RET_KILL_PROCESS 0x80000000U /* kill the process */
#endif
#ifndef SECCOMP_RET_ACTION_FULL
#define SECCOMP_RET_ACTION_FULL 0xffff0000U
#endif
namespace sandbox2 { namespace sandbox2 {
namespace bpf { namespace bpf {
namespace { namespace {
@ -94,8 +110,8 @@ std::string DecodeInstruction(const sock_filter& inst, int pc) {
switch (inst.code) { switch (inst.code) {
case BPF_LD | BPF_W | BPF_ABS: case BPF_LD | BPF_W | BPF_ABS:
if (inst.k & 3) { if (inst.k & 3) {
return absl::StrCat("A := *0x", absl::Hex(inst.k), return absl::StrCat("A := data[0x", absl::Hex(inst.k),
" (misaligned read)"); "] (misaligned load)");
} }
if (INSIDE_FIELD(inst.k, nr)) { if (INSIDE_FIELD(inst.k, nr)) {
return "A := syscall number"; return "A := syscall number";
@ -142,23 +158,13 @@ std::string DecodeInstruction(const sock_filter& inst, int pc) {
return absl::StrCat("M[", inst.k, "] := X"); return absl::StrCat("M[", inst.k, "] := X");
case BPF_RET | BPF_K: { case BPF_RET | BPF_K: {
__u32 data = inst.k & SECCOMP_RET_DATA; __u32 data = inst.k & SECCOMP_RET_DATA;
#ifdef SECCOMP_RET_ACTION_FULL
switch (inst.k & SECCOMP_RET_ACTION_FULL) { switch (inst.k & SECCOMP_RET_ACTION_FULL) {
#ifdef SECCOMP_RET_KILL_PROCESS
case SECCOMP_RET_KILL_PROCESS: case SECCOMP_RET_KILL_PROCESS:
return "KILL_PROCESS"; return "KILL_PROCESS";
#endif
#else
switch (inst.k & SECCOMP_RET_ACTION) {
#endif
#ifdef SECCOMP_RET_LOG
case SECCOMP_RET_LOG: case SECCOMP_RET_LOG:
return "LOG"; return "LOG";
#endif
#ifdef SECCOMP_RET_USER_NOTIF
case SECCOMP_RET_USER_NOTIF: case SECCOMP_RET_USER_NOTIF:
return "USER_NOTIF"; return "USER_NOTIF";
#endif
case SECCOMP_RET_KILL: case SECCOMP_RET_KILL:
return "KILL"; return "KILL";
case SECCOMP_RET_ALLOW: case SECCOMP_RET_ALLOW:

View File

@ -0,0 +1,149 @@
#include "sandboxed_api/sandbox2/bpfdisassembler.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "sandboxed_api/sandbox2/util/bpf_helper.h"
namespace sandbox2 {
namespace bpf {
namespace {
using ::testing::Eq;
using ::testing::StartsWith;
#ifndef SECCOMP_RET_USER_NOTIF
#define SECCOMP_RET_USER_NOTIF 0x7fc00000U /* notifies userspace */
#endif
#ifndef SECCOMP_RET_KILL_PROCESS
#define SECCOMP_RET_KILL_PROCESS 0x80000000U /* kill the process */
#endif
#ifndef SECCOMP_RET_LOG
#define SECCOMP_RET_LOG 0x7ffc0000U /* allow after logging */
#endif
TEST(DecodeInstructionTest, Loads) {
EXPECT_THAT(DecodeInstruction(LOAD_ARCH, 1), Eq("A := architecture"));
EXPECT_THAT(DecodeInstruction(LOAD_SYSCALL_NR, 1), Eq("A := syscall number"));
EXPECT_THAT(DecodeInstruction(ARG_32(0), 1), Eq("A := arg 0 low"));
EXPECT_THAT(
DecodeInstruction(BPF_STMT(BPF_LD | BPF_W | BPF_ABS, HI_ARG(0)), 1),
Eq("A := arg 0 high"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_LD | BPF_W | BPF_LEN, 0), 1),
Eq("A := sizeof(seccomp_data)"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_LDX | BPF_W | BPF_LEN, 0), 1),
Eq("X := sizeof(seccomp_data)"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_LD | BPF_IMM, 0x1234), 1),
Eq("A := 0x1234"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_LDX | BPF_IMM, 0x1234), 1),
Eq("X := 0x1234"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_MISC | BPF_TAX, 0), 1),
Eq("X := A"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_MISC | BPF_TXA, 0), 1),
Eq("A := X"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 0x1), 1),
Eq("A := data[0x1] (misaligned load)"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 0x1234), 1),
Eq("A := data[0x1234] (invalid load)"));
}
TEST(DecodeInstructionTest, Memory) {
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_ST, 1), 1), Eq("M[1] := A"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_STX, 1), 1), Eq("M[1] := X"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_LD | BPF_MEM, 1), 1),
Eq("A := M[1]"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_LDX | BPF_MEM, 1), 1),
Eq("X := M[1]"));
}
TEST(DecodeInstructionTest, Returns) {
EXPECT_THAT(DecodeInstruction(KILL, 1), Eq("KILL"));
EXPECT_THAT(DecodeInstruction(ALLOW, 1), Eq("ALLOW"));
EXPECT_THAT(DecodeInstruction(TRAP(0x12), 1), Eq("TRAP 0x12"));
EXPECT_THAT(DecodeInstruction(ERRNO(0x23), 1), Eq("ERRNO 0x23"));
EXPECT_THAT(DecodeInstruction(TRACE(0x34), 1), Eq("TRACE 0x34"));
EXPECT_THAT(
DecodeInstruction(BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_USER_NOTIF), 1),
Eq("USER_NOTIF"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_LOG), 1),
Eq("LOG"));
EXPECT_THAT(
DecodeInstruction(BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL_PROCESS), 1),
Eq("KILL_PROCESS"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_RET + BPF_A, 0), 1),
Eq("return A"));
}
TEST(DecodeInstructionTest, Alu) {
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_ALU | BPF_NEG, 0), 1),
Eq("A := -A"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_ALU | BPF_ADD | BPF_K, 5), 1),
Eq("A := A + 0x5"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_ALU | BPF_SUB | BPF_K, 5), 1),
Eq("A := A - 0x5"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_ALU | BPF_DIV | BPF_X, 0), 1),
Eq("A := A / X"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_ALU | BPF_MUL | BPF_X, 0), 1),
Eq("A := A * X"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_ALU | BPF_AND | BPF_K, 6), 1),
Eq("A := A & 0x6"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_ALU | BPF_OR | BPF_K, 7), 1),
Eq("A := A | 0x7"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_ALU | BPF_XOR | BPF_K, 8), 1),
Eq("A := A ^ 0x8"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_ALU | BPF_RSH | BPF_K, 9), 1),
Eq("A := A >> 0x9"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_ALU | BPF_LSH | BPF_K, 1), 1),
Eq("A := A << 0x1"));
}
TEST(DecodeInstructionTest, Jump) {
EXPECT_THAT(
DecodeInstruction(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x1234, 1, 0), 1),
Eq("if A == 0x1234 goto 3"));
EXPECT_THAT(
DecodeInstruction(BPF_JUMP(BPF_JMP | BPF_JGT | BPF_K, 0x1234, 0, 1), 1),
Eq("if A <= 0x1234 goto 3"));
EXPECT_THAT(
DecodeInstruction(BPF_JUMP(BPF_JMP | BPF_JGT | BPF_K, 0x1234, 1, 2), 1),
Eq("if A > 0x1234 then 3 else 4"));
EXPECT_THAT(
DecodeInstruction(BPF_JUMP(BPF_JMP | BPF_JSET | BPF_X, 1, 1, 0), 1),
Eq("if A & X goto 3"));
EXPECT_THAT(
DecodeInstruction(BPF_JUMP(BPF_JMP | BPF_JGE | BPF_X, 0, 0, 1), 1),
Eq("if A < X goto 3"));
EXPECT_THAT(
DecodeInstruction(BPF_JUMP(BPF_JMP | BPF_JGE | BPF_X, 0, 1, 2), 1),
Eq("if A >= X then 3 else 4"));
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_JMP | BPF_K, 3), 1),
Eq("jump to 5"));
}
TEST(DecodeInstructionTest, Invalid) {
EXPECT_THAT(DecodeInstruction(BPF_STMT(BPF_LDX | BPF_W | BPF_ABS, 0), 1),
StartsWith("Invalid instruction"));
}
TEST(DisasmTest, Simple) {
EXPECT_THAT(Disasm({ALLOW}), Eq("000: ALLOW\n"));
EXPECT_THAT(Disasm({KILL}), Eq("000: KILL\n"));
}
TEST(DisasmTest, Complex) {
EXPECT_THAT(Disasm({LOAD_ARCH, JNE32(0x1, KILL), LOAD_SYSCALL_NR,
JEQ32(0x1234, ERRNO(0x33)), TRACE(0x22)}),
Eq(R"(000: A := architecture
001: if A == 0x1 goto 3
002: KILL
003: A := syscall number
004: if A != 0x1234 goto 6
005: ERRNO 0x33
006: TRACE 0x22
)"));
}
} // namespace
} // namespace bpf
} // namespace sandbox2