Skip to content

Commit 8e48ca8

Browse files
authored
Nested stack frames and bpf_loop support (#86)
BPF allows passing stack pointers to a subprogram, which then can do load/store operations on such pointers. In the verification log, such pointers are referred to with the following notation: `fp[<frame>]<offset>`, for example `fp[1]-64`. In particular, bpf_loop [1] helper accepts a callback and pointer to context, which must be a stack pointer. And so the verifier log trace for bpf_loop looks like a helper call, followed by instruction stream of the callback function (in the new frame), but with the reference to the context in one of the registers. These changes implement support for value references and dependency tracking of stack slots at arbitrary depth, as well as UI changes to display all stack frames (and not only the current one). This is achieved by introduction of `BpfMemSlotMap` type. It's essentially a map with string keys, except that it maintains currentFrame value to be able to correctly identify the frame of references like `fp-64` where frame is not specified. `BpfState` is factored out into a class and the `values` and `lastKnownWrites` maps of `BpfState` thus become `BpfMemSlotMap`s, allowing to not modify the logic of how they are built and maintained for the most part. Implement carrying over of stack values from parent frame to inner frame and back (on exit), so that inter-frame stack slot dependencies can be tracked. [1] https://docs.ebpf.io/linux/helper-function/bpf_loop/
1 parent c122272 commit 8e48ca8

File tree

7 files changed

+577
-192
lines changed

7 files changed

+577
-192
lines changed

src/__snapshots__/App.test.tsx.snap

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,9 @@ exports[`App renders the log visualizer when text is pasted 1`] = `
232232
id="line-0"
233233
line-index="0"
234234
>
235+
<span
236+
class="line-indent"
237+
/>
235238
*(u8 *)(
236239
<span
237240
class="mem-slot r7"
@@ -294,7 +297,7 @@ exports[`App renders the log visualizer when text is pasted 1`] = `
294297
</div>
295298
<div>
296299
Frame:
297-
0
300+
1
298301
</div>
299302
</div>
300303
<table>

src/analyzer.test.ts

Lines changed: 120 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {
22
BpfState,
33
BpfValue,
44
getMemSlotDependencies,
5-
initialBpfState,
65
processRawLines,
76
VerifierLogState,
87
} from "./analyzer";
@@ -14,6 +13,8 @@ import {
1413
ParsedLineType,
1514
BpfInstructionKind,
1615
BpfJmpKind,
16+
BpfTargetJmpInstruction,
17+
InstructionLine,
1718
} from "./parser";
1819

1920
function expectInitialBpfState(s: BpfState) {
@@ -42,8 +43,8 @@ function getVerifierLogState(logString: string): VerifierLogState {
4243
}
4344

4445
describe("analyzer", () => {
45-
it("returns valid initialBpfState()", () => {
46-
expectInitialBpfState(initialBpfState());
46+
it("returns valid new BpfState()", () => {
47+
expectInitialBpfState(new BpfState({}));
4748
});
4849

4950
const basicVerifierLog = `
@@ -682,4 +683,120 @@ Func#123 ('my_global_func') is global and assumed valid.
682683
expect(r6Deps).toEqual(new Set<number>([4, 3, 2, 1, 0]));
683684
});
684685
});
686+
687+
describe("tracks parent stack slot writes", () => {
688+
const rawLog = `
689+
2829: (7b) *(u64 *)(r10 -8) = r1 ; R1_w=0 R10=fp0 fp-8_w=0
690+
2830: (bf) r3 = r10 ; R3_w=fp0 R10=fp0
691+
2831: (07) r3 += -8 ; R3_w=fp-8
692+
2832: (bf) r1 = r6 ; R1_w=scalar(id=5800,umin=1) R6_w=scalar(id=5800,umin=1)
693+
2833: (18) r2 = 0xdeadbeef ; R2=0xdeadbeef
694+
2835: (85) call pc+140
695+
2976: frame1: R1=scalar(id=5800,umin=1) R2=0xdeadbeef R3=fp[0]-8 R10=fp0
696+
; bt_node *btn = btree->root; @ btree.bpf.c:146
697+
2976: (bf) r1 = addr_space_cast(r1, 0, 1) ; frame1: R1_w=arena
698+
2992: (79) r6 = *(u64 *)(r3 +0) ; frame1: R6_w=0 R3=fp[0]-8
699+
2993: (07) r6 += 13 ; frame1: R6_w=13
700+
2994: (7b) *(u64 *)(r3 +0) = r6 ; frame1: R3=fp[0]-8 R6=13
701+
2995: (b4) w0 = 0 ; frame1: R0_w=0
702+
3028: (95) exit
703+
3050: (79) r7 = *(u64 *)(r10 -8) ; fp-8=13 R7_w=13
704+
`;
705+
const logState = getVerifierLogState(rawLog);
706+
it("tracks fp-8 dependency on write at pc 2994", () => {
707+
const idx = 14;
708+
const s = logState.bpfStates[idx];
709+
expect(s.idx).toBe(14);
710+
expect(s.pc).toBe(3050);
711+
const deps = getMemSlotDependencies(logState, idx, "fp-8");
712+
expect(deps).toEqual(new Set<number>([11]));
713+
expect(logState.bpfStates[11].pc).toBe(2994);
714+
});
715+
});
716+
717+
describe("identifies bpf_loop as a subprogram call", () => {
718+
const rawLog = `
719+
1187: (7b) *(u64 *)(r10 -64) = r9 ; frame1: R9=42 R10=fp0 fp-64_w=42 refs=84
720+
1189: (bf) r3 = r10 ; frame1: R3_w=fp0 R10=fp0 refs=84
721+
1190: (07) r3 += -64 ; frame1: R3_w=fp-64 refs=84
722+
; bpf_loop(BPFJ_FILE_MAX_RECURSION, &bpfj_file_save_path, &ctx, 0); @ file.h:256
723+
1191: (b4) w1 = 256 ; frame1: R1_w=256 refs=84
724+
1192: (18) r2 = 0x1bf ; frame1: R2_w=func() refs=84
725+
1194: (b7) r4 = 0 ; frame1: R4=0 refs=84
726+
1195: (85) call bpf_loop#181
727+
1640: frame2: R1=scalar() R2=fp[1]-64 R10=fp0 refs=84 cb
728+
1641: (a7) r1 ^= -1 ; frame2: R1_w=scalar(smin=0xffffffff00000000,smax=-1,umin=0xffffffff00000000,var_off=(0xffffffff00000000; 0xffffffff)) refs=84 cb
729+
1649: (63) *(u32 *)(r2 +16) = r1 ; frame2: R1_w=0xfffffff9 R2=fp[1]-64 refs=84 cb
730+
1650: (05) goto pc+27
731+
1678: (95) exit
732+
returning from callee:
733+
frame2: R0=1 R1=0xfffffff9 R2=fp[1]-64
734+
to caller at 1195:
735+
frame1: R0=map_value(map=bpfj_file_scrat,ks=4,vs=1024) R1=256 R2=func() R3=fp-64
736+
1196: (71) r1 = *(u8 *)(r6 +1) ; frame1: R1_w=scalar(smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff)) R6=map_value(map=bpfj_file_scrat,ks=4,vs=1024) refs=84
737+
`;
738+
const logState = getVerifierLogState(rawLog);
739+
it("bpf_loop is in new frame", () => {
740+
const s = logState.bpfStates[7];
741+
expect(s.idx).toBe(7);
742+
expect(s.pc).toBe(1195);
743+
expect(s.frame).toBe(2);
744+
expect(s.values.get("r1")?.value).toBe("scalar()");
745+
expect(s.values.get("r2")?.value).toBe("fp[1]-64");
746+
expect(s.values.get("fp-64")?.value).toBeUndefined();
747+
expect(s.values.get("fp[1]-64")?.value).toBe("42");
748+
749+
let line = logState.lines[7];
750+
expect(line.type).toBe(ParsedLineType.INSTRUCTION);
751+
line = <InstructionLine>line;
752+
expect(line.bpfIns).toMatchObject({
753+
kind: BpfInstructionKind.JMP,
754+
jmpKind: BpfJmpKind.HELPER_CALL,
755+
});
756+
const ins = <BpfTargetJmpInstruction>line.bpfIns;
757+
expect(ins.target).toContain("bpf_loop");
758+
});
759+
it("r2+16 at 1649 writes to fp[1]-48", () => {
760+
const s = logState.bpfStates[10];
761+
expect(s.idx).toBe(10);
762+
expect(s.pc).toBe(1649);
763+
expect(s.frame).toBe(2);
764+
expect(s.values.get("r1")?.value).toBe("0xfffffff9");
765+
expect(s.values.get("fp[1]-48")?.value).toBe("0xfffffff9");
766+
expect(s.values.get("fp[0]-48")?.value).toBeUndefined();
767+
expect(s.values.get("fp-48")?.value).toBeUndefined();
768+
});
769+
it("exit pops the frame", () => {
770+
const s = logState.bpfStates[12];
771+
expect(s.idx).toBe(12);
772+
expect(s.pc).toBe(1678);
773+
expect(s.frame).toBe(1);
774+
expect(s.values.get("fp[1]-64")?.value).toBe("42");
775+
expect(s.values.get("fp-64")?.value).toBe("42");
776+
});
777+
});
778+
779+
describe("computes dependencies correctly for stack slot", () => {
780+
const rawLog = `
781+
3172: (bf) r1 = addr_space_cast(r8, 0, 1) ; frame1: R1_w=arena R8=scalar()
782+
3173: (7b) *(u64 *)(r10 -8) = r1 ; frame1: R1_w=arena R10=fp0 fp-8_w=arena
783+
3314: (79) r1 = *(u64 *)(r10 -8) ; frame1: R1_w=arena R10=fp0 fp-8=arena
784+
3315: (0f) r2 += r1
785+
`;
786+
const logState = getVerifierLogState(rawLog);
787+
it("fp-8 dependencies at 3314", () => {
788+
const s = logState.bpfStates[2];
789+
expect(s.idx).toBe(2);
790+
expect(s.pc).toBe(3314);
791+
const deps = getMemSlotDependencies(logState, s.idx, "fp-8");
792+
expect(deps).toEqual(new Set<number>([1, 0]));
793+
});
794+
it("r1 dependencies at 3315", () => {
795+
const s = logState.bpfStates[3];
796+
expect(s.idx).toBe(3);
797+
expect(s.pc).toBe(3315);
798+
const deps = getMemSlotDependencies(logState, s.idx, "r1");
799+
expect(deps).toEqual(new Set<number>([2, 1, 0]));
800+
});
801+
});
685802
});

0 commit comments

Comments
 (0)