Skip to content

Commit bb2a07b

Browse files
committed
Emit new indirect guards when parents have different starts
If a block has two parents, that each make a LoadIndirect with a different start, we wouldn't always emit a new guard if the block did a LoadIndirect itself: if the start was greater, we'd consider it covered under the parents' guards. But as of commit ce7ee68 "Validate variable packet offsets, support negative offsets", we store the packet pointer plus the start offset in a register. If the guards have different starts, this register will therefore have a different value. We can't naively use it in the child block. Emit new guards (to load a new value in the register) when the start is different. For the C backend: move the asm volatile constraint just after calculating indirect, so clang can't assume it's value and reuse it between guards. Switch indirect from being a input, to an input / output so clang can't know it's value.
1 parent 4866675 commit bb2a07b

4 files changed

Lines changed: 59 additions & 9 deletions

File tree

c.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,11 +215,11 @@ func insnToC(insn instruction, blk *block) ([]string, error) {
215215
return []string{
216216
// Sign extend RegX to 64bits.
217217
fmt.Sprintf("indirect = (uint8_t *) (((int64_t) (int32_t) x) + %d);", i.start),
218+
// Prevent clang from sharing indirect between bounds checks:
219+
// the verifier needs to see the full dance every time.
220+
fmt.Sprintf(`asm volatile("" : "+r" (indirect));`),
218221
fmt.Sprintf("if ((uint64_t)indirect >= %d) return false;", i.maxStartOffset()),
219222
fmt.Sprintf("indirect = data + (uint64_t)indirect;"),
220-
// Prevent clang from calculating indirect + delta() directly from the packet start when RegX is constant:
221-
// only indirect has the correct bounds check.
222-
fmt.Sprintf(`asm volatile("" : : "r" (indirect));`),
223223
fmt.Sprintf("if (indirect + %d > data_end) return false;", i.length()),
224224
}, nil
225225

cbpfc.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,11 +242,13 @@ func (a packetGuardIndirect) restrict(o packetGuard) packetGuard {
242242
return packetGuardIndirect{}
243243
}
244244

245-
n := a
246-
247-
if b.start > a.start {
248-
n.start = b.start
245+
// If the start is different, we need a new guard: we can't know which of the two
246+
// offsets have been stored for the RegX + start check.
247+
if a.start != b.start {
248+
return packetGuardIndirect{}
249249
}
250+
251+
n := a
250252
if b.end < a.end {
251253
n.end = b.end
252254
}

cbpfc_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1410,7 +1410,7 @@ func TestIndirectGuardParentsNotOK(t *testing.T) {
14101410
addIndirectPacketGuards(blocks, compileOpts{})
14111411

14121412
matchBlock(t, blocks[0], join(
1413-
[]instruction{{Instruction: packetGuardIndirect{start: 9, end: 14}}},
1413+
[]instruction{{Instruction: packetGuardIndirect{start: 10, end: 14}}},
14141414
insns[:2],
14151415
), nil)
14161416
matchBlock(t, blocks[1], join(
@@ -1421,7 +1421,12 @@ func TestIndirectGuardParentsNotOK(t *testing.T) {
14211421
[]instruction{{Instruction: packetGuardIndirect{start: 9, end: 15}}},
14221422
insns[4:5],
14231423
), nil)
1424-
matchBlock(t, blocks[3], insns[5:], nil)
1424+
// block 3 is reached from block 1 (start 8) and block 2 (start 9). The two
1425+
// anchors disagree, so it can't reuse either parent's guard and emits its own.
1426+
matchBlock(t, blocks[3], join(
1427+
[]instruction{{Instruction: packetGuardIndirect{start: 9, end: 10}}},
1428+
insns[5:],
1429+
), nil)
14251430
}
14261431

14271432
func join(insns ...[]instruction) []instruction {

insn_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,49 @@ func TestLoadIndirectGuardOverflow(t *testing.T) {
347347
}, nil, noMatch)
348348
}
349349

350+
// A block with an indirect load that is reachable from two parents with
351+
// indirect guards that have different starts.
352+
func TestLoadIndirectGuardStartMismatch(t *testing.T) {
353+
t.Parallel()
354+
355+
filter := []bpf.Instruction{
356+
// block 0: variable RegX (0 for the test packets), branch on packet[0].
357+
/* 0 */ bpf.LoadAbsolute{Off: 0, Size: 4},
358+
/* 1 */ bpf.TAX{}, // RegX = packet[0:4] = 0
359+
/* 2 */ bpf.LoadAbsolute{Off: 4, Size: 1},
360+
/* 3 */ bpf.JumpIf{Cond: bpf.JumpEqual, Val: 1, SkipTrue: 0, SkipFalse: 2}, // block 1 or block 2
361+
362+
// block 1: indirect guard start 8.
363+
/* 4 */ bpf.LoadIndirect{Off: 8, Size: 4}, // guard [8:12]
364+
/* 5 */ bpf.Jump{Skip: 1}, // jump to block 3
365+
366+
// block 2: indirect guard start 13.
367+
/* 6 */ bpf.LoadIndirect{Off: 13, Size: 2}, // guard [13:15]
368+
// fall through to block 3
369+
370+
// block 3: indirect load at Off 9 - reached from both parents.
371+
/* 7 */ bpf.LoadIndirect{Off: 9, Size: 1},
372+
/* 8 */ bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x42, SkipTrue: 1},
373+
/* 9 */ bpf.RetConstant{Val: 0},
374+
/* 10 */ bpf.RetConstant{Val: 1},
375+
}
376+
377+
// packet[0:4] = 0 -> RegX == 0, so LoadIndirect offsets are absolute packet offsets.
378+
// packet[9] = 0x42 is the byte block 3 must read to match.
379+
// The other guarded bytes are 0 so a wrong read yields the wrong (noMatch) result.
380+
packet := func(selector byte) []byte {
381+
in := make([]byte, 16)
382+
in[4] = selector // selects block 1 (==1) or block 2 (!=1)
383+
in[9] = 0x42
384+
return in
385+
}
386+
387+
// Path through block 1 (guard start 8).
388+
checkBackends(t, filter, packet(1), match)
389+
// Path through block 2 (guard start 13).
390+
checkBackends(t, filter, packet(0), match)
391+
}
392+
350393
// Indirect load with an offset packet pointer.
351394
func TestLoadIndirectPacketStartMaxOffset(t *testing.T) {
352395
t.Parallel()

0 commit comments

Comments
 (0)