summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexei Starovoitov <ast@kernel.org>2026-09-04 18:17:31 -0700
committerAlexei Starovoitov <ast@kernel.org>2026-09-04 18:17:31 -0700
commitb75a000f2ac15f4778ddd6d9298d60b24ad776fa (patch)
tree816a0b2b050b0d983e9406afe751efd10f903582
parent519f858317afc52e986817ad63fd68172dd38b6c (diff)
parentcf2475616b11c0efefdd969d42913f56ca39f918 (diff)
downloadlinux-stable-b75a000f2ac15f4778ddd6d9298d60b24ad776fa.tar.gz
linux-stable-b75a000f2ac15f4778ddd6d9298d60b24ad776fa.zip
Merge branch 'bpf-add-missing-precision-propagation-after-bpf_register_is_null-calls'
Eduard Zingerman says: ==================== bpf: add missing precision propagation after bpf_register_is_null calls Fix [1] uncovered a host of locations where the call to bpf_register_is_null() is not followed by a call to bpf_mark_chain_precision(). check_map_kptr_access() is omitted as it is handled [2] by another series. [1] https://lore.kernel.org/bpf/20260904083325.2083493-7-eddyz87@gmail.com/ [2] https://lore.kernel.org/bpf/20260904104203.345917-6-memxor@gmail.com/ --- ==================== Link: https://patch.msgid.link/20260904-register-is-null-precise-fixes-v1-0-0f5a360ff15d@gmail.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
-rw-r--r--kernel/bpf/verifier.c78
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_cgroup_storage.c29
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c24
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_precision.c100
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_subprog_precision.c12
5 files changed, 214 insertions, 29 deletions
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index c8699a8831df..1c3039f3fc32 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -4246,6 +4246,15 @@ static int mark_stack_arg_precision(struct bpf_verifier_env *env, int arg_idx)
return mark_chain_precision_batch(env, env->cur_state);
}
+static int mark_arg_precision(struct bpf_verifier_env *env, argno_t argno)
+{
+ int regno = reg_from_argno(argno);
+
+ if (regno >= 0)
+ return mark_chain_precision(env, regno);
+ return mark_stack_arg_precision(env, arg_idx_from_argno(argno));
+}
+
static int check_outgoing_stack_args(struct bpf_verifier_env *env, struct bpf_func_state *caller,
int nargs, const char *callee_name, const struct btf *btf,
const struct btf_param *args)
@@ -7151,14 +7160,8 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,
if (err && failure)
*failure = BPF_MEM_SIZE_FAIL_MEMORY;
- if (!err) {
- int regno = reg_from_argno(size_argno);
-
- if (regno >= 0)
- err = mark_chain_precision(env, regno);
- else
- err = mark_stack_arg_precision(env, arg_idx_from_argno(size_argno));
- }
+ if (!err)
+ err = mark_arg_precision(env, size_argno);
return err;
@@ -7175,7 +7178,7 @@ static int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg
int size, err = 0;
if (bpf_register_is_null(reg))
- return 0;
+ return mark_arg_precision(env, argno);
if (known_memory)
*known_memory = true;
@@ -8759,11 +8762,15 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
return err;
}
- if (bpf_register_is_null(reg) && type_may_be_null(arg_type))
+ if (bpf_register_is_null(reg) && type_may_be_null(arg_type)) {
/* A NULL register has a SCALAR_VALUE type, so skip
* type checking.
*/
+ err = mark_chain_precision(env, regno);
+ if (err)
+ return err;
goto skip_type_check;
+ }
/* arg_btf_id and arg_size are in a union. */
if (base_type(arg_type) == ARG_PTR_TO_BTF_ID ||
@@ -9761,8 +9768,12 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
struct bpf_call_arg_meta meta;
int err;
- if (bpf_register_is_null(reg) && type_may_be_null(arg->arg_type))
+ if (bpf_register_is_null(reg) && type_may_be_null(arg->arg_type)) {
+ err = mark_arg_precision(env, argno);
+ if (err)
+ return err;
continue;
+ }
memset(&meta, 0, sizeof(meta)); /* leave func_id as zero */
err = check_reg_type(env, reg, argno, arg->arg_type, &arg->btf_id, &meta,
@@ -10685,33 +10696,45 @@ static struct bpf_insn_aux_data *cur_aux(const struct bpf_verifier_env *env)
return &env->insn_aux_data[env->insn_idx];
}
-static bool loop_flag_is_zero(struct bpf_verifier_env *env)
+/* Returns 1 if R4 is a known zero, 0 if it is not, a negative errno on error. */
+static int loop_flag_is_zero(struct bpf_verifier_env *env)
{
struct bpf_reg_state *reg = reg_state(env, BPF_REG_4);
- bool reg_is_null = bpf_register_is_null(reg);
+ int err;
- if (reg_is_null)
- mark_chain_precision(env, BPF_REG_4);
+ if (!bpf_register_is_null(reg))
+ return 0;
- return reg_is_null;
+ err = mark_chain_precision(env, BPF_REG_4);
+ if (err)
+ return err;
+ return 1;
}
-static void update_loop_inline_state(struct bpf_verifier_env *env, u32 subprogno)
+static int update_loop_inline_state(struct bpf_verifier_env *env, u32 subprogno)
{
struct bpf_loop_inline_state *state = &cur_aux(env)->loop_inline_state;
+ int flag_is_zero;
if (!state->initialized) {
+ flag_is_zero = loop_flag_is_zero(env);
+ if (flag_is_zero < 0)
+ return flag_is_zero;
state->initialized = 1;
- state->fit_for_inline = loop_flag_is_zero(env);
+ state->fit_for_inline = flag_is_zero;
state->callback_subprogno = subprogno;
- return;
+ return 0;
}
if (!state->fit_for_inline)
- return;
+ return 0;
- state->fit_for_inline = (loop_flag_is_zero(env) &&
+ flag_is_zero = loop_flag_is_zero(env);
+ if (flag_is_zero < 0)
+ return flag_is_zero;
+ state->fit_for_inline = (flag_is_zero &&
state->callback_subprogno == subprogno);
+ return 0;
}
/* Returns whether or not the given map can potentially elide
@@ -10923,6 +10946,9 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
verbose(env, "get_local_storage() doesn't support non-zero flags\n");
return -EINVAL;
}
+ err = mark_chain_precision(env, BPF_REG_2);
+ if (err)
+ return err;
break;
case BPF_FUNC_for_each_map_elem:
err = push_callback_call(env, insn, insn_idx, meta.subprogno,
@@ -10940,7 +10966,9 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
err = check_bpf_snprintf_call(env, regs);
break;
case BPF_FUNC_loop:
- update_loop_inline_state(env, meta.subprogno);
+ err = update_loop_inline_state(env, meta.subprogno);
+ if (err)
+ return err;
/* Verifier relies on R1 value to determine if bpf_loop() iteration
* is finished, thus mark it precise.
*/
@@ -12717,8 +12745,12 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
if (reg_is_referenced(env, reg))
update_ref_obj(&meta->ref_obj, reg);
- if (bpf_register_is_null(reg) && type_may_be_null(kf_arg_type))
+ if (bpf_register_is_null(reg) && type_may_be_null(kf_arg_type)) {
+ ret = mark_arg_precision(env, argno);
+ if (ret)
+ return ret;
continue;
+ }
if (is_kfunc_arg_map(btf, &args[i])) {
ref_id = *reg2btf_ids[CONST_PTR_TO_MAP];
diff --git a/tools/testing/selftests/bpf/progs/verifier_cgroup_storage.c b/tools/testing/selftests/bpf/progs/verifier_cgroup_storage.c
index 9a13f5c11ac7..884080a5bffc 100644
--- a/tools/testing/selftests/bpf/progs/verifier_cgroup_storage.c
+++ b/tools/testing/selftests/bpf/progs/verifier_cgroup_storage.c
@@ -305,4 +305,33 @@ __naked void cpu_cgroup_storage_access_6(void)
: __clobber_all);
}
+/*
+ * Verification takes two paths: with r2 being scalar zero on path (1)
+ * and with r2 being some other scalar on path (2).
+ * Check that the verifier does not use checkpoints created
+ * on path (1) to prune path (2).
+ */
+SEC("cgroup/skb")
+__failure
+__flag(BPF_F_TEST_STATE_FREQ)
+__msg("get_local_storage() doesn't support non-zero flags")
+__naked void non_zero_flags_on_a_pruned_path(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ /* r2 is 0 on the path explored first, 1 on the other */\
+ r2 = 1; \
+ if r0 == 0 goto 1f; \
+ r2 = 0; \
+1: r1 = %[cgroup_storage] ll; \
+ call %[bpf_get_local_storage]; \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32),
+ __imm(bpf_get_local_storage),
+ __imm_addr(cgroup_storage)
+ : __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c
index 0bdeb7bc4687..a3d2af8dc839 100644
--- a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c
+++ b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c
@@ -56,6 +56,30 @@ int trusted_task_arg_nullable(void *ctx)
return res;
}
+/*
+ * Check that the verifier does not use checkpoints created
+ * on path with r1 == 0 to prune path with r1 != 0.
+ */
+SEC("?tp_btf/task_newtask")
+__failure
+__flag(BPF_F_TEST_STATE_FREQ)
+__msg("R1 type=scalar expected=ptr_, trusted_ptr_, rcu_ptr_")
+__naked int null_btf_id_arg_global_subprog(void)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "r1 = 42;"
+ "if r0 > 42 goto 1f;"
+ "r1 = 0;"
+ "1:"
+ "call subprog_trusted_task_nullable;"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
__weak int subprog_trusted_task_nonnull(struct task_struct *task __arg_trusted)
{
return task->pid + task->tgid;
diff --git a/tools/testing/selftests/bpf/progs/verifier_precision.c b/tools/testing/selftests/bpf/progs/verifier_precision.c
index 6f325876efdd..f4459561bf39 100644
--- a/tools/testing/selftests/bpf/progs/verifier_precision.c
+++ b/tools/testing/selftests/bpf/progs/verifier_precision.c
@@ -2,8 +2,10 @@
/* Copyright (C) 2023 SUSE LLC */
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
+#include <stdbool.h>
#include "../../../include/linux/filter.h"
#include "bpf_misc.h"
+#include "bpf_kfuncs.h"
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
@@ -642,4 +644,102 @@ __naked int bpf_atomic_cmpxchg_32bit_precision(void)
: __clobber_all);
}
+/*
+ * Verification takes two paths: with r1 being scalar zero on path (1)
+ * and with r1 being some other scalar on path (2).
+ * Check that the verifier does not use checkpoints created
+ * on path (1) to prune path (2).
+ */
+SEC("?tc")
+__flag(BPF_F_TEST_STATE_FREQ)
+__failure __msg("R1 type=scalar expected=fp")
+__naked int null_mem_arg_zero_size(void)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "r1 = 42;"
+ "if r0 > 42 goto 1f;"
+ "r1 = 0;"
+ "1:"
+ "r2 = 0;"
+ "r3 = 0;"
+ "r4 = 0;"
+ "r5 = 0;"
+ /*
+ * ARG_PTR_TO_MEM | PTR_MAYBE_NULL parameter can be NULL,
+ * but can't be some other scalar value.
+ */
+ "call %[bpf_csum_diff];"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_get_prandom_u32),
+ __imm(bpf_csum_diff)
+ : __clobber_all);
+}
+
+__weak int subprog_mem_arg(int *p)
+{
+ if (p)
+ return *p;
+ return 0;
+}
+
+/*
+ * Verification takes two paths: with r1 being scalar zero on path (1)
+ * and with r1 being some other scalar on path (2).
+ * Check that the verifier does not use checkpoints created
+ * on path (1) to prune path (2).
+ */
+SEC("?raw_tp")
+__flag(BPF_F_TEST_STATE_FREQ)
+__failure __msg("R1 type=scalar expected=fp")
+__naked int null_mem_arg_global_subprog(void)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "r1 = 42;"
+ "if r0 > 42 goto 1f;"
+ "r1 = 0;"
+ "1:"
+ "call subprog_mem_arg;"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* Same as above, check that path with r3 == 0 does not prune the path with r3 != 0 */
+SEC("?tc")
+__flag(BPF_F_TEST_STATE_FREQ)
+__failure __msg("R3 type=scalar expected=fp")
+int null_kfunc_arg_dynptr_slice(struct __sk_buff *skb)
+{
+ struct bpf_dynptr ptr;
+
+ bpf_dynptr_from_skb(skb, 0, &ptr);
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "r3 = 42;"
+ "if r0 > 42 goto 1f;"
+ "r3 = 0;"
+ "1:"
+ "r1 = %[ptr];"
+ "r2 = 0;"
+ "r4 = 8;"
+ "call %[bpf_dynptr_slice];"
+ :
+ : __imm_ptr(ptr),
+ __imm(bpf_get_prandom_u32),
+ __imm(bpf_dynptr_slice)
+ : __clobber_common);
+ return 0;
+}
+
+void __kfunc_btf_root(void)
+{
+ bpf_dynptr_slice(0, 0, 0, 0);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c b/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c
index e174a905c562..dc0c7034c04f 100644
--- a/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c
+++ b/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c
@@ -287,9 +287,9 @@ __msg("17: (b7) r0 = 0")
__msg("18: (95) exit")
__msg("returning from callee:")
__msg("to caller at 9:")
-__msg("frame 0: propagating r1,r4")
+__msg("frame 0: propagating r1,r3,r4")
__msg("mark_precise: frame0: last_idx 9 first_idx 9 subseq_idx -1")
-__msg("mark_precise: frame0: regs=r1,r4 stack= before 18: (95) exit")
+__msg("mark_precise: frame0: regs=r1,r3,r4 stack= before 18: (95) exit")
__msg("from 18 to 9: safe")
__naked int callback_result_precise(void)
{
@@ -419,9 +419,9 @@ __msg("to caller at 9:")
/* r1, r4 are always precise for bpf_loop(),
* r6 was marked before backtracking to callback body.
*/
-__msg("frame 0: propagating r1,r4,r6")
+__msg("frame 0: propagating r1,r3,r4,r6")
__msg("mark_precise: frame0: last_idx 9 first_idx 9 subseq_idx -1")
-__msg("mark_precise: frame0: regs=r1,r4,r6 stack= before 16: (95) exit")
+__msg("mark_precise: frame0: regs=r1,r3,r4,r6 stack= before 16: (95) exit")
__msg("mark_precise: frame1: regs= stack= before 15: (b7) r0 = 0")
__msg("mark_precise: frame1: regs= stack= before 9: (85) call bpf_loop")
__msg("mark_precise: frame0: parent state regs= stack=:")
@@ -575,9 +575,9 @@ __msg("to caller at 10:")
/* r1, r4 are always precise for bpf_loop(),
* fp-8 was marked before backtracking to callback body.
*/
-__msg("frame 0: propagating r1,r4,fp-8")
+__msg("frame 0: propagating r1,r3,r4,fp-8")
__msg("mark_precise: frame0: last_idx 10 first_idx 10 subseq_idx -1")
-__msg("mark_precise: frame0: regs=r1,r4 stack=-8 before 18: (95) exit")
+__msg("mark_precise: frame0: regs=r1,r3,r4 stack=-8 before 18: (95) exit")
__msg("mark_precise: frame1: regs= stack= before 17: (b7) r0 = 0")
__msg("mark_precise: frame1: regs= stack= before 10: (85) call bpf_loop#181")
__msg("mark_precise: frame0: parent state regs= stack=:")