Skip to content

Commit 04180cf

Browse files
puranjaymohanKernel Patches Daemon
authored andcommitted
bpf: support nested rcu critical sections
Currently, nested rcu critical sections are rejected by the verifier and rcu_lock state is managed by a boolean variable. Add support for nested rcu critical sections by make active_rcu_locks a counter similar to active_preempt_locks. bpf_rcu_read_lock() increments this counter and bpf_rcu_read_unlock() decrements it, MEM_RCU -> PTR_UNTRUSTED transition happens when active_rcu_locks drops to 0. Signed-off-by: Puranjay Mohan <[email protected]>
1 parent 4a6b8b7 commit 04180cf

File tree

3 files changed

+24
-27
lines changed

3 files changed

+24
-27
lines changed

include/linux/bpf_verifier.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ struct bpf_verifier_state {
416416
u32 active_irq_id;
417417
u32 active_lock_id;
418418
void *active_lock_ptr;
419-
bool active_rcu_lock;
419+
u32 active_rcu_locks;
420420

421421
bool speculative;
422422
bool in_sleepable;

kernel/bpf/verifier.c

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,7 +1437,7 @@ static int copy_reference_state(struct bpf_verifier_state *dst, const struct bpf
14371437
dst->acquired_refs = src->acquired_refs;
14381438
dst->active_locks = src->active_locks;
14391439
dst->active_preempt_locks = src->active_preempt_locks;
1440-
dst->active_rcu_lock = src->active_rcu_lock;
1440+
dst->active_rcu_locks = src->active_rcu_locks;
14411441
dst->active_irq_id = src->active_irq_id;
14421442
dst->active_lock_id = src->active_lock_id;
14431443
dst->active_lock_ptr = src->active_lock_ptr;
@@ -5880,7 +5880,7 @@ static bool in_sleepable(struct bpf_verifier_env *env)
58805880
*/
58815881
static bool in_rcu_cs(struct bpf_verifier_env *env)
58825882
{
5883-
return env->cur_state->active_rcu_lock ||
5883+
return env->cur_state->active_rcu_locks ||
58845884
env->cur_state->active_locks ||
58855885
!in_sleepable(env);
58865886
}
@@ -10735,7 +10735,7 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
1073510735
}
1073610736

1073710737
if (env->subprog_info[subprog].might_sleep &&
10738-
(env->cur_state->active_rcu_lock || env->cur_state->active_preempt_locks ||
10738+
(env->cur_state->active_rcu_locks || env->cur_state->active_preempt_locks ||
1073910739
env->cur_state->active_irq_id || !in_sleepable(env))) {
1074010740
verbose(env, "global functions that may sleep are not allowed in non-sleepable context,\n"
1074110741
"i.e., in a RCU/IRQ/preempt-disabled section, or in\n"
@@ -11314,7 +11314,7 @@ static int check_resource_leak(struct bpf_verifier_env *env, bool exception_exit
1131411314
return -EINVAL;
1131511315
}
1131611316

11317-
if (check_lock && env->cur_state->active_rcu_lock) {
11317+
if (check_lock && env->cur_state->active_rcu_locks) {
1131811318
verbose(env, "%s cannot be used inside bpf_rcu_read_lock-ed region\n", prefix);
1131911319
return -EINVAL;
1132011320
}
@@ -11452,7 +11452,7 @@ static int get_helper_proto(struct bpf_verifier_env *env, int func_id,
1145211452
/* Check if we're in a sleepable context. */
1145311453
static inline bool in_sleepable_context(struct bpf_verifier_env *env)
1145411454
{
11455-
return !env->cur_state->active_rcu_lock &&
11455+
return !env->cur_state->active_rcu_locks &&
1145611456
!env->cur_state->active_preempt_locks &&
1145711457
!env->cur_state->active_irq_id &&
1145811458
in_sleepable(env);
@@ -11518,7 +11518,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
1151811518
return err;
1151911519
}
1152011520

11521-
if (env->cur_state->active_rcu_lock) {
11521+
if (env->cur_state->active_rcu_locks) {
1152211522
if (fn->might_sleep) {
1152311523
verbose(env, "sleepable helper %s#%d in rcu_read_lock region\n",
1152411524
func_id_name(func_id), func_id);
@@ -14006,36 +14006,33 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
1400614006
preempt_disable = is_kfunc_bpf_preempt_disable(&meta);
1400714007
preempt_enable = is_kfunc_bpf_preempt_enable(&meta);
1400814008

14009-
if (env->cur_state->active_rcu_lock) {
14009+
if (rcu_lock) {
14010+
env->cur_state->active_rcu_locks++;
14011+
} else if (rcu_unlock) {
1401014012
struct bpf_func_state *state;
1401114013
struct bpf_reg_state *reg;
1401214014
u32 clear_mask = (1 << STACK_SPILL) | (1 << STACK_ITER);
1401314015

14014-
if (in_rbtree_lock_required_cb(env) && (rcu_lock || rcu_unlock)) {
14015-
verbose(env, "Calling bpf_rcu_read_{lock,unlock} in unnecessary rbtree callback\n");
14016-
return -EACCES;
14017-
}
14018-
14019-
if (rcu_lock) {
14020-
verbose(env, "nested rcu read lock (kernel function %s)\n", func_name);
14016+
if (env->cur_state->active_rcu_locks == 0) {
14017+
verbose(env, "unmatched rcu read unlock (kernel function %s)\n", func_name);
1402114018
return -EINVAL;
14022-
} else if (rcu_unlock) {
14019+
}
14020+
if (--env->cur_state->active_rcu_locks == 0) {
1402314021
bpf_for_each_reg_in_vstate_mask(env->cur_state, state, reg, clear_mask, ({
1402414022
if (reg->type & MEM_RCU) {
1402514023
reg->type &= ~(MEM_RCU | PTR_MAYBE_NULL);
1402614024
reg->type |= PTR_UNTRUSTED;
1402714025
}
1402814026
}));
14029-
env->cur_state->active_rcu_lock = false;
14030-
} else if (sleepable) {
14031-
verbose(env, "kernel func %s is sleepable within rcu_read_lock region\n", func_name);
14032-
return -EACCES;
1403314027
}
14034-
} else if (rcu_lock) {
14035-
env->cur_state->active_rcu_lock = true;
14036-
} else if (rcu_unlock) {
14037-
verbose(env, "unmatched rcu read unlock (kernel function %s)\n", func_name);
14038-
return -EINVAL;
14028+
} else if (sleepable && env->cur_state->active_rcu_locks) {
14029+
verbose(env, "kernel func %s is sleepable within rcu_read_lock region\n", func_name);
14030+
return -EACCES;
14031+
}
14032+
14033+
if (in_rbtree_lock_required_cb(env) && (rcu_lock || rcu_unlock)) {
14034+
verbose(env, "Calling bpf_rcu_read_{lock,unlock} in unnecessary rbtree callback\n");
14035+
return -EACCES;
1403914036
}
1404014037

1404114038
if (env->cur_state->active_preempt_locks) {
@@ -19328,7 +19325,7 @@ static bool refsafe(struct bpf_verifier_state *old, struct bpf_verifier_state *c
1932819325
if (old->active_preempt_locks != cur->active_preempt_locks)
1932919326
return false;
1933019327

19331-
if (old->active_rcu_lock != cur->active_rcu_lock)
19328+
if (old->active_rcu_locks != cur->active_rcu_locks)
1933219329
return false;
1933319330

1933419331
if (!check_ids(old->active_irq_id, cur->active_irq_id, idmap))

tools/testing/selftests/bpf/prog_tests/rcu_read_lock.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ static void test_success(void)
2828
bpf_program__set_autoload(skel->progs.two_regions, true);
2929
bpf_program__set_autoload(skel->progs.non_sleepable_1, true);
3030
bpf_program__set_autoload(skel->progs.non_sleepable_2, true);
31+
bpf_program__set_autoload(skel->progs.nested_rcu_region, true);
3132
bpf_program__set_autoload(skel->progs.task_trusted_non_rcuptr, true);
3233
bpf_program__set_autoload(skel->progs.rcu_read_lock_subprog, true);
3334
bpf_program__set_autoload(skel->progs.rcu_read_lock_global_subprog, true);
@@ -78,7 +79,6 @@ static const char * const inproper_region_tests[] = {
7879
"non_sleepable_rcu_mismatch",
7980
"inproper_sleepable_helper",
8081
"inproper_sleepable_kfunc",
81-
"nested_rcu_region",
8282
"rcu_read_lock_global_subprog_lock",
8383
"rcu_read_lock_global_subprog_unlock",
8484
"rcu_read_lock_sleepable_helper_global_subprog",

0 commit comments

Comments
 (0)