* [PATCH bpf v2 0/2] Fix for raw_tp PTR_MAYBE_NULL unmarking
@ 2024-12-05 22:31 Kumar Kartikeya Dwivedi
2024-12-05 22:31 ` [PATCH bpf v2 1/2] bpf: Suppress warning for non-zero off raw_tp arg NULL check Kumar Kartikeya Dwivedi
2024-12-05 22:31 ` [PATCH bpf v2 2/2] selftests/bpf: Add raw_tp tests for PTR_MAYBE_NULL marking Kumar Kartikeya Dwivedi
0 siblings, 2 replies; 5+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2024-12-05 22:31 UTC (permalink / raw)
To: bpf
Cc: kkd, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Martin KaFai Lau, Eduard Zingerman, Manu Bretelle, kernel-team
A production BPF program had the following code produced by LLVM.
r0 = 1024;
r1 = ...; // r1 = trusted_or_null_(id=1)
r3 = r1; // r3 = trusted_or_null_(id=1) r1 = trusted_or_null_(id=1)
r3 += r0; // r3 = trusted_or_null_(id=1, off=1024)
if r1 == 0 goto pc+X;
After cb4158ce8ec8 ("bpf: Mark raw_tp arguments with PTR_MAYBE_NULL"),
the production BPF program began throwing a warning in the verifier
because for the code above, when unmarking null mark from r1, the
verifier will notice another register r3 with same id but off != 0,
which is unexpected, since offset modification on PTR_MAYBE_NULL is not
permitted, but the aforementioned commit relaxed that restriction to
preserve compatibility with non-NULL raw_tp args.
Provide a fix to suppress the warning for raw_tp args. We will follow up
with a more generic fix to handle such patterns for all pointer types in
the verifier, which currently involves playing whack-a-mole with
suppressing such LLVM optimizations and reworking BPF programs to avoid
verifier errors.
Changelog:
----------
v1 -> v2
v1: https://lore.kernel.org/bpf/20241204024154.21386-1-memxor@gmail.com
* Fix eager unmarking bug (Eduard)
* Generalize approach, always unmark NULL when off == 0 is checked
* Make NULL check noop if operand has off != 0
* Do not reset id when treating as noop
* Trim comment (Alexei)
* Adjust selftests
Kumar Kartikeya Dwivedi (2):
bpf: Suppress warning for non-zero off raw_tp arg NULL check
selftests/bpf: Add raw_tp tests for PTR_MAYBE_NULL marking
kernel/bpf/verifier.c | 38 +++++++--
.../selftests/bpf/prog_tests/raw_tp_null.c | 6 ++
.../selftests/bpf/progs/raw_tp_null_fail.c | 80 +++++++++++++++++++
3 files changed, 116 insertions(+), 8 deletions(-)
create mode 100644 tools/testing/selftests/bpf/progs/raw_tp_null_fail.c
base-commit: 5a6ea7022ff4d2a65ae328619c586d6a8909b48b
--
2.43.5
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH bpf v2 1/2] bpf: Suppress warning for non-zero off raw_tp arg NULL check
2024-12-05 22:31 [PATCH bpf v2 0/2] Fix for raw_tp PTR_MAYBE_NULL unmarking Kumar Kartikeya Dwivedi
@ 2024-12-05 22:31 ` Kumar Kartikeya Dwivedi
2024-12-06 0:00 ` Eduard Zingerman
2024-12-05 22:31 ` [PATCH bpf v2 2/2] selftests/bpf: Add raw_tp tests for PTR_MAYBE_NULL marking Kumar Kartikeya Dwivedi
1 sibling, 1 reply; 5+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2024-12-05 22:31 UTC (permalink / raw)
To: bpf
Cc: kkd, Manu Bretelle, Alexei Starovoitov, Andrii Nakryiko,
Daniel Borkmann, Martin KaFai Lau, Eduard Zingerman, kernel-team
The fixed commit began marking raw_tp arguments as PTR_MAYBE_NULL to
avoid dead code elimination in the verifier, since raw_tp arguments
may actually be NULL at runtime. However, to preserve compatibility,
it simulated the raw_tp accesses as if the NULL marking was not present.
One of the behaviors permitted by this simulation is offset modification
for NULL pointers. Typically, this pattern is rejected by the verifier,
and users make workarounds to prevent the compiler from producing such
patterns. However, now that it is allowed, when the compiler emits such
code, the offset modification is allowed and a PTR_MAYBE_NULL raw_tp arg
with non-zero off can be formed.
The failing example program had the following pseudo-code:
r0 = 1024;
r1 = ...; // r1 = trusted_or_null_(id=1)
r3 = r1; // r3 = trusted_or_null_(id=1) r1 = trusted_or_null_(id=1)
r3 += r0; // r3 = trusted_or_null_(id=1, off=1024)
if r1 == 0 goto pc+X;
At this point, while mark_ptr_or_null_reg will see PTR_MAYBE_NULL and
off == 0 for r1, it will notice non-zero off for r3, and the
WARN_ON_ONCE will fire, as the condition checks excluding register types
do not include raw_tp argument type.
This is a pattern produced by LLVM, therefore it is hard to suppress it
everywhere in BPF programs.
The right "generic" fix for this issue in general, will be permitting
offset modification for PTR_MAYBE_NULL pointers everywhere, and
enforcing that the instruction operand of a conditional jump has the
offset as zero. It's other copies may still have non-zero offset, and
that is fine. But this is more involved and will take longer to
integrate.
If a zero offset pointer is NULL checked, all copies can be marked
non-NULL, while checking non-zero offset PTR_MAYBE_NULL is a no-op.
For now, only make this change for raw_tp arguments, and table the
generic fix for later.
Dereferencing such pointers will still work as the fixed commit allowed
it for raw_tp args.
Fixes: cb4158ce8ec8 ("bpf: Mark raw_tp arguments with PTR_MAYBE_NULL")
Reported-by: Manu Bretelle <chantra@meta.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
kernel/bpf/verifier.c | 38 ++++++++++++++++++++++++++++++--------
1 file changed, 30 insertions(+), 8 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 2fd35465d650..dea92cac2522 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -15340,7 +15340,8 @@ static int reg_set_min_max(struct bpf_verifier_env *env,
return err;
}
-static void mark_ptr_or_null_reg(struct bpf_func_state *state,
+static void mark_ptr_or_null_reg(struct bpf_verifier_env *env,
+ struct bpf_func_state *state,
struct bpf_reg_state *reg, u32 id,
bool is_null)
{
@@ -15357,8 +15358,8 @@ static void mark_ptr_or_null_reg(struct bpf_func_state *state,
*/
if (WARN_ON_ONCE(reg->smin_value || reg->smax_value || !tnum_equals_const(reg->var_off, 0)))
return;
- if (!(type_is_ptr_alloc_obj(reg->type) || type_is_non_owning_ref(reg->type)) &&
- WARN_ON_ONCE(reg->off))
+ if (!(type_is_ptr_alloc_obj(reg->type) || type_is_non_owning_ref(reg->type) ||
+ mask_raw_tp_reg_cond(env, reg)) && WARN_ON_ONCE(reg->off))
return;
if (is_null) {
@@ -15390,11 +15391,12 @@ static void mark_ptr_or_null_reg(struct bpf_func_state *state,
/* The logic is similar to find_good_pkt_pointers(), both could eventually
* be folded together at some point.
*/
-static void mark_ptr_or_null_regs(struct bpf_verifier_state *vstate, u32 regno,
+static void mark_ptr_or_null_regs(struct bpf_verifier_env *env,
+ struct bpf_verifier_state *vstate, u32 regno,
bool is_null)
{
struct bpf_func_state *state = vstate->frame[vstate->curframe];
- struct bpf_reg_state *regs = state->regs, *reg;
+ struct bpf_reg_state *regs = state->regs, *reg = ®s[regno];
u32 ref_obj_id = regs[regno].ref_obj_id;
u32 id = regs[regno].id;
@@ -15405,8 +15407,28 @@ static void mark_ptr_or_null_regs(struct bpf_verifier_state *vstate, u32 regno,
*/
WARN_ON_ONCE(release_reference_state(state, id));
+ /* For raw_tp args, compiler can produce code of the following
+ * pattern:
+ * r3 = r1; // r1 = trusted_or_null_(id=1) r3 = trusted_or_null_(id=1)
+ * r3 += 8; // r3 = trusted_or_null_(id=1,off=8)
+ * if r1 == 0 goto pc+N; // r1 = trusted_(id=1)
+ *
+ * But we musn't remove the or_null mark from r3, as it won't be
+ * NULL.
+ *
+ * Only do unmarking of everything sharing id if operand of NULL check
+ * has off = 0.
+ */
+ if (mask_raw_tp_reg_cond(env, reg) && reg->off) {
+ /* We don't reset reg->id back to 0, as it's unexpected
+ * when PTR_MAYBE_NULL is set. Simply avoid performing
+ * a walk for other registers with the same id.
+ */
+ return;
+ }
+
bpf_for_each_reg_in_vstate(vstate, state, reg, ({
- mark_ptr_or_null_reg(state, reg, id, is_null);
+ mark_ptr_or_null_reg(env, state, reg, id, is_null);
}));
}
@@ -15832,9 +15854,9 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
/* Mark all identical registers in each branch as either
* safe or unknown depending R == 0 or R != 0 conditional.
*/
- mark_ptr_or_null_regs(this_branch, insn->dst_reg,
+ mark_ptr_or_null_regs(env, this_branch, insn->dst_reg,
opcode == BPF_JNE);
- mark_ptr_or_null_regs(other_branch, insn->dst_reg,
+ mark_ptr_or_null_regs(env, other_branch, insn->dst_reg,
opcode == BPF_JEQ);
} else if (!try_match_pkt_pointers(insn, dst_reg, ®s[insn->src_reg],
this_branch, other_branch) &&
--
2.43.5
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH bpf v2 2/2] selftests/bpf: Add raw_tp tests for PTR_MAYBE_NULL marking
2024-12-05 22:31 [PATCH bpf v2 0/2] Fix for raw_tp PTR_MAYBE_NULL unmarking Kumar Kartikeya Dwivedi
2024-12-05 22:31 ` [PATCH bpf v2 1/2] bpf: Suppress warning for non-zero off raw_tp arg NULL check Kumar Kartikeya Dwivedi
@ 2024-12-05 22:31 ` Kumar Kartikeya Dwivedi
2024-12-06 0:08 ` Eduard Zingerman
1 sibling, 1 reply; 5+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2024-12-05 22:31 UTC (permalink / raw)
To: bpf
Cc: kkd, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Martin KaFai Lau, Eduard Zingerman, Manu Bretelle, kernel-team
Ensure that pointers with off != 0 are never unmarked as PTR_MAYBE_NULL
when doing NULL checks, while pointers that have off == 0 continue
getting unmarked, and also unmark associated copies with same id but
possibly non-zero offset.
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
.../selftests/bpf/prog_tests/raw_tp_null.c | 6 ++
.../selftests/bpf/progs/raw_tp_null_fail.c | 80 +++++++++++++++++++
2 files changed, 86 insertions(+)
create mode 100644 tools/testing/selftests/bpf/progs/raw_tp_null_fail.c
diff --git a/tools/testing/selftests/bpf/prog_tests/raw_tp_null.c b/tools/testing/selftests/bpf/prog_tests/raw_tp_null.c
index 6fa19449297e..13fcd4c31034 100644
--- a/tools/testing/selftests/bpf/prog_tests/raw_tp_null.c
+++ b/tools/testing/selftests/bpf/prog_tests/raw_tp_null.c
@@ -3,6 +3,12 @@
#include <test_progs.h>
#include "raw_tp_null.skel.h"
+#include "raw_tp_null_fail.skel.h"
+
+void test_raw_tp_null_fail(void)
+{
+ RUN_TESTS(raw_tp_null_fail);
+}
void test_raw_tp_null(void)
{
diff --git a/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c b/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c
new file mode 100644
index 000000000000..68de752cfe53
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+
+/* r1 with off=0 is checked, which marks r0 with off=8 as non-null */
+SEC("tp_btf/bpf_testmod_test_raw_tp_null")
+__success
+__log_level(2)
+__msg("3: (07) r0 += 8 ; R0_w=trusted_ptr_or_null_sk_buff(id=1,off=8)")
+__msg("4: (15) if r1 == 0x0 goto pc+1 ; R1_w=trusted_ptr_sk_buff()")
+__msg("5: (bf) r2 = r0 ; R0_w=trusted_ptr_sk_buff(off=8)")
+int BPF_PROG(test_raw_tp_null_check_zero_off, struct sk_buff *skb)
+{
+ asm volatile (
+ "r1 = *(u64 *)(r1 +0); \
+ r0 = r1; \
+ r2 = 0; \
+ r0 += 8; \
+ if r1 == 0 goto jmp; \
+ r2 = r0; \
+ jmp: "
+ ::
+ : __clobber_all
+ );
+ return 0;
+}
+
+/* r2 with offset is checked, which won't mark r1 with off=0 as non-NULL */
+SEC("tp_btf/bpf_testmod_test_raw_tp_null")
+__success
+__log_level(2)
+__msg("3: (07) r2 += 8 ; R2_w=trusted_ptr_or_null_sk_buff(id=1,off=8)")
+__msg("4: (15) if r2 == 0x0 goto pc+1 ; R2_w=trusted_ptr_or_null_sk_buff(id=1,off=8)")
+__msg("5: (bf) r2 = r1 ; R1_w=trusted_ptr_or_null_sk_buff(id=1)")
+int BPF_PROG(test_raw_tp_null_copy_check_with_off, struct sk_buff *skb)
+{
+ asm volatile (
+ "r1 = *(u64 *)(r1 +0); \
+ r2 = r1; \
+ r3 = 0; \
+ r2 += 8; \
+ if r2 == 0 goto jmp2; \
+ r2 = r1; \
+ jmp2: "
+ ::
+ : __clobber_all
+ );
+ return 0;
+}
+
+/* Ensure state doesn't change for r0 and r1 when performing repeated checks.. */
+SEC("tp_btf/bpf_testmod_test_raw_tp_null")
+__success
+__log_level(2)
+__msg("2: (07) r0 += 8 ; R0_w=trusted_ptr_or_null_sk_buff(id=1,off=8)")
+__msg("3: (15) if r0 == 0x0 goto pc+3 ; R0_w=trusted_ptr_or_null_sk_buff(id=1,off=8)")
+__msg("4: (15) if r0 == 0x0 goto pc+2 ; R0_w=trusted_ptr_or_null_sk_buff(id=1,off=8)")
+__msg("5: (15) if r0 == 0x0 goto pc+1 ; R0_w=trusted_ptr_or_null_sk_buff(id=1,off=8)")
+__msg("6: (bf) r2 = r1 ; R1=trusted_ptr_or_null_sk_buff(id=1)")
+int BPF_PROG(test_raw_tp_check_with_off, struct sk_buff *skb)
+{
+ asm volatile (
+ "r1 = *(u64 *)(r1 +0); \
+ r0 = r1; \
+ r0 += 8; \
+ if r0 == 0 goto jmp3; \
+ if r0 == 0 goto jmp3; \
+ if r0 == 0 goto jmp3; \
+ r2 = r1; \
+ jmp3: "
+ ::
+ : __clobber_all
+ );
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
--
2.43.5
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH bpf v2 1/2] bpf: Suppress warning for non-zero off raw_tp arg NULL check
2024-12-05 22:31 ` [PATCH bpf v2 1/2] bpf: Suppress warning for non-zero off raw_tp arg NULL check Kumar Kartikeya Dwivedi
@ 2024-12-06 0:00 ` Eduard Zingerman
0 siblings, 0 replies; 5+ messages in thread
From: Eduard Zingerman @ 2024-12-06 0:00 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi, bpf
Cc: kkd, Manu Bretelle, Alexei Starovoitov, Andrii Nakryiko,
Daniel Borkmann, Martin KaFai Lau, kernel-team
On Thu, 2024-12-05 at 14:31 -0800, Kumar Kartikeya Dwivedi wrote:
[...]
> Fixes: cb4158ce8ec8 ("bpf: Mark raw_tp arguments with PTR_MAYBE_NULL")
> Reported-by: Manu Bretelle <chantra@meta.com>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> ---
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
I think this should work.
Although, I'm not sure if we should delay generic fix and do this in two steps.
> kernel/bpf/verifier.c | 38 ++++++++++++++++++++++++++++++--------
> 1 file changed, 30 insertions(+), 8 deletions(-)
>
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 2fd35465d650..dea92cac2522 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -15340,7 +15340,8 @@ static int reg_set_min_max(struct bpf_verifier_env *env,
> return err;
> }
>
> -static void mark_ptr_or_null_reg(struct bpf_func_state *state,
> +static void mark_ptr_or_null_reg(struct bpf_verifier_env *env,
> + struct bpf_func_state *state,
> struct bpf_reg_state *reg, u32 id,
> bool is_null)
> {
> @@ -15357,8 +15358,8 @@ static void mark_ptr_or_null_reg(struct bpf_func_state *state,
> */
> if (WARN_ON_ONCE(reg->smin_value || reg->smax_value || !tnum_equals_const(reg->var_off, 0)))
> return;
> - if (!(type_is_ptr_alloc_obj(reg->type) || type_is_non_owning_ref(reg->type)) &&
> - WARN_ON_ONCE(reg->off))
> + if (!(type_is_ptr_alloc_obj(reg->type) || type_is_non_owning_ref(reg->type) ||
> + mask_raw_tp_reg_cond(env, reg)) && WARN_ON_ONCE(reg->off))
Nit: the condition is a bit hard to read, maybe rewrite it as follows:
if (!type_is_ptr_alloc_obj(reg->type) &&
!type_is_non_owning_ref(reg->type) &&
!mask_raw_tp_reg_cond(env, reg) &&
WARN_ON_ONCE(reg->off))
return;
?
> return;
>
> if (is_null) {
[...]
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH bpf v2 2/2] selftests/bpf: Add raw_tp tests for PTR_MAYBE_NULL marking
2024-12-05 22:31 ` [PATCH bpf v2 2/2] selftests/bpf: Add raw_tp tests for PTR_MAYBE_NULL marking Kumar Kartikeya Dwivedi
@ 2024-12-06 0:08 ` Eduard Zingerman
0 siblings, 0 replies; 5+ messages in thread
From: Eduard Zingerman @ 2024-12-06 0:08 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi, bpf
Cc: kkd, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Martin KaFai Lau, Manu Bretelle, kernel-team
On Thu, 2024-12-05 at 14:31 -0800, Kumar Kartikeya Dwivedi wrote:
> Ensure that pointers with off != 0 are never unmarked as PTR_MAYBE_NULL
> when doing NULL checks, while pointers that have off == 0 continue
> getting unmarked, and also unmark associated copies with same id but
> possibly non-zero offset.
>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> ---
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
[...]
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2024-12-06 0:08 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-12-05 22:31 [PATCH bpf v2 0/2] Fix for raw_tp PTR_MAYBE_NULL unmarking Kumar Kartikeya Dwivedi
2024-12-05 22:31 ` [PATCH bpf v2 1/2] bpf: Suppress warning for non-zero off raw_tp arg NULL check Kumar Kartikeya Dwivedi
2024-12-06 0:00 ` Eduard Zingerman
2024-12-05 22:31 ` [PATCH bpf v2 2/2] selftests/bpf: Add raw_tp tests for PTR_MAYBE_NULL marking Kumar Kartikeya Dwivedi
2024-12-06 0:08 ` Eduard Zingerman
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox