Skip to content

Commit 8636c48

Browse files
committed
Parse value expressions printed on separate lines
Verifier in some cases may print information about known values separately from the relevant instruciton, for example: 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 Note that second line contains state produced by instrcution at 100. Implement parsing these messages and incorporating the information from them in the computed array of BpfState objects.
1 parent c6693ca commit 8636c48

File tree

4 files changed

+159
-65
lines changed

4 files changed

+159
-65
lines changed

src/analyzer.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,4 +563,42 @@ Func#123 ('my_global_func') is global and assumed valid.
563563
});
564564
});
565565
});
566+
567+
describe("takes into accout BPF_STATE_EXPRS messages", () => {
568+
const rawLog = `
569+
96: (18) r1 = 0xffff888370cf0a00 ; frame1: R1_w=map_ptr(map=bpfj_log_map,ks=0,vs=0)
570+
98: (b7) r2 = 196 ; frame1: R2_w=196
571+
99: (b7) r3 = 0 ; frame1: R3_w=0
572+
100: (85) call bpf_ringbuf_reserve#131
573+
101: frame1: R0=ringbuf_mem_or_null(id=5,ref_obj_id=5,sz=196) refs=5
574+
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
575+
`;
576+
const { bpfStates } = getVerifierLogState(rawLog);
577+
const val = "ringbuf_mem_or_null(id=5,ref_obj_id=5,sz=196)";
578+
it("call bpf_ringbuf_reserve#131 contains state from the next log line", () => {
579+
const s = bpfStates[3];
580+
expect(s.idx).toBe(3);
581+
expect(s.pc).toBe(100);
582+
expect(s.values.get("r0")).toMatchObject({
583+
value: val,
584+
effect: Effect.WRITE,
585+
});
586+
});
587+
588+
it("101: (bf) r7 = r0 depends on the call", () => {
589+
const s = bpfStates[5];
590+
expect(s.idx).toBe(5);
591+
expect(s.pc).toBe(101);
592+
expect(s.values.get("r0")).toMatchObject({
593+
value: val,
594+
effect: Effect.READ,
595+
});
596+
expect(s.values.get("r7")).toMatchObject({
597+
value: val,
598+
effect: Effect.WRITE,
599+
});
600+
expect(s.lastKnownWrites.get("r0")).toBe(3);
601+
expect(s.lastKnownWrites.get("r7")).toBe(5);
602+
});
603+
});
566604
});

src/analyzer.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import {
1111
getCLineId,
1212
KnownMessageInfoType,
1313
GlobalFuncValidInfo,
14+
InstructionLine,
15+
BpfStateExprsInfo,
1416
} from "./parser";
17+
import { siblingInsLine } from "./utils";
1518

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

299+
function updatePrevInsBpfState(
300+
lines: ParsedLine[],
301+
info: BpfStateExprsInfo,
302+
idx: number,
303+
) {
304+
// the heuristic for BPF_STATE_EXPRS messages is to append
305+
// the exprs to the state of the _previous_ instruction
306+
const prevIdx = siblingInsLine(lines, idx, -1);
307+
if (prevIdx < idx) {
308+
const prevLine = <InstructionLine>lines[prevIdx];
309+
prevLine.bpfStateExprs.push(...info.bpfStateExprs);
310+
}
311+
}
312+
296313
export function processRawLines(rawLines: string[]): VerifierLogState {
297314
let bpfStates: BpfState[] = [];
298315
let lines: ParsedLine[] = [];
@@ -322,12 +339,12 @@ export function processRawLines(rawLines: string[]): VerifierLogState {
322339
// Process known messages and fixup parsed lines
323340
knownMessageIdxs.forEach((idx) => {
324341
const parsedLine = lines[idx];
325-
if (
326-
idx > 0 &&
327-
parsedLine.type === ParsedLineType.KNOWN_MESSAGE &&
328-
parsedLine.info.type == KnownMessageInfoType.GLOBAL_FUNC_VALID
329-
) {
330-
updateGlobalFuncCall(lines[idx - 1], parsedLine.info);
342+
if (parsedLine.type !== ParsedLineType.KNOWN_MESSAGE) return;
343+
const info = parsedLine.info;
344+
if (info.type === KnownMessageInfoType.GLOBAL_FUNC_VALID && idx > 0) {
345+
updateGlobalFuncCall(lines[idx - 1], info);
346+
} else if (info.type === KnownMessageInfoType.BPF_STATE_EXPRS) {
347+
updatePrevInsBpfState(lines, info, idx);
331348
}
332349
});
333350

src/parser.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
} from "./parser";
1717

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

src/parser.ts

Lines changed: 97 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ export type CSourceLine = {
221221

222222
export enum KnownMessageInfoType {
223223
GLOBAL_FUNC_VALID = "GLOBAL_FUNC_VALID",
224+
BPF_STATE_EXPRS = "BPF_STATE_EXPRS",
224225
}
225226

226227
export type GlobalFuncValidInfo = {
@@ -229,7 +230,13 @@ export type GlobalFuncValidInfo = {
229230
funcName: string;
230231
};
231232

232-
export type KnownMessageInfo = GlobalFuncValidInfo;
233+
export type BpfStateExprsInfo = {
234+
type: KnownMessageInfoType.BPF_STATE_EXPRS;
235+
pc: number;
236+
bpfStateExprs: BpfStateExpr[];
237+
};
238+
239+
export type KnownMessageInfo = BpfStateExprsInfo | GlobalFuncValidInfo;
233240

234241
export type KnownMessageLine = {
235242
type: ParsedLineType.KNOWN_MESSAGE;
@@ -247,11 +254,67 @@ type BpfStateExpr = {
247254
value: string;
248255
rawKey: string;
249256
frame?: number;
257+
pc?: number;
250258
};
251259

252260
export const BPF_SCRATCH_REGS = ["r1", "r2", "r3", "r4", "r5"];
253261
export const BPF_CALLEE_SAVED_REGS = ["r6", "r7", "r8", "r9"];
254262

263+
const RE_WHITESPACE = /^\s+/;
264+
const RE_PROGRAM_COUNTER = /^([0-9]+):/;
265+
const RE_BPF_OPCODE = /^\(([0-9a-f][0-9a-f])\)/;
266+
const RE_REGISTER = /^(r10|r[0-9]|w[0-9])/;
267+
const RE_MEMORY_REF = /^\*\((u8|u16|u32|u64) \*\)\((r10|r[0-9]) ([+-][0-9]+)\)/;
268+
const RE_IMM_VALUE = /^(0x[0-9a-f]+|[+-]?[0-9]+)/;
269+
const RE_CALL_TARGET = /^call ([0-9a-z_#+-]+)/;
270+
const RE_GOTO_OP = /^((?:may_)?goto|goto_or_nop) (pc[+-][0-9]+)/;
271+
const RE_FRAME_ID = /^frame([0-9]+): /;
272+
const RE_ADDR_SPACE_CAST =
273+
/^(r[0-9]) = addr_space_cast\((r[0-9]), ([0-9]+, [0-9]+)\)/;
274+
const RE_C_SOURCE_LINE = /^;\s*(.*) @ ([a-zA-Z0-9_\-.]+):([0-9]+)/;
275+
276+
const BPF_ALU_OPERATORS = [
277+
"s>>=",
278+
"s<<=",
279+
"<<=",
280+
">>=",
281+
"+=",
282+
"-=",
283+
"*=",
284+
"/=",
285+
"%=",
286+
"&=",
287+
"|=",
288+
"^=",
289+
"=",
290+
];
291+
const BPF_COND_OPERATORS = [
292+
"s>=",
293+
"s<=",
294+
"==",
295+
"!=",
296+
"<=",
297+
">=",
298+
"s<",
299+
"s>",
300+
"<",
301+
">",
302+
];
303+
304+
function parseProgramCounter(str: string): { pc: number | null; rest: string } {
305+
const { match, rest } = consumeRegex(RE_PROGRAM_COUNTER, str);
306+
if (match)
307+
return {
308+
pc: parseInt(match[1], 10),
309+
rest,
310+
};
311+
else
312+
return {
313+
pc: null,
314+
rest: str,
315+
};
316+
}
317+
255318
const parseBpfStateExpr = (
256319
str: string,
257320
): { expr: BpfStateExpr; rest: string } | undefined => {
@@ -285,10 +348,9 @@ const parseBpfStateExpr = (
285348
export const parseBpfStateExprs = (
286349
str: string,
287350
): { exprs: BpfStateExpr[]; rest: string } => {
288-
let { match, rest } = consumeString("; ", str);
289-
if (!match) return { exprs: [], rest: str };
351+
let { pc, rest } = parseProgramCounter(str);
290352

291-
let frame = consumeRegex(RE_FRAME_ID, rest);
353+
let frame = consumeRegex(RE_FRAME_ID, consumeSpaces(rest));
292354
let frameId = 0;
293355
if (frame.match) {
294356
frameId = parseInt(frame.match[1], 10);
@@ -301,52 +363,12 @@ export const parseBpfStateExprs = (
301363
if (!parsed) break;
302364
rest = consumeSpaces(parsed.rest);
303365
parsed.expr.frame = frameId;
366+
if (pc) parsed.expr.pc = pc;
304367
exprs.push(parsed.expr);
305368
}
306369
return { exprs, rest };
307370
};
308371

309-
const RE_WHITESPACE = /^\s+/;
310-
const RE_PROGRAM_COUNTER = /^([0-9]+):/;
311-
const RE_BPF_OPCODE = /^\(([0-9a-f][0-9a-f])\)/;
312-
const RE_REGISTER = /^(r10|r[0-9]|w[0-9])/;
313-
const RE_MEMORY_REF = /^\*\((u8|u16|u32|u64) \*\)\((r10|r[0-9]) ([+-][0-9]+)\)/;
314-
const RE_IMM_VALUE = /^(0x[0-9a-f]+|[+-]?[0-9]+)/;
315-
const RE_CALL_TARGET = /^call ([0-9a-z_#+-]+)/;
316-
const RE_GOTO_OP = /^((?:may_)?goto|goto_or_nop) (pc[+-][0-9]+)/;
317-
const RE_FRAME_ID = /^frame([0-9]+): /;
318-
const RE_ADDR_SPACE_CAST =
319-
/^(r[0-9]) = addr_space_cast\((r[0-9]), ([0-9]+, [0-9]+)\)/;
320-
const RE_C_SOURCE_LINE = /^;\s*(.*) @ ([a-zA-Z0-9_\-.]+):([0-9]+)/;
321-
322-
const BPF_ALU_OPERATORS = [
323-
"s>>=",
324-
"s<<=",
325-
"<<=",
326-
">>=",
327-
"+=",
328-
"-=",
329-
"*=",
330-
"/=",
331-
"%=",
332-
"&=",
333-
"|=",
334-
"^=",
335-
"=",
336-
];
337-
const BPF_COND_OPERATORS = [
338-
"s>=",
339-
"s<=",
340-
"==",
341-
"!=",
342-
"<=",
343-
">=",
344-
"s<",
345-
"s>",
346-
"<",
347-
">",
348-
];
349-
350372
function consumeRegex(
351373
regex: RegExp,
352374
str: string,
@@ -820,18 +842,15 @@ function parseBpfInstruction(
820842
rawLine: string,
821843
idx: number,
822844
): InstructionLine | null {
823-
let { match, rest } = consumeRegex(
824-
RE_PROGRAM_COUNTER,
825-
consumeSpaces(rawLine),
826-
);
827-
if (!match) return null;
845+
let { pc, rest } = parseProgramCounter(consumeSpaces(rawLine));
846+
if (pc === null) return null;
828847

829-
const pc = parseInt(match[1], 10);
830848
const parsedIns = parseOpcodeIns(consumeSpaces(rest), pc);
831849
if (!parsedIns.ins) return null;
832850

833851
const ins = parsedIns.ins;
834852
rest = consumeSpaces(parsedIns.rest);
853+
rest = consumeString("; ", rest).rest;
835854
const { exprs } = parseBpfStateExprs(rest);
836855
return {
837856
type: ParsedLineType.INSTRUCTION,
@@ -845,21 +864,41 @@ function parseBpfInstruction(
845864
const RE_MSG_GLOBAL_FUNC_VALID =
846865
/^Func#([-0-9]+) \('(.+)'\) is global and assumed valid\./;
847866

867+
function parseGlobalFuncValidMessage(str: string): GlobalFuncValidInfo | null {
868+
let { match } = consumeRegex(RE_MSG_GLOBAL_FUNC_VALID, str);
869+
if (!match) return null;
870+
return {
871+
type: KnownMessageInfoType.GLOBAL_FUNC_VALID,
872+
funcId: parseInt(match[1], 10),
873+
funcName: match[2],
874+
};
875+
}
876+
877+
function parseExprsOnly(str: string): BpfStateExprsInfo | null {
878+
let { pc, rest } = parseProgramCounter(consumeSpaces(str));
879+
if (pc === null) return null;
880+
rest = consumeSpaces(rest);
881+
const { exprs } = parseBpfStateExprs(rest);
882+
return {
883+
type: KnownMessageInfoType.BPF_STATE_EXPRS,
884+
bpfStateExprs: exprs,
885+
pc,
886+
};
887+
}
888+
848889
function parseKnownMessage(
849890
rawLine: string,
850891
idx: number,
851892
): KnownMessageLine | null {
852-
let { match } = consumeRegex(RE_MSG_GLOBAL_FUNC_VALID, rawLine);
853-
if (!match) return null;
893+
let info: KnownMessageInfo | null;
894+
info = parseGlobalFuncValidMessage(rawLine);
895+
if (!info) info = parseExprsOnly(rawLine);
896+
if (!info) return null;
854897
return {
855898
type: ParsedLineType.KNOWN_MESSAGE,
856899
idx,
857900
raw: rawLine,
858-
info: {
859-
type: KnownMessageInfoType.GLOBAL_FUNC_VALID,
860-
funcId: parseInt(match[1], 10),
861-
funcName: match[2],
862-
},
901+
info,
863902
};
864903
}
865904

0 commit comments

Comments
 (0)