Skip to content

Commit cc2b14d

Browse files
4astborkmann
authored andcommitted
bpf: teach verifier to recognize zero initialized stack
programs with function calls are often passing various pointers via stack. When all calls are inlined llvm flattens stack accesses and optimizes away extra branches. When functions are not inlined it becomes the job of the verifier to recognize zero initialized stack to avoid exploring paths that program will not take. The following program would fail otherwise: ptr = &buffer_on_stack; *ptr = 0; ... func_call(.., ptr, ...) { if (..) *ptr = bpf_map_lookup(); } ... if (*ptr != 0) { // Access (*ptr)->field is valid. // Without stack_zero tracking such (*ptr)->field access // will be rejected } since stack slots are no longer uniform invalid | spill | misc add liveness marking to all slots, but do it in 8 byte chunks. So if nothing was read or written in [fp-16, fp-9] range it will be marked as LIVE_NONE. If any byte in that range was read, it will be marked LIVE_READ and stacksafe() check will perform byte-by-byte verification. If all bytes in the range were written the slot will be marked as LIVE_WRITTEN. This significantly speeds up state equality comparison and reduces total number of states processed. before after bpf_lb-DLB_L3.o 2051 2003 bpf_lb-DLB_L4.o 3287 3164 bpf_lb-DUNKNOWN.o 1080 1080 bpf_lxc-DDROP_ALL.o 24980 12361 bpf_lxc-DUNKNOWN.o 34308 16605 bpf_netdev.o 15404 10962 bpf_overlay.o 7191 6679 Signed-off-by: Alexei Starovoitov <[email protected]> Acked-by: Daniel Borkmann <[email protected]> Signed-off-by: Daniel Borkmann <[email protected]>
1 parent a7ff3ec commit cc2b14d

File tree

2 files changed

+103
-29
lines changed

2 files changed

+103
-29
lines changed

include/linux/bpf_verifier.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ struct bpf_reg_state {
9191
enum bpf_stack_slot_type {
9292
STACK_INVALID, /* nothing was stored in this stack slot */
9393
STACK_SPILL, /* register spilled into stack */
94-
STACK_MISC /* BPF program wrote some data into this slot */
94+
STACK_MISC, /* BPF program wrote some data into this slot */
95+
STACK_ZERO, /* BPF program wrote constant zero */
9596
};
9697

9798
#define BPF_REG_SIZE 8 /* size of eBPF register in bytes */

kernel/bpf/verifier.c

Lines changed: 101 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,8 @@ static void print_verifier_state(struct bpf_verifier_env *env,
311311
verbose(env, "=%s",
312312
reg_type_str[state->stack[i].spilled_ptr.type]);
313313
}
314+
if (state->stack[i].slot_type[0] == STACK_ZERO)
315+
verbose(env, " fp%d=0", (-i - 1) * BPF_REG_SIZE);
314316
}
315317
verbose(env, "\n");
316318
}
@@ -522,6 +524,13 @@ static void __mark_reg_known_zero(struct bpf_reg_state *reg)
522524
__mark_reg_known(reg, 0);
523525
}
524526

527+
static void __mark_reg_const_zero(struct bpf_reg_state *reg)
528+
{
529+
__mark_reg_known(reg, 0);
530+
reg->off = 0;
531+
reg->type = SCALAR_VALUE;
532+
}
533+
525534
static void mark_reg_known_zero(struct bpf_verifier_env *env,
526535
struct bpf_reg_state *regs, u32 regno)
527536
{
@@ -937,6 +946,12 @@ static bool is_spillable_regtype(enum bpf_reg_type type)
937946
}
938947
}
939948

