Skip to content

Commit 425cfbc

Browse files
Jakub Kicinskishiloong
authored andcommitted
bpf: verifier: make sure callees don't prune with caller differences
commit 7640ead upstream ANBZ: torvalds#200 Currently for liveness and state pruning the register parentage chains don't include states of the callee. This makes some sense as the callee can't access those registers. However, this means that READs done after the callee returns will not propagate into the states of the callee. Callee will then perform pruning disregarding differences in caller state. Example: 0: (85) call bpf_user_rnd_u32 1: (b7) r8 = 0 2: (55) if r0 != 0x0 goto pc+1 3: (b7) r8 = 1 4: (bf) r1 = r8 5: (85) call pc+4 6: (15) if r8 == 0x1 goto pc+1 7: (05) *(u64 *)(r9 - 8) = r3 8: (b7) r0 = 0 9: (95) exit 10: (15) if r1 == 0x0 goto pc+0 11: (95) exit Here we acquire unknown state with call to get_random() [1]. Then we store this random state in r8 (either 0 or 1) [1 - 3], and make a call on line 5. Callee does nothing but a trivial conditional jump (to create a pruning point). Upon return caller checks the state of r8 and either performs an unsafe read or not. Verifier will first explore the path with r8 == 1, creating a pruning point at [11]. The parentage chain for r8 will include only callers states so once verifier reaches [6] it will mark liveness only on states in the caller, and not [11]. Now when verifier walks the paths with r8 == 0 it will reach [11] and since REG_LIVE_READ on r8 was not propagated there it will prune the walk entirely (stop walking the entire program, not just the callee). Since [6] was never walked with r8 == 0, [7] will be considered dead and replaced with "goto -1" causing hang at runtime. This patch weaves the callee's explored states onto the callers parentage chain. Rough parentage for r8 would have looked like this before: [0] [1] [2] [3] [4] [5] [10] [11] [6] [7] | | ,---|----. | | | sl0: sl0: / sl0: \ sl0: sl0: sl0: fr0: r8 <-- fr0: r8<+--fr0: r8 `fr0: r8 ,fr0: r8<-fr0: r8 \ fr1: r8 <- fr1: r8 / \__________________/ after: [0] [1] [2] [3] [4] [5] [10] [11] [6] [7] | | | | | | sl0: sl0: sl0: sl0: sl0: sl0: fr0: r8 <-- fr0: r8 <- fr0: r8 <- fr0: r8 <-fr0: r8<-fr0: r8 fr1: r8 <- fr1: r8 Now the mark from instruction 6 will travel through callees states. Note that we don't have to connect r0 because its overwritten by callees state on return and r1 - r5 because those are not alive any more once a call is made. v2: - don't connect the callees registers twice (Alexei: suggestion & code) - add more details to the comment (Ed & Alexei) v1: don't unnecessarily link caller saved regs (Jiong) Fixes: f4d7e40 ("bpf: introduce function calls (verification)") Reported-by: David Beckett <[email protected]> Signed-off-by: Jakub Kicinski <[email protected]> Reviewed-by: Jiong Wang <[email protected]> Reviewed-by: Edward Cree <[email protected]> Signed-off-by: Alexei Starovoitov <[email protected]> Signed-off-by: Qiao Ma <[email protected]> Acked-by: Tony Lu <[email protected]>
1 parent 1bcb235 commit 425cfbc

File tree

2 files changed

+38
-3
lines changed

2 files changed

+38
-3
lines changed

kernel/bpf/verifier.c

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6338,9 +6338,16 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx)
63386338
clear_jmp_history(cur);
63396339
new_sl->next = *explored_state(env, insn_idx);
63406340
*explored_state(env, insn_idx) = new_sl;
6341-
/* connect new state to parentage chain */
6342-
for (i = 0; i < BPF_REG_FP; i++)
6343-
cur_regs(env)[i].parent = &new->frame[new->curframe]->regs[i];
6341+
/* connect new state to parentage chain. Current frame needs all
6342+
* registers connected. Only r6 - r9 of the callers are alive (pushed
6343+
* to the stack implicitly by JITs) so in callers' frames connect just
6344+
* r6 - r9 as an optimization. Callers will have r1 - r5 connected to
6345+
* the state of the call instruction (with WRITTEN set), and r0 comes
6346+
* from callee with its full parentage chain, anyway.
6347+
*/
6348+
for (j = 0; j <= cur->curframe; j++)
6349+
for (i = j < cur->curframe ? BPF_REG_6 : 0; i < BPF_REG_FP; i++)
6350+
cur->frame[j]->regs[i].parent = &new->frame[j]->regs[i];
63446351
/* clear write marks in current state: the writes we did are not writes
63456352
* our child did, so they don't screen off its reads from us.
63466353
* (There are no read marks in current state, because reads always mark

tools/testing/selftests/bpf/test_verifier.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13071,6 +13071,34 @@ static struct bpf_test tests[] = {
1307113071
.result_unpriv = REJECT,
1307213072
.result = ACCEPT,
1307313073
},
13074+
{
13075+
"calls: cross frame pruning",
13076+
.insns = {
13077+
/* r8 = !!random();
13078+
* call pruner()
13079+
* if (r8)
13080+
* do something bad;
13081+
*/
13082+
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0,
13083+
BPF_FUNC_get_prandom_u32),
13084+
BPF_MOV64_IMM(BPF_REG_8, 0),
13085+
BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1),
13086+
BPF_MOV64_IMM(BPF_REG_8, 1),
13087+
BPF_MOV64_REG(BPF_REG_1, BPF_REG_8),
13088+
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 1, 0, 4),
13089+
BPF_JMP_IMM(BPF_JEQ, BPF_REG_8, 1, 1),
13090+
BPF_LDX_MEM(BPF_B, BPF_REG_9, BPF_REG_1, 0),
13091+
BPF_MOV64_IMM(BPF_REG_0, 0),
13092+
BPF_EXIT_INSN(),
13093+
BPF_JMP_IMM(BPF_JEQ, BPF_REG_1, 0, 0),
13094+
BPF_EXIT_INSN(),
13095+
},
13096+
.prog_type = BPF_PROG_TYPE_SOCKET_FILTER,
13097+
.errstr_unpriv = "function calls to other bpf functions are allowed for root only",
13098+
.result_unpriv = REJECT,
13099+
.errstr = "!read_ok",
13100+
.result = REJECT,
13101+
},
1307413102
{
1307513103
"reference tracking in call: free reference in subprog and outside",
1307613104
.insns = {

0 commit comments

Comments
 (0)