Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 91 additions & 19 deletions src/analyzer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,10 @@ function expectInitialBpfState(s: BpfState) {
}
}

function bpfStatesFromLog(logString: string): BpfState[] {
function getVerifierLogState(logString: string): VerifierLogState {
const strings = logString.split("\n");
strings.shift(); // remove the first \n
const logState: VerifierLogState = processRawLines(strings);
const { bpfStates } = logState;
return bpfStates;
return processRawLines(strings);
}

describe("analyzer", () => {
Expand All @@ -66,7 +64,7 @@ processed 8 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_stat
ERROR: Loading BPF object(s) failed.
`;
describe("processes basicVerifierLog end to end normally", () => {
const bpfStates = bpfStatesFromLog(basicVerifierLog);
const { bpfStates } = getVerifierLogState(basicVerifierLog);
for (let i = 0; i <= 4; i++) {
expectInitialBpfState(bpfStates[i]);
}
Expand All @@ -80,6 +78,7 @@ ERROR: Loading BPF object(s) failed.
value: "1",
effect: Effect.WRITE,
});
expect(s.lastKnownWrites.get("r2")).toBe(5);
});

it("*(u64 *)(r10 -24) = r2", () => {
Expand All @@ -95,6 +94,8 @@ ERROR: Loading BPF object(s) failed.
value: "1",
effect: Effect.WRITE,
});
expect(s.lastKnownWrites.get("r2")).toBe(5);
expect(s.lastKnownWrites.get("fp-24")).toBe(6);
});

it("r3 = *(u64 *)(r1 +32)", () => {
Expand All @@ -110,6 +111,9 @@ ERROR: Loading BPF object(s) failed.
value: "scalar()",
effect: Effect.WRITE,
});
expect(s.lastKnownWrites.get("r2")).toBe(5);
expect(s.lastKnownWrites.get("fp-24")).toBe(6);
expect(s.lastKnownWrites.get("r3")).toBe(7);
});

it("r3 += -16", () => {
Expand All @@ -122,6 +126,9 @@ ERROR: Loading BPF object(s) failed.
effect: Effect.UPDATE,
prevValue: "scalar()",
});
expect(s.lastKnownWrites.get("r2")).toBe(5);
expect(s.lastKnownWrites.get("fp-24")).toBe(6);
expect(s.lastKnownWrites.get("r3")).toBe(8);
});

it("r1 = r10", () => {
Expand All @@ -137,6 +144,10 @@ ERROR: Loading BPF object(s) failed.
value: "fp-0",
effect: Effect.WRITE,
});
expect(s.lastKnownWrites.get("r2")).toBe(5);
expect(s.lastKnownWrites.get("fp-24")).toBe(6);
expect(s.lastKnownWrites.get("r3")).toBe(8);
expect(s.lastKnownWrites.get("r1")).toBe(9);
});

it("r1 += -8", () => {
Expand All @@ -149,6 +160,10 @@ ERROR: Loading BPF object(s) failed.
effect: Effect.UPDATE,
prevValue: "fp-0",
});
expect(s.lastKnownWrites.get("r2")).toBe(5);
expect(s.lastKnownWrites.get("fp-24")).toBe(6);
expect(s.lastKnownWrites.get("r3")).toBe(8);
expect(s.lastKnownWrites.get("r1")).toBe(10);
});

it("r2 = 16", () => {
Expand All @@ -160,6 +175,10 @@ ERROR: Loading BPF object(s) failed.
value: "16",
effect: Effect.WRITE,
});
expect(s.lastKnownWrites.get("r2")).toBe(11);
expect(s.lastKnownWrites.get("fp-24")).toBe(6);
expect(s.lastKnownWrites.get("r3")).toBe(8);
expect(s.lastKnownWrites.get("r1")).toBe(10);
});

it("call bpf_probe_read_user#112", () => {
Expand All @@ -182,6 +201,11 @@ ERROR: Loading BPF object(s) failed.
expect(s.values.get("r3")?.prevValue).toBe("scalar()");
expect(s.values.get("r4")?.prevValue).toBeUndefined();
expect(s.values.get("r5")?.prevValue).toBeUndefined();

expect(s.lastKnownWrites.get("fp-24")).toBe(6);
for (const id of ["r0", "r1", "r2", "r3", "r4", "r5"]) {
expect(s.lastKnownWrites.get(id)).toBe(12);
}
});
});

Expand All @@ -196,7 +220,7 @@ ERROR: Loading BPF object(s) failed.
901: (07) r2 += -24 ; R2_w=fp-24
902: (79) r6 = *(u64 *)(r2 +0) ; R2=fp-24 R6_w=scalar(id=102) fp-24=scalar(id=102)
`;
const bpfStates = bpfStatesFromLog(sampleLog);
const { bpfStates } = getVerifierLogState(sampleLog);
it("*(u64 *)(r1 +0) = r8", () => {
const s = bpfStates[4];
expect(s.frame).toBe(0);
Expand Down Expand Up @@ -240,7 +264,9 @@ to caller at 705:
705: (bf) r9 = r0 ; R0=rcu_ptr_task_struct(off=0,imm=0) R9_w=rcu_ptr_task_struct(off=0,imm=0)
`;
describe("processes verifierLogFragmentWithASubprogramCall end to end normally", () => {
const bpfStates = bpfStatesFromLog(verifierLogFragmentWithASubprogramCall);
const { bpfStates } = getVerifierLogState(
verifierLogFragmentWithASubprogramCall,
);

const r1Value = "map_value(off=0,ks=4,vs=2808,imm=0)";
const r2Value = "rcu_ptr_task_struct(off=0,imm=0)";
Expand All @@ -255,6 +281,7 @@ to caller at 705:
value: r1Value,
effect: Effect.WRITE,
});
expect(s.lastKnownWrites.get("r1")).toBe(0);
});

it("r2 = r6", () => {
Expand All @@ -267,6 +294,8 @@ to caller at 705:
value: r2Value,
effect: Effect.WRITE,
});
expect(s.lastKnownWrites.get("r1")).toBe(0);
expect(s.lastKnownWrites.get("r2")).toBe(1);
});

// compute savedRegValues for the exit test
Expand Down Expand Up @@ -306,6 +335,8 @@ to caller at 705:
effect: Effect.WRITE,
});
}
expect(s.lastKnownWrites.get("r1")).toBe(0);
expect(s.lastKnownWrites.get("r2")).toBe(1);
});

it("; call comments", () => {
Expand All @@ -327,6 +358,9 @@ to caller at 705:
value: r2Value,
effect: Effect.WRITE,
});
expect(s.lastKnownWrites.get("r1")).toBe(0);
expect(s.lastKnownWrites.get("r2")).toBe(1);
expect(s.lastKnownWrites.get("r0")).toBe(10);
});

it("exit", () => {
Expand All @@ -343,6 +377,7 @@ to caller at 705:
for (const reg of BPF_CALLEE_SAVED_REGS) {
expect(s.values.get(reg)).toEqual(savedRegValues.get(reg));
}
expect(s.lastKnownWrites.get("r0")).toBe(10);
});

it("; exit comments", () => {
Expand All @@ -364,6 +399,8 @@ to caller at 705:
value: r2Value,
effect: Effect.WRITE,
});
expect(s.lastKnownWrites.get("r0")).toBe(10);
expect(s.lastKnownWrites.get("r9")).toBe(16);
});
});

Expand All @@ -380,10 +417,9 @@ to caller at 705:
`;

describe("for verifierLogFragmentWithSimpleCSource", () => {
const strings = verifierLogFragmentWithSimpleCSource.split("\n");
strings.shift(); // remove the first \n
const logState: VerifierLogState = processRawLines(strings);
const { cSourceMap } = logState;
const { cSourceMap } = getVerifierLogState(
verifierLogFragmentWithSimpleCSource,
);

it("creates correct C source line entries", () => {
expect(cSourceMap.cSourceLines.size).toBe(3);
Expand Down Expand Up @@ -449,10 +485,9 @@ to caller at 705:
`;

describe("processes verifierLogFragmentWithAPieceOfLoop correctly", () => {
const strings = verifierLogFragmentWithAPieceOfLoop.split("\n");
strings.shift(); // remove the first \n
const logState: VerifierLogState = processRawLines(strings);
const { cSourceMap } = logState;
const { cSourceMap } = getVerifierLogState(
verifierLogFragmentWithAPieceOfLoop,
);

it("creates correct C source line entries", () => {
expect(cSourceMap.cSourceLines.size).toBe(1);
Expand Down Expand Up @@ -494,10 +529,9 @@ Func#123 ('my_global_func') is global and assumed valid.
`;

describe("global func call", () => {
const strings = verifierLogWithGlobalFuncCall.split("\n");
strings.shift(); // remove the first \n
const logState: VerifierLogState = processRawLines(strings);
const { lines, bpfStates } = logState;
const { lines, bpfStates } = getVerifierLogState(
verifierLogWithGlobalFuncCall,
);

it("transforms global function call correctly", () => {
// Check that line 1 is transformed from SUBPROGRAM_CALL to HELPER_CALL
Expand Down Expand Up @@ -529,4 +563,42 @@ Func#123 ('my_global_func') is global and assumed valid.
});
});
});

describe("takes into accout BPF_STATE_EXPRS messages", () => {
const rawLog = `
96: (18) r1 = 0xffff888370cf0a00 ; frame1: R1_w=map_ptr(map=bpfj_log_map,ks=0,vs=0)
98: (b7) r2 = 196 ; frame1: R2_w=196
99: (b7) r3 = 0 ; frame1: R3_w=0
100: (85) call bpf_ringbuf_reserve#131
101: frame1: R0=ringbuf_mem_or_null(id=5,ref_obj_id=5,sz=196) refs=5
101: (bf) r7 = r0 ; frame1: R0=ringbuf_mem_or_null(id=5,ref_obj_id=5,sz=196) R7_w=ringbuf_mem_or_null(id=5,ref_obj_id=5,sz=196) refs=5
`;
const { bpfStates } = getVerifierLogState(rawLog);
const val = "ringbuf_mem_or_null(id=5,ref_obj_id=5,sz=196)";
it("call bpf_ringbuf_reserve#131 contains state from the next log line", () => {
const s = bpfStates[3];
expect(s.idx).toBe(3);
expect(s.pc).toBe(100);
expect(s.values.get("r0")).toMatchObject({
value: val,
effect: Effect.WRITE,
});
});

it("101: (bf) r7 = r0 depends on the call", () => {
const s = bpfStates[5];
expect(s.idx).toBe(5);
expect(s.pc).toBe(101);
expect(s.values.get("r0")).toMatchObject({
value: val,
effect: Effect.READ,
});
expect(s.values.get("r7")).toMatchObject({
value: val,
effect: Effect.WRITE,
});
expect(s.lastKnownWrites.get("r0")).toBe(3);
expect(s.lastKnownWrites.get("r7")).toBe(5);
});
});
});
29 changes: 23 additions & 6 deletions src/analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import {
getCLineId,
KnownMessageInfoType,
GlobalFuncValidInfo,
InstructionLine,
BpfStateExprsInfo,
} from "./parser";
import { siblingInsLine } from "./utils";

export type BpfValue = {
value: string;
Expand Down Expand Up @@ -293,6 +296,20 @@ function updateGlobalFuncCall(callLine: ParsedLine, info: GlobalFuncValidInfo) {
ins.writes = ["r0", ...BPF_SCRATCH_REGS];
}

function updatePrevInsBpfState(
lines: ParsedLine[],
info: BpfStateExprsInfo,
idx: number,
) {
// the heuristic for BPF_STATE_EXPRS messages is to append
// the exprs to the state of the _previous_ instruction
const prevIdx = siblingInsLine(lines, idx, -1);
if (prevIdx < idx) {
Copy link
Collaborator

@jordalgo jordalgo Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this check necessary?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it'll only trigger for idx=0.

const prevLine = <InstructionLine>lines[prevIdx];
prevLine.bpfStateExprs.push(...info.bpfStateExprs);
}
}

export function processRawLines(rawLines: string[]): VerifierLogState {
let bpfStates: BpfState[] = [];
let lines: ParsedLine[] = [];
Expand Down Expand Up @@ -322,12 +339,12 @@ export function processRawLines(rawLines: string[]): VerifierLogState {
// Process known messages and fixup parsed lines
knownMessageIdxs.forEach((idx) => {
const parsedLine = lines[idx];
if (
idx > 0 &&
parsedLine.type === ParsedLineType.KNOWN_MESSAGE &&
parsedLine.info.type == KnownMessageInfoType.GLOBAL_FUNC_VALID
) {
updateGlobalFuncCall(lines[idx - 1], parsedLine.info);
if (parsedLine.type !== ParsedLineType.KNOWN_MESSAGE) return;
const info = parsedLine.info;
if (info.type === KnownMessageInfoType.GLOBAL_FUNC_VALID && idx > 0) {
updateGlobalFuncCall(lines[idx - 1], info);
} else if (info.type === KnownMessageInfoType.BPF_STATE_EXPRS) {
updatePrevInsBpfState(lines, info, idx);
}
});

Expand Down
2 changes: 1 addition & 1 deletion src/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from "./parser";

const AluInstructionSample = "0: (b7) r2 = 1 ; R2_w=1";
const BPFStateExprSample = "; R2_w=1 R10=fp0 fp-24_w=1";
const BPFStateExprSample = "R2_w=1 R10=fp0 fp-24_w=1";
const MemoryWriteSample = "1: (7b) *(u64 *)(r10 -24) = r2" + BPFStateExprSample;
const CallInstructionSample = "7: (85) call bpf_probe_read_user#112";
const AddrSpaceCastSample1 =
Expand Down
Loading