949+
/* Does this register contain a constant zero? */
950+
static bool register_is_null(struct bpf_reg_state *reg)
951+
{
952+
return reg->type == SCALAR_VALUE && tnum_equals_const(reg->var_off, 0);
953+
}
954+
940955
/* check_stack_read/write functions track spill/fill of registers,
941956
* stack boundary and alignment are checked in check_mem_access()
942957
*/
@@ -984,12 +999,30 @@ static int check_stack_write(struct bpf_verifier_env *env,
984999
for (i = 0; i < BPF_REG_SIZE; i++)
9851000
state->stack[spi].slot_type[i] = STACK_SPILL;
9861001
} else {
1002+
u8 type = STACK_MISC;
1003+
9871004
/* regular write of data into stack */
9881005
state->stack[spi].spilled_ptr = (struct bpf_reg_state) {};
9891006

1007+
/* only mark the slot as written if all 8 bytes were written
1008+
* otherwise read propagation may incorrectly stop too soon
1009+
* when stack slots are partially written.
1010+
* This heuristic means that read propagation will be
1011+
* conservative, since it will add reg_live_read marks
1012+
* to stack slots all the way to first state when programs
1013+
* writes+reads less than 8 bytes
1014+
*/
1015+
if (size == BPF_REG_SIZE)
1016+
state->stack[spi].spilled_ptr.live |= REG_LIVE_WRITTEN;
1017+
1018+
/* when we zero initialize stack slots mark them as such */
1019+
if (value_regno >= 0 &&
1020+
register_is_null(&cur->regs[value_regno]))
1021+
type = STACK_ZERO;
1022+
9901023
for (i = 0; i < size; i++)
9911024
state->stack[spi].slot_type[(slot - i) % BPF_REG_SIZE] =
992-
STACK_MISC;
1025+
type;
9931026
}
9941027
return 0;
9951028
}
@@ -1030,6 +1063,14 @@ static void mark_stack_slot_read(struct bpf_verifier_env *env,
10301063
bool writes = parent == state->parent; /* Observe write marks */
10311064

10321065
while (parent) {
1066+
if (parent->frame[frameno]->allocated_stack <= slot * BPF_REG_SIZE)
1067+
/* since LIVE_WRITTEN mark is only done for full 8-byte
1068+
* write the read marks are conservative and parent
1069+
* state may not even have the stack allocated. In such case
1070+
* end the propagation, since the loop reached beginning
1071+
* of the function
1072+
*/
1073+
break;
10331074
/* if read wasn't screened by an earlier write ... */
10341075
if (writes && state->frame[frameno]->stack[slot].spilled_ptr.live & REG_LIVE_WRITTEN)
10351076
break;
@@ -1077,21 +1118,38 @@ static int check_stack_read(struct bpf_verifier_env *env,
10771118
* which resets stack/reg liveness for state transitions
10781119
*/
10791120
state->regs[value_regno].live |= REG_LIVE_WRITTEN;
1080-
mark_stack_slot_read(env, vstate, vstate->parent, spi,
1081-
reg_state->frameno);
10821121
}
1122+
mark_stack_slot_read(env, vstate, vstate->parent, spi,
1123+
reg_state->frameno);
10831124
return 0;
10841125
} else {
1126+
int zeros = 0;
1127+
10851128
for (i = 0; i < size; i++) {
1086-
if (stype[(slot - i) % BPF_REG_SIZE] != STACK_MISC) {
1087-
verbose(env, "invalid read from stack off %d+%d size %d\n",
1088-
off, i, size);
1089-
return -EACCES;
1129+
if (stype[(slot - i) % BPF_REG_SIZE] == STACK_MISC)
1130+
continue;
1131+
if (stype[(slot - i) % BPF_REG_SIZE] == STACK_ZERO) {
1132+
zeros++;
1133+
continue;
10901134
}
1135+
verbose(env, "invalid read from stack off %d+%d size %d\n",
1136+
off, i, size);
1137+
return -EACCES;
1138+
}
1139+
mark_stack_slot_read(env, vstate, vstate->parent, spi,
1140+
reg_state->frameno);
1141+
if (value_regno >= 0) {
1142+
if (zeros == size) {
1143+
/* any size read into register is zero extended,
1144+
* so the whole register == const_zero
1145+
*/
1146+
__mark_reg_const_zero(&state->regs[value_regno]);
1147+
} else {
1148+
/* have read misc data from the stack */
1149+
mark_reg_unknown(env, state->regs, value_regno);
1150+
}
1151+
state->regs[value_regno].live |= REG_LIVE_WRITTEN;
10911152
}
1092-
if (value_regno >= 0)
1093-
/* have read misc data from the stack */
1094-
mark_reg_unknown(env, state->regs, value_regno);
10951153
return 0;
10961154
}
10971155
}
@@ -1578,12 +1636,6 @@ static int check_xadd(struct bpf_verifier_env *env, int insn_idx, struct bpf_ins
15781636
BPF_SIZE(insn->code), BPF_WRITE, -1);
15791637
}
15801638

