bpf: Allow locking bpf_spin_lock global variables

Global variables reside in maps accessible using direct_value_addr
callbacks, so giving each load instruction's rewrite a unique reg->id
disallows us from holding locks which are global.

The reason for preserving reg->id as a unique value for registers that
may point to spin lock is that two separate lookups are treated as two
separate memory regions, and any possible aliasing is ignored for the
purposes of spin lock correctness.

This is not great especially for the global variable case, which are
served from maps that have max_entries == 1, i.e. they always lead to
map values pointing into the same map value.

So refactor the active_spin_lock into a 'active_lock' structure which
represents the lock identity, and instead of the reg->id, remember two
fields, a pointer and the reg->id. The pointer will store reg->map_ptr
or reg->btf. It's only necessary to distinguish for the id == 0 case of
global variables, but always setting the pointer to a non-NULL value and
using the pointer to check whether the lock is held simplifies code in
the verifier.

This is generic enough to allow it for global variables, map lookups,
and allocated objects at the same time.

Note that while whether a lock is held can be answered by just comparing
active_lock.ptr to NULL, to determine whether the register is pointing
to the same held lock requires comparing _both_ ptr and id.

Finally, as a result of this refactoring, pseudo load instructions are
not given a unique reg->id, as they are doing lookup for the same map
value (max_entries is never greater than 1).

Essentially, we consider that the tuple of (ptr, id) will always be
unique for any kind of argument to bpf_spin_{lock,unlock}.

Note that this can be extended in the future to also remember offset
used for locking, so that we can introduce multiple bpf_spin_lock fields
in the same allocation.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20221118015614.2013203-10-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Kumar Kartikeya Dwivedi 2022-11-18 07:25:59 +05:30 committed by Alexei Starovoitov
parent 4e814da0d5
commit d0d78c1df9
2 changed files with 43 additions and 14 deletions

View File

@ -323,7 +323,21 @@ struct bpf_verifier_state {
u32 branches; u32 branches;
u32 insn_idx; u32 insn_idx;
u32 curframe; u32 curframe;
u32 active_spin_lock; /* For every reg representing a map value or allocated object pointer,
* we consider the tuple of (ptr, id) for them to be unique in verifier
* context and conside them to not alias each other for the purposes of
* tracking lock state.
*/
struct {
/* This can either be reg->map_ptr or reg->btf. If ptr is NULL,
* there's no active lock held, and other fields have no
* meaning. If non-NULL, it indicates that a lock is held and
* id member has the reg->id of the register which can be >= 0.
*/
void *ptr;
/* This will be reg->id */
u32 id;
} active_lock;
bool speculative; bool speculative;
/* first and last insn idx of this verifier state */ /* first and last insn idx of this verifier state */

View File

@ -1221,7 +1221,8 @@ static int copy_verifier_state(struct bpf_verifier_state *dst_state,
} }
dst_state->speculative = src->speculative; dst_state->speculative = src->speculative;
dst_state->curframe = src->curframe; dst_state->curframe = src->curframe;
dst_state->active_spin_lock = src->active_spin_lock; dst_state->active_lock.ptr = src->active_lock.ptr;
dst_state->active_lock.id = src->active_lock.id;
dst_state->branches = src->branches; dst_state->branches = src->branches;
dst_state->parent = src->parent; dst_state->parent = src->parent;
dst_state->first_insn_idx = src->first_insn_idx; dst_state->first_insn_idx = src->first_insn_idx;
@ -5596,7 +5597,7 @@ int check_kfunc_mem_size_reg(struct bpf_verifier_env *env, struct bpf_reg_state
* Since only one bpf_spin_lock is allowed the checks are simpler than * Since only one bpf_spin_lock is allowed the checks are simpler than
* reg_is_refcounted() logic. The verifier needs to remember only * reg_is_refcounted() logic. The verifier needs to remember only
* one spin_lock instead of array of acquired_refs. * one spin_lock instead of array of acquired_refs.
* cur_state->active_spin_lock remembers which map value element or allocated * cur_state->active_lock remembers which map value element or allocated
* object got locked and clears it after bpf_spin_unlock. * object got locked and clears it after bpf_spin_unlock.
*/ */
static int process_spin_lock(struct bpf_verifier_env *env, int regno, static int process_spin_lock(struct bpf_verifier_env *env, int regno,
@ -5640,22 +5641,35 @@ static int process_spin_lock(struct bpf_verifier_env *env, int regno,
return -EINVAL; return -EINVAL;
} }
if (is_lock) { if (is_lock) {
if (cur->active_spin_lock) { if (cur->active_lock.ptr) {
verbose(env, verbose(env,
"Locking two bpf_spin_locks are not allowed\n"); "Locking two bpf_spin_locks are not allowed\n");
return -EINVAL; return -EINVAL;
} }
cur->active_spin_lock = reg->id; if (map)
cur->active_lock.ptr = map;
else
cur->active_lock.ptr = btf;
cur->active_lock.id = reg->id;
} else { } else {
if (!cur->active_spin_lock) { void *ptr;
if (map)
ptr = map;
else
ptr = btf;
if (!cur->active_lock.ptr) {
verbose(env, "bpf_spin_unlock without taking a lock\n"); verbose(env, "bpf_spin_unlock without taking a lock\n");
return -EINVAL; return -EINVAL;
} }
if (cur->active_spin_lock != reg->id) { if (cur->active_lock.ptr != ptr ||
cur->active_lock.id != reg->id) {
verbose(env, "bpf_spin_unlock of different lock\n"); verbose(env, "bpf_spin_unlock of different lock\n");
return -EINVAL; return -EINVAL;
} }
cur->active_spin_lock = 0; cur->active_lock.ptr = NULL;
cur->active_lock.id = 0;
} }
return 0; return 0;
} }
@ -10617,8 +10631,8 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)
insn->src_reg == BPF_PSEUDO_MAP_IDX_VALUE) { insn->src_reg == BPF_PSEUDO_MAP_IDX_VALUE) {
dst_reg->type = PTR_TO_MAP_VALUE; dst_reg->type = PTR_TO_MAP_VALUE;
dst_reg->off = aux->map_off; dst_reg->off = aux->map_off;
if (btf_record_has_field(map->record, BPF_SPIN_LOCK)) WARN_ON_ONCE(map->max_entries != 1);
dst_reg->id = ++env->id_gen; /* We want reg->id to be same (0) as map_value is not distinct */
} else if (insn->src_reg == BPF_PSEUDO_MAP_FD || } else if (insn->src_reg == BPF_PSEUDO_MAP_FD ||
insn->src_reg == BPF_PSEUDO_MAP_IDX) { insn->src_reg == BPF_PSEUDO_MAP_IDX) {
dst_reg->type = CONST_PTR_TO_MAP; dst_reg->type = CONST_PTR_TO_MAP;
@ -10696,7 +10710,7 @@ static int check_ld_abs(struct bpf_verifier_env *env, struct bpf_insn *insn)
return err; return err;
} }
if (env->cur_state->active_spin_lock) { if (env->cur_state->active_lock.ptr) {
verbose(env, "BPF_LD_[ABS|IND] cannot be used inside bpf_spin_lock-ed region\n"); verbose(env, "BPF_LD_[ABS|IND] cannot be used inside bpf_spin_lock-ed region\n");
return -EINVAL; return -EINVAL;
} }
@ -11962,7 +11976,8 @@ static bool states_equal(struct bpf_verifier_env *env,
if (old->speculative && !cur->speculative) if (old->speculative && !cur->speculative)
return false; return false;
if (old->active_spin_lock != cur->active_spin_lock) if (old->active_lock.ptr != cur->active_lock.ptr ||
old->active_lock.id != cur->active_lock.id)
return false; return false;
/* for states to be equal callsites have to be the same /* for states to be equal callsites have to be the same
@ -12607,7 +12622,7 @@ static int do_check(struct bpf_verifier_env *env)
return -EINVAL; return -EINVAL;
} }
if (env->cur_state->active_spin_lock && if (env->cur_state->active_lock.ptr &&
(insn->src_reg == BPF_PSEUDO_CALL || (insn->src_reg == BPF_PSEUDO_CALL ||
insn->imm != BPF_FUNC_spin_unlock)) { insn->imm != BPF_FUNC_spin_unlock)) {
verbose(env, "function calls are not allowed while holding a lock\n"); verbose(env, "function calls are not allowed while holding a lock\n");
@ -12644,7 +12659,7 @@ static int do_check(struct bpf_verifier_env *env)
return -EINVAL; return -EINVAL;
} }
if (env->cur_state->active_spin_lock) { if (env->cur_state->active_lock.ptr) {
verbose(env, "bpf_spin_unlock is missing\n"); verbose(env, "bpf_spin_unlock is missing\n");
return -EINVAL; return -EINVAL;
} }