|
@@ -832,11 +832,6 @@ static int check_map_access(struct bpf_verifier_env *env, u32 regno,
|
|
*/
|
|
*/
|
|
if (log_level)
|
|
if (log_level)
|
|
print_verifier_state(state);
|
|
print_verifier_state(state);
|
|
- /* If the offset is variable, we will need to be stricter in state
|
|
|
|
- * pruning from now on.
|
|
|
|
- */
|
|
|
|
- if (!tnum_is_const(reg->var_off))
|
|
|
|
- env->varlen_map_value_access = true;
|
|
|
|
/* The minimum value is only important with signed
|
|
/* The minimum value is only important with signed
|
|
* comparisons where we can't assume the floor of a
|
|
* comparisons where we can't assume the floor of a
|
|
* value is 0. If we are using signed variables for our
|
|
* value is 0. If we are using signed variables for our
|
|
@@ -3247,9 +3242,8 @@ static bool check_ids(u32 old_id, u32 cur_id, struct idpair *idmap)
|
|
}
|
|
}
|
|
|
|
|
|
/* Returns true if (rold safe implies rcur safe) */
|
|
/* Returns true if (rold safe implies rcur safe) */
|
|
-static bool regsafe(struct bpf_reg_state *rold,
|
|
|
|
- struct bpf_reg_state *rcur,
|
|
|
|
- bool varlen_map_access, struct idpair *idmap)
|
|
|
|
|
|
+static bool regsafe(struct bpf_reg_state *rold, struct bpf_reg_state *rcur,
|
|
|
|
+ struct idpair *idmap)
|
|
{
|
|
{
|
|
if (!(rold->live & REG_LIVE_READ))
|
|
if (!(rold->live & REG_LIVE_READ))
|
|
/* explored state didn't use this */
|
|
/* explored state didn't use this */
|
|
@@ -3281,22 +3275,14 @@ static bool regsafe(struct bpf_reg_state *rold,
|
|
tnum_is_unknown(rold->var_off);
|
|
tnum_is_unknown(rold->var_off);
|
|
}
|
|
}
|
|
case PTR_TO_MAP_VALUE:
|
|
case PTR_TO_MAP_VALUE:
|
|
- if (varlen_map_access) {
|
|
|
|
- /* If the new min/max/var_off satisfy the old ones and
|
|
|
|
- * everything else matches, we are OK.
|
|
|
|
- * We don't care about the 'id' value, because nothing
|
|
|
|
- * uses it for PTR_TO_MAP_VALUE (only for ..._OR_NULL)
|
|
|
|
- */
|
|
|
|
- return memcmp(rold, rcur, offsetof(struct bpf_reg_state, id)) == 0 &&
|
|
|
|
- range_within(rold, rcur) &&
|
|
|
|
- tnum_in(rold->var_off, rcur->var_off);
|
|
|
|
- } else {
|
|
|
|
- /* If the ranges/var_off were not the same, but
|
|
|
|
- * everything else was and we didn't do a variable
|
|
|
|
- * access into a map then we are a-ok.
|
|
|
|
- */
|
|
|
|
- return memcmp(rold, rcur, offsetof(struct bpf_reg_state, id)) == 0;
|
|
|
|
- }
|
|
|
|
|
|
+ /* If the new min/max/var_off satisfy the old ones and
|
|
|
|
+ * everything else matches, we are OK.
|
|
|
|
+ * We don't care about the 'id' value, because nothing
|
|
|
|
+ * uses it for PTR_TO_MAP_VALUE (only for ..._OR_NULL)
|
|
|
|
+ */
|
|
|
|
+ return memcmp(rold, rcur, offsetof(struct bpf_reg_state, id)) == 0 &&
|
|
|
|
+ range_within(rold, rcur) &&
|
|
|
|
+ tnum_in(rold->var_off, rcur->var_off);
|
|
case PTR_TO_MAP_VALUE_OR_NULL:
|
|
case PTR_TO_MAP_VALUE_OR_NULL:
|
|
/* a PTR_TO_MAP_VALUE could be safe to use as a
|
|
/* a PTR_TO_MAP_VALUE could be safe to use as a
|
|
* PTR_TO_MAP_VALUE_OR_NULL into the same map.
|
|
* PTR_TO_MAP_VALUE_OR_NULL into the same map.
|
|
@@ -3380,7 +3366,6 @@ static bool states_equal(struct bpf_verifier_env *env,
|
|
struct bpf_verifier_state *old,
|
|
struct bpf_verifier_state *old,
|
|
struct bpf_verifier_state *cur)
|
|
struct bpf_verifier_state *cur)
|
|
{
|
|
{
|
|
- bool varlen_map_access = env->varlen_map_value_access;
|
|
|
|
struct idpair *idmap;
|
|
struct idpair *idmap;
|
|
bool ret = false;
|
|
bool ret = false;
|
|
int i;
|
|
int i;
|
|
@@ -3391,8 +3376,7 @@ static bool states_equal(struct bpf_verifier_env *env,
|
|
return false;
|
|
return false;
|
|
|
|
|
|
for (i = 0; i < MAX_BPF_REG; i++) {
|
|
for (i = 0; i < MAX_BPF_REG; i++) {
|
|
- if (!regsafe(&old->regs[i], &cur->regs[i], varlen_map_access,
|
|
|
|
- idmap))
|
|
|
|
|
|
+ if (!regsafe(&old->regs[i], &cur->regs[i], idmap))
|
|
goto out_free;
|
|
goto out_free;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -3412,7 +3396,7 @@ static bool states_equal(struct bpf_verifier_env *env,
|
|
continue;
|
|
continue;
|
|
if (!regsafe(&old->spilled_regs[i / BPF_REG_SIZE],
|
|
if (!regsafe(&old->spilled_regs[i / BPF_REG_SIZE],
|
|
&cur->spilled_regs[i / BPF_REG_SIZE],
|
|
&cur->spilled_regs[i / BPF_REG_SIZE],
|
|
- varlen_map_access, idmap))
|
|
|
|
|
|
+ idmap))
|
|
/* when explored and current stack slot are both storing
|
|
/* when explored and current stack slot are both storing
|
|
* spilled registers, check that stored pointers types
|
|
* spilled registers, check that stored pointers types
|
|
* are the same as well.
|
|
* are the same as well.
|
|
@@ -3433,9 +3417,16 @@ out_free:
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/* A write screens off any subsequent reads; but write marks come from the
|
|
|
|
+ * straight-line code between a state and its parent. When we arrive at a
|
|
|
|
+ * jump target (in the first iteration of the propagate_liveness() loop),
|
|
|
|
+ * we didn't arrive by the straight-line code, so read marks in state must
|
|
|
|
+ * propagate to parent regardless of state's write marks.
|
|
|
|
+ */
|
|
static bool do_propagate_liveness(const struct bpf_verifier_state *state,
|
|
static bool do_propagate_liveness(const struct bpf_verifier_state *state,
|
|
struct bpf_verifier_state *parent)
|
|
struct bpf_verifier_state *parent)
|
|
{
|
|
{
|
|
|
|
+ bool writes = parent == state->parent; /* Observe write marks */
|
|
bool touched = false; /* any changes made? */
|
|
bool touched = false; /* any changes made? */
|
|
int i;
|
|
int i;
|
|
|
|
|
|
@@ -3447,7 +3438,9 @@ static bool do_propagate_liveness(const struct bpf_verifier_state *state,
|
|
for (i = 0; i < BPF_REG_FP; i++) {
|
|
for (i = 0; i < BPF_REG_FP; i++) {
|
|
if (parent->regs[i].live & REG_LIVE_READ)
|
|
if (parent->regs[i].live & REG_LIVE_READ)
|
|
continue;
|
|
continue;
|
|
- if (state->regs[i].live == REG_LIVE_READ) {
|
|
|
|
|
|
+ if (writes && (state->regs[i].live & REG_LIVE_WRITTEN))
|
|
|
|
+ continue;
|
|
|
|
+ if (state->regs[i].live & REG_LIVE_READ) {
|
|
parent->regs[i].live |= REG_LIVE_READ;
|
|
parent->regs[i].live |= REG_LIVE_READ;
|
|
touched = true;
|
|
touched = true;
|
|
}
|
|
}
|
|
@@ -3460,7 +3453,9 @@ static bool do_propagate_liveness(const struct bpf_verifier_state *state,
|
|
continue;
|
|
continue;
|
|
if (parent->spilled_regs[i].live & REG_LIVE_READ)
|
|
if (parent->spilled_regs[i].live & REG_LIVE_READ)
|
|
continue;
|
|
continue;
|
|
- if (state->spilled_regs[i].live == REG_LIVE_READ) {
|
|
|
|
|
|
+ if (writes && (state->spilled_regs[i].live & REG_LIVE_WRITTEN))
|
|
|
|
+ continue;
|
|
|
|
+ if (state->spilled_regs[i].live & REG_LIVE_READ) {
|
|
parent->spilled_regs[i].live |= REG_LIVE_READ;
|
|
parent->spilled_regs[i].live |= REG_LIVE_READ;
|
|
touched = true;
|
|
touched = true;
|
|
}
|
|
}
|
|
@@ -3468,6 +3463,15 @@ static bool do_propagate_liveness(const struct bpf_verifier_state *state,
|
|
return touched;
|
|
return touched;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/* "parent" is "a state from which we reach the current state", but initially
|
|
|
|
+ * it is not the state->parent (i.e. "the state whose straight-line code leads
|
|
|
|
+ * to the current state"), instead it is the state that happened to arrive at
|
|
|
|
+ * a (prunable) equivalent of the current state. See comment above
|
|
|
|
+ * do_propagate_liveness() for consequences of this.
|
|
|
|
+ * This function is just a more efficient way of calling mark_reg_read() or
|
|
|
|
+ * mark_stack_slot_read() on each reg in "parent" that is read in "state",
|
|
|
|
+ * though it requires that parent != state->parent in the call arguments.
|
|
|
|
+ */
|
|
static void propagate_liveness(const struct bpf_verifier_state *state,
|
|
static void propagate_liveness(const struct bpf_verifier_state *state,
|
|
struct bpf_verifier_state *parent)
|
|
struct bpf_verifier_state *parent)
|
|
{
|
|
{
|
|
@@ -3496,6 +3500,12 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx)
|
|
/* reached equivalent register/stack state,
|
|
/* reached equivalent register/stack state,
|
|
* prune the search.
|
|
* prune the search.
|
|
* Registers read by the continuation are read by us.
|
|
* Registers read by the continuation are read by us.
|
|
|
|
+ * If we have any write marks in env->cur_state, they
|
|
|
|
+ * will prevent corresponding reads in the continuation
|
|
|
|
+ * from reaching our parent (an explored_state). Our
|
|
|
|
+ * own state will get the read marks recorded, but
|
|
|
|
+ * they'll be immediately forgotten as we're pruning
|
|
|
|
+ * this state and will pop a new one.
|
|
*/
|
|
*/
|
|
propagate_liveness(&sl->state, &env->cur_state);
|
|
propagate_liveness(&sl->state, &env->cur_state);
|
|
return 1;
|
|
return 1;
|
|
@@ -3519,7 +3529,12 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx)
|
|
env->explored_states[insn_idx] = new_sl;
|
|
env->explored_states[insn_idx] = new_sl;
|
|
/* connect new state to parentage chain */
|
|
/* connect new state to parentage chain */
|
|
env->cur_state.parent = &new_sl->state;
|
|
env->cur_state.parent = &new_sl->state;
|
|
- /* clear liveness marks in current state */
|
|
|
|
|
|
+ /* clear write marks in current state: the writes we did are not writes
|
|
|
|
+ * our child did, so they don't screen off its reads from us.
|
|
|
|
+ * (There are no read marks in current state, because reads always mark
|
|
|
|
+ * their parent and current state never has children yet. Only
|
|
|
|
+ * explored_states can get read marks.)
|
|
|
|
+ */
|
|
for (i = 0; i < BPF_REG_FP; i++)
|
|
for (i = 0; i < BPF_REG_FP; i++)
|
|
env->cur_state.regs[i].live = REG_LIVE_NONE;
|
|
env->cur_state.regs[i].live = REG_LIVE_NONE;
|
|
for (i = 0; i < MAX_BPF_STACK / BPF_REG_SIZE; i++)
|
|
for (i = 0; i < MAX_BPF_STACK / BPF_REG_SIZE; i++)
|
|
@@ -3550,7 +3565,6 @@ static int do_check(struct bpf_verifier_env *env)
|
|
init_reg_state(regs);
|
|
init_reg_state(regs);
|
|
state->parent = NULL;
|
|
state->parent = NULL;
|
|
insn_idx = 0;
|
|
insn_idx = 0;
|
|
- env->varlen_map_value_access = false;
|
|
|
|
for (;;) {
|
|
for (;;) {
|
|
struct bpf_insn *insn;
|
|
struct bpf_insn *insn;
|
|
u8 class;
|
|
u8 class;
|