1581-
/* Does this register contain a constant zero? */
1582-
static bool register_is_null(struct bpf_reg_state *reg)
1583-
{
1584-
return reg->type == SCALAR_VALUE && tnum_equals_const(reg->var_off, 0);
1585-
}
1586-
15871639
/* when register 'regno' is passed into function that will read 'access_size'
15881640
* bytes from that pointer, make sure that it's within stack boundary
15891641
* and all elements of stack are initialized.
@@ -1633,15 +1685,30 @@ static int check_stack_boundary(struct bpf_verifier_env *env, int regno,
16331685
}
16341686

16351687
for (i = 0; i < access_size; i++) {
1688+
u8 *stype;
1689+
16361690
slot = -(off + i) - 1;
16371691
spi = slot / BPF_REG_SIZE;
1638-
if (state->allocated_stack <= slot ||
1639-
state->stack[spi].slot_type[slot % BPF_REG_SIZE] !=
1640-
STACK_MISC) {
1641-
verbose(env, "invalid indirect read from stack off %d+%d size %d\n",
1642-
off, i, access_size);
1643-
return -EACCES;
1692+
if (state->allocated_stack <= slot)
1693+
goto err;
1694+
stype = &state->stack[spi].slot_type[slot % BPF_REG_SIZE];
1695+
if (*stype == STACK_MISC)
1696+
goto mark;
1697+
if (*stype == STACK_ZERO) {
1698+
/* helper can write anything into the stack */
1699+
*stype = STACK_MISC;
1700+
goto mark;
16441701
}
1702+
err:
1703+
verbose(env, "invalid indirect read from stack off %d+%d size %d\n",
1704+
off, i, access_size);
1705+
return -EACCES;
1706+
mark:
1707+
/* reading any byte out of 8-byte 'spill_slot' will cause
1708+
* the whole slot to be marked as 'read'
1709+
*/
1710+
mark_stack_slot_read(env, env->cur_state, env->cur_state->parent,
1711+
spi, state->frameno);
16451712
}
16461713
return update_stack_depth(env, state, off);
16471714
}
@@ -4022,8 +4089,19 @@ static bool stacksafe(struct bpf_func_state *old,
40224089
for (i = 0; i < old->allocated_stack; i++) {
40234090
spi = i / BPF_REG_SIZE;
40244091

4092+
if (!(old->stack[spi].spilled_ptr.live & REG_LIVE_READ))
4093+
/* explored state didn't use this */
4094+
return true;
4095+
40254096
if (old->stack[spi].slot_type[i % BPF_REG_SIZE] == STACK_INVALID)
40264097
continue;
4098+
/* if old state was safe with misc data in the stack
4099+
* it will be safe with zero-initialized stack.
4100+
* The opposite is not true
4101+
*/
4102+
if (old->stack[spi].slot_type[i % BPF_REG_SIZE] == STACK_MISC &&
4103+
cur->stack[spi].slot_type[i % BPF_REG_SIZE] == STACK_ZERO)
4104+
continue;
40274105
if (old->stack[spi].slot_type[i % BPF_REG_SIZE] !=
40284106
cur->stack[spi].slot_type[i % BPF_REG_SIZE])
40294107
/* Ex: old explored (safe) state has STACK_SPILL in
@@ -4164,10 +4242,6 @@ static int propagate_liveness(struct bpf_verifier_env *env,
41644242
parent = vparent->frame[frame];
41654243
for (i = 0; i < state->allocated_stack / BPF_REG_SIZE &&
41664244
i < parent->allocated_stack / BPF_REG_SIZE; i++) {
4167-
if (parent->stack[i].slot_type[0] != STACK_SPILL)
4168-
continue;
4169-
if (state->stack[i].slot_type[0] != STACK_SPILL)
4170-
continue;
41714245
if (parent->stack[i].spilled_ptr.live & REG_LIVE_READ)
41724246
continue;
41734247
if (state->stack[i].spilled_ptr.live & REG_LIVE_READ)
@@ -4247,8 +4321,7 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx)
42474321
struct bpf_func_state *frame = cur->frame[j];
42484322

42494323
for (i = 0; i < frame->allocated_stack / BPF_REG_SIZE; i++)
4250-
if (frame->stack[i].slot_type[0] == STACK_SPILL)
4251-
frame->stack[i].spilled_ptr.live = REG_LIVE_NONE;
4324+
frame->stack[i].spilled_ptr.live = REG_LIVE_NONE;
42524325
}
42534326
return 0;
42544327
}

0 commit comments

Comments
 (0)