mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-13 23:29:10 -04:00
bpf: Migrate release_on_unlock logic to non-owning ref semantics
This patch introduces non-owning reference semantics to the verifier,
specifically linked_list API kfunc handling. release_on_unlock logic for
refs is refactored - with small functional changes - to implement these
semantics, and bpf_list_push_{front,back} are migrated to use them.
When a list node is pushed to a list, the program still has a pointer to
the node:
n = bpf_obj_new(typeof(*n));
bpf_spin_lock(&l);
bpf_list_push_back(&l, n);
/* n still points to the just-added node */
bpf_spin_unlock(&l);
What the verifier considers n to be after the push, and thus what can be
done with n, are changed by this patch.
Common properties both before/after this patch:
* After push, n is only a valid reference to the node until end of
critical section
* After push, n cannot be pushed to any list
* After push, the program can read the node's fields using n
Before:
* After push, n retains the ref_obj_id which it received on
bpf_obj_new, but the associated bpf_reference_state's
release_on_unlock field is set to true
* release_on_unlock field and associated logic is used to implement
"n is only a valid ref until end of critical section"
* After push, n cannot be written to, the node must be removed from
the list before writing to its fields
* After push, n is marked PTR_UNTRUSTED
After:
* After push, n's ref is released and ref_obj_id set to 0. NON_OWN_REF
type flag is added to reg's type, indicating that it's a non-owning
reference.
* NON_OWN_REF flag and logic is used to implement "n is only a
valid ref until end of critical section"
* n can be written to (except for special fields e.g. bpf_list_node,
timer, ...)
Summary of specific implementation changes to achieve the above:
* release_on_unlock field, ref_set_release_on_unlock helper, and logic
to "release on unlock" based on that field are removed
* The anonymous active_lock struct used by bpf_verifier_state is
pulled out into a named struct bpf_active_lock.
* NON_OWN_REF type flag is introduced along with verifier logic
changes to handle non-owning refs
* Helpers are added to use NON_OWN_REF flag to implement non-owning
ref semantics as described above
* invalidate_non_owning_refs - helper to clobber all non-owning refs
matching a particular bpf_active_lock identity. Replaces
release_on_unlock logic in process_spin_lock.
* ref_set_non_owning - set NON_OWN_REF type flag after doing some
sanity checking
* ref_convert_owning_non_owning - convert owning reference w/
specified ref_obj_id to non-owning references. Set NON_OWN_REF
flag for each reg with that ref_obj_id and 0-out its ref_obj_id
* Update linked_list selftests to account for minor semantic
differences introduced by this patch
* Writes to a release_on_unlock node ref are not allowed, while
writes to non-owning reference pointees are. As a result the
linked_list "write after push" failure tests are no longer scenarios
that should fail.
* The test##missing_lock##op and test##incorrect_lock##op
macro-generated failure tests need to have a valid node argument in
order to have the same error output as before. Otherwise
verification will fail early and the expected error output won't be seen.
Signed-off-by: Dave Marchevsky <davemarchevsky@fb.com>
Link: https://lore.kernel.org/r/20230212092715.1422619-2-davemarchevsky@fb.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
committed by
Alexei Starovoitov
parent
39c536acc3
commit
6a3cd3318f
@@ -181,6 +181,7 @@ enum btf_field_type {
|
||||
BPF_KPTR = BPF_KPTR_UNREF | BPF_KPTR_REF,
|
||||
BPF_LIST_HEAD = (1 << 4),
|
||||
BPF_LIST_NODE = (1 << 5),
|
||||
BPF_GRAPH_NODE_OR_ROOT = BPF_LIST_NODE | BPF_LIST_HEAD,
|
||||
};
|
||||
|
||||
struct btf_field_kptr {
|
||||
@@ -576,6 +577,11 @@ enum bpf_type_flag {
|
||||
/* MEM is tagged with rcu and memory access needs rcu_read_lock protection. */
|
||||
MEM_RCU = BIT(13 + BPF_BASE_TYPE_BITS),
|
||||
|
||||
/* Used to tag PTR_TO_BTF_ID | MEM_ALLOC references which are non-owning.
|
||||
* Currently only valid for linked-list and rbtree nodes.
|
||||
*/
|
||||
NON_OWN_REF = BIT(14 + BPF_BASE_TYPE_BITS),
|
||||
|
||||
__BPF_TYPE_FLAG_MAX,
|
||||
__BPF_TYPE_LAST_FLAG = __BPF_TYPE_FLAG_MAX - 1,
|
||||
};
|
||||
|
||||
@@ -43,6 +43,22 @@ enum bpf_reg_liveness {
|
||||
REG_LIVE_DONE = 0x8, /* liveness won't be updating this register anymore */
|
||||
};
|
||||
|
||||
/* 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 bpf_active_lock {
|
||||
/* 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;
|
||||
};
|
||||
|
||||
struct bpf_reg_state {
|
||||
/* Ordering of fields matters. See states_equal() */
|
||||
enum bpf_reg_type type;
|
||||
@@ -226,11 +242,6 @@ struct bpf_reference_state {
|
||||
* exiting a callback function.
|
||||
*/
|
||||
int callback_ref;
|
||||
/* Mark the reference state to release the registers sharing the same id
|
||||
* on bpf_spin_unlock (for nodes that we will lose ownership to but are
|
||||
* safe to access inside the critical section).
|
||||
*/
|
||||
bool release_on_unlock;
|
||||
};
|
||||
|
||||
/* state of the program:
|
||||
@@ -331,21 +342,8 @@ struct bpf_verifier_state {
|
||||
u32 branches;
|
||||
u32 insn_idx;
|
||||
u32 curframe;
|
||||
/* 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;
|
||||
|
||||
struct bpf_active_lock active_lock;
|
||||
bool speculative;
|
||||
bool active_rcu_lock;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user