Skip to content

Commit b0d53be

Browse files
committed
Handle "global and assumed valid" messages
Verifier may emit a message indicating a global function call, which is not verified inline, for example: 565: (85) call pc+234 Func#5 ('pick_any_bit') is global and assumed valid. This is important for visualizer, because so far `call pc+offset` pattern was handled exclusively as subprogram call. A small challenge with this is that the overall design of bpfvv expects line-by-line processing of the input log. And to handle cases like this some backtracking (or lookahead) mechanism is necessary, even though it is convenient to work with "lines" fundamentally. Address this with the following changes: * introduce ParsedLineType.KNOWN_MESSAGE and KnownMessageInfo to recognize particular verifier messages * leave parser interface unchanged (parseLine) * split parsed lines processing in the analyzer into separate steps * parse lines individually * process known messages, potentially editing parsedLines array (this is backtracking essentially) * and then build bpfStates and cSourceMap
1 parent f0a1ede commit b0d53be

File tree

6 files changed

+198
-19
lines changed

6 files changed

+198
-19
lines changed

src/analyzer.test.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@ import {
66
VerifierLogState,
77
} from "./analyzer";
88

9-
import { BPF_CALLEE_SAVED_REGS, BPF_SCRATCH_REGS, Effect } from "./parser";
9+
import {
10+
BPF_CALLEE_SAVED_REGS,
11+
BPF_SCRATCH_REGS,
12+
Effect,
13+
ParsedLineType,
14+
BpfInstructionKind,
15+
BpfJmpKind,
16+
} from "./parser";
1017

1118
function expectInitialBpfState(s: BpfState) {
1219
expect(s.frame).toBe(0);
@@ -448,4 +455,49 @@ to caller at 705:
448455
});
449456
});
450457
});
458+
459+
describe("processes known messages", () => {
460+
const verifierLogWithGlobalFuncCall = `
461+
0: (b7) r2 = 1 ; R2_w=1
462+
1: (85) call pc+10
463+
Func#123 ('my_global_func') is global and assumed valid.
464+
2: (bf) r0 = r1 ; R0_w=ctx() R1=ctx()
465+
`;
466+
467+
describe("global func call", () => {
468+
const strings = verifierLogWithGlobalFuncCall.split("\n");
469+
strings.shift(); // remove the first \n
470+
const logState: VerifierLogState = processRawLines(strings);
471+
const { lines, bpfStates } = logState;
472+
473+
it("transforms global function call correctly", () => {
474+
// Check that line 1 is transformed from SUBPROGRAM_CALL to HELPER_CALL
475+
expect(lines[1]).toMatchObject({
476+
type: ParsedLineType.INSTRUCTION,
477+
bpfIns: {
478+
kind: BpfInstructionKind.JMP,
479+
jmpKind: BpfJmpKind.HELPER_CALL,
480+
target: "my_global_func",
481+
reads: BPF_SCRATCH_REGS,
482+
writes: ["r0", ...BPF_SCRATCH_REGS],
483+
},
484+
});
485+
});
486+
487+
it("processes BPF states correctly after transformation", () => {
488+
// Check that the transformed call is processed as a helper call
489+
const callState = bpfStates[1];
490+
expect(callState.frame).toBe(0);
491+
expect(callState.pc).toBe(1);
492+
for (const reg of BPF_SCRATCH_REGS) {
493+
expect(callState.values.get(reg)).toMatchObject({
494+
effect: Effect.UPDATE,
495+
});
496+
}
497+
expect(callState.values.get("r0")).toMatchObject({
498+
effect: Effect.WRITE,
499+
});
500+
});
501+
});
502+
});
451503
});

src/analyzer.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
ParsedLineType,
1010
parseLine,
1111
getCLineId,
12+
KnownMessageInfoType,
13+
GlobalFuncValidInfo,
1214
} from "./parser";
1315

1416
export type BpfValue = {
@@ -267,16 +269,54 @@ export function getEmptyVerifierState(): VerifierLogState {
267269
};
268270
}
269271

272+
function updateGlobalFuncCall(callLine: ParsedLine, info: GlobalFuncValidInfo) {
273+
// "assumed valid" message indicates that previous instructions was a call to a global function which verifier recognizes as valid.
274+
// So we change the call ParsedLine to a HELPER_CALL, so that it is processed accordingly.
275+
if (callLine.type !== ParsedLineType.INSTRUCTION) return;
276+
const ins = callLine.bpfIns;
277+
if (
278+
ins.kind !== BpfInstructionKind.JMP ||
279+
ins.jmpKind !== BpfJmpKind.SUBPROGRAM_CALL
280+
)
281+
return;
282+
ins.jmpKind = BpfJmpKind.HELPER_CALL;
283+
ins.target = info.funcName;
284+
ins.reads = BPF_SCRATCH_REGS;
285+
ins.writes = ["r0", ...BPF_SCRATCH_REGS];
286+
}
287+
270288
export function processRawLines(rawLines: string[]): VerifierLogState {
271289
let bpfStates: BpfState[] = [];
272290
let lines: ParsedLine[] = [];
273291
let savedBpfStates: BpfState[] = [];
274292
let idxsForCLine: number[] = [];
275293
let currentCSourceLine: CSourceLine | undefined;
276294
const cSourceMap = new CSourceMap();
295+
const knownMessageIdxs: number[] = [];
277296

278-
rawLines.forEach((rawLine, idx) => {
297+
// First pass: parse individual lines
298+
lines = rawLines.map((rawLine, idx) => {
279299
const parsedLine = parseLine(rawLine, idx);
300+
if (parsedLine.type === ParsedLineType.KNOWN_MESSAGE) {
301+
knownMessageIdxs.push(idx);
302+
}
303+
return parsedLine;
304+
});
305+
306+
// Process known messages and fixup parsed lines
307+
knownMessageIdxs.forEach((idx) => {
308+
const parsedLine = lines[idx];
309+
if (
310+
idx > 0 &&
311+
parsedLine.type === ParsedLineType.KNOWN_MESSAGE &&
312+
parsedLine.info.type == KnownMessageInfoType.GLOBAL_FUNC_VALID
313+
) {
314+
updateGlobalFuncCall(lines[idx - 1], parsedLine.info);
315+
}
316+
});
317+
318+
// Second pass: build CSourceMap and BpfState[]
319+
lines.forEach((parsedLine, idx) => {
280320
switch (parsedLine.type) {
281321
case ParsedLineType.C_SOURCE: {
282322
if (currentCSourceLine) {
@@ -297,7 +337,6 @@ export function processRawLines(rawLines: string[]): VerifierLogState {
297337
savedBpfStates,
298338
);
299339
bpfStates.push(bpfState);
300-
lines.push(parsedLine);
301340
});
302341
if (currentCSourceLine) {
303342
cSourceMap.addCSourceLine(currentCSourceLine, idxsForCLine);

src/components.test.tsx

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -295,15 +295,11 @@ describe("JmpInstruction", () => {
295295
);
296296
});
297297

298-
describe("CallHtml argument counting", () => {
299-
function setCallArgValue(
300-
state: BpfState,
301-
arg: string,
302-
preCallValue: string,
303-
) {
304-
state.values.set(arg, makeValue("", Effect.UPDATE, preCallValue));
305-
}
298+
function setCallArgValue(state: BpfState, arg: string, preCallValue: string) {
299+
state.values.set(arg, makeValue("", Effect.UPDATE, preCallValue));
300+
}
306301

302+
describe("CallHtml argument counting", () => {
307303
it("shows 3 arguments when r4 and r5 are scratched", () => {
308304
const ins = createTargetJmpIns(
309305
BpfJmpCode.JA,
@@ -360,4 +356,30 @@ describe("JmpInstruction", () => {
360356
expect(innerHTML).not.toMatch(/r5.*,/);
361357
});
362358
});
359+
360+
describe("global function call rendering", () => {
361+
it("renders global function name", () => {
362+
// After analyzer transformation, a global function call becomes a HELPER_CALL
363+
// with the function name as the target (e.g., "my_global_func" instead of "pc+10")
364+
const ins = createTargetJmpIns(
365+
BpfJmpCode.CALL,
366+
BpfJmpKind.HELPER_CALL,
367+
"my_global_func",
368+
);
369+
const line = createLine(ins);
370+
const state = initialBpfState();
371+
setCallArgValue(state, "r1", "ctx()");
372+
setCallArgValue(state, "r2", "buffer_ptr");
373+
setCallArgValue(state, "r3", "");
374+
375+
render(<JmpInstruction ins={ins} line={line} state={state} />);
376+
const divs = document.getElementsByTagName("div");
377+
expect(divs.length).toBe(1);
378+
379+
const innerHTML = divs[0].innerHTML;
380+
expect(innerHTML).toContain("my_global_func");
381+
expect(innerHTML).toContain("r1");
382+
expect(innerHTML).toContain("r2");
383+
});
384+
});
363385
});

src/components.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,6 @@ function CallHtml({
6767
if (!location) {
6868
return <></>;
6969
}
70-
const start = line.raw.length + location.offset;
71-
const end = start + location.size;
7270
const target = ins.target || "";
7371
const helperName = target.substring(0, target.indexOf("#"));
7472

@@ -123,11 +121,7 @@ function CallHtml({
123121
}
124122

125123
let contents: ReactElement[] = [];
126-
contents.push(
127-
<React.Fragment key="line-start">
128-
{line.raw.slice(start, end)}
129-
</React.Fragment>,
130-
);
124+
contents.push(<React.Fragment key="line-start">{target}</React.Fragment>);
131125

132126
contents.push(<React.Fragment key="paren-open">(</React.Fragment>);
133127
for (let i = 1; i <= numArgs; i++) {

src/parser.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
BpfTargetJmpInstruction,
1313
BpfAddressSpaceCastInstruction,
1414
InstructionLine,
15+
KnownMessageInfoType,
1516
} from "./parser";
1617

1718
const AluInstructionSample = "0: (b7) r2 = 1 ; R2_w=1";
@@ -169,4 +170,29 @@ describe("parser", () => {
169170
raw: CSourceLineEmptySample2,
170171
});
171172
});
173+
174+
describe("Known message parsing", () => {
175+
const GlobalFuncValidSample =
176+
"Func#123 ('my_func') is global and assumed valid.";
177+
const NotAKnownMessage = "Some other verifier message that doesn't match";
178+
179+
it("parses global function valid messages", () => {
180+
const parsed = parseLine(GlobalFuncValidSample, 10);
181+
expect(parsed).toMatchObject({
182+
type: ParsedLineType.KNOWN_MESSAGE,
183+
idx: 10,
184+
raw: GlobalFuncValidSample,
185+
info: {
186+
type: KnownMessageInfoType.GLOBAL_FUNC_VALID,
187+
funcId: 123,
188+
funcName: "my_func",
189+
},
190+
});
191+
});
192+
193+
it("does not parse non-matching lines as known messages", () => {
194+
const parsed = parseLine(NotAKnownMessage, 5);
195+
expect(parsed.type).toBe(ParsedLineType.UNRECOGNIZED);
196+
});
197+
});
172198
});

src/parser.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ export enum ParsedLineType {
192192
UNRECOGNIZED = "UNRECOGNIZED",
193193
INSTRUCTION = "INSTRUCTION",
194194
C_SOURCE = "C_SOURCE",
195+
KNOWN_MESSAGE = "KNOWN_MESSAGE",
195196
}
196197

197198
type GenericParsedLine = {
@@ -217,7 +218,28 @@ export type CSourceLine = {
217218
id: string;
218219
} & GenericParsedLine;
219220

220-
export type ParsedLine = UnrecognizedLine | InstructionLine | CSourceLine;
221+
export enum KnownMessageInfoType {
222+
GLOBAL_FUNC_VALID = "GLOBAL_FUNC_VALID",
223+
}
224+
225+
export type GlobalFuncValidInfo = {
226+
type: KnownMessageInfoType.GLOBAL_FUNC_VALID;
227+
funcId: number;
228+
funcName: string;
229+
};
230+
231+
export type KnownMessageInfo = GlobalFuncValidInfo;
232+
233+
export type KnownMessageLine = {
234+
type: ParsedLineType.KNOWN_MESSAGE;
235+
info: KnownMessageInfo;
236+
} & GenericParsedLine;
237+
238+
export type ParsedLine =
239+
| UnrecognizedLine
240+
| InstructionLine
241+
| CSourceLine
242+
| KnownMessageLine;
221243

222244
type BpfStateExpr = {
223245
id: string;
@@ -815,13 +837,37 @@ function parseBpfInstruction(
815837
};
816838
}
817839

840+
const RE_MSG_GLOBAL_FUNC_VALID =
841+
/^Func#([-0-9]+) \('(.+)'\) is global and assumed valid\./;
842+
843+
function parseKnownMessage(
844+
rawLine: string,
845+
idx: number,
846+
): KnownMessageLine | null {
847+
let { match } = consumeRegex(RE_MSG_GLOBAL_FUNC_VALID, rawLine);
848+
if (!match) return null;
849+
return {
850+
type: ParsedLineType.KNOWN_MESSAGE,
851+
idx,
852+
raw: rawLine,
853+
info: {
854+
type: KnownMessageInfoType.GLOBAL_FUNC_VALID,
855+
funcId: parseInt(match[1], 10),
856+
funcName: match[2],
857+
},
858+
};
859+
}
860+
818861
export function parseLine(rawLine: string, idx: number): ParsedLine {
819862
let parsed: ParsedLine | null = parseCSourceLine(rawLine, idx);
820863
if (parsed) return parsed;
821864

822865
parsed = parseBpfInstruction(rawLine, idx);
823866
if (parsed) return parsed;
824867

868+
parsed = parseKnownMessage(rawLine, idx);
869+
if (parsed) return parsed;
870+
825871
return {
826872
type: ParsedLineType.UNRECOGNIZED,
827873
idx,

0 commit comments

Comments
 (0)