summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-09-06 13:49:44 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-09-06 13:49:44 -0700
commit2beb1b31a12b57e19cd5c82ea6d54e56520605e8 (patch)
tree622b0fd0b7fd41bdf9240873cb6d7514c248330b /tools
parent88405f0ad1d5c680afe3ea0ce9345fa9e1deaac8 (diff)
parent536b523b407397c8d3967c020ce7aad70a0ea030 (diff)
downloadlinux-stable-2beb1b31a12b57e19cd5c82ea6d54e56520605e8.tar.gz
linux-stable-2beb1b31a12b57e19cd5c82ea6d54e56520605e8.zip
Merge tag 'bpf-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf
Pull bpf fixes from Alexei Starovoitov: "This mainly contains verifier fixes that address bugs reported by Nicholas Carlini. - Fix incorrect non-NULL inference in pointer comparisons: pointer types that may be NULL at runtime, pointers with unbounded offsets, JMP32 comparisons with zero, and imprecise zero registers (Eduard Zingerman) - Fix precision tracking for half-dead zero spills, ld_abs/ld_ind implicit subprog exit, bpf_loop() callbacks, linked scalar ids and NULL call arguments (Eduard Zingerman) - Reject BPF_PSEUDO_FUNC reference to the main program, fix zero extension of arena 32-bit cmpxchg, don't rewrite bpf_fastcall patterns entered by a jump (Eduard Zingerman) - Fix percpu map update and BPF_F_CPU validation with sparse CPU IDs (Hui Su) - Fix NULL-ptr-derefs in bpf_snprintf_btf() for void and VAR types, and reject key-less BTF for hash maps (Jiayuan Chen) - Various fixes (Kumar Kartikeya Dwivedi): - Fix out-of-bounds access in disassembler on invalid LDSX instruction - mark siginfo of signal tracepoints as scalar and sched_process_wait argument as nullable - mark faultable stack helpers as sleepable - reject tail calls and legacy packet loads from callbacks - enforce rbtree callback lock restrictions for resilient locks - require MEM_PERCPU for percpu kptr stores - clear NON_OWN_REF after RCU protection ends - mark NULL kptr stores precise - preserve inner map identity in callback frames - reject non-scalar bpf_loop() iteration counts - Fix trampoline allocation slowdown on x86 by using EXECMEM_MODULE_DATA (Mike Rapoport) - Keep bpf_refcount_acquire() nullable for borrowed RCU kptrs and reject untrusted allocated-object pointers (Ning Ding) - Fix special fields handling in recycled rhtab elements (Nuoqi Gui, Yuan Chen)" * tag 'bpf-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf: (86 commits) bpf, riscv: Make arena support depend on ZACAS selftests/bpf: Test pointer bpf_loop iteration count rejection bpf: Reject non-scalar bpf_loop iteration counts bpf: use mark_arg_precision() in check_mem_size_reg() bpf: propagate mark_chain_precision() errors out of loop_flag_is_zero() selftests/bpf: precision of a NULL global subprogram BTF_ID argument bpf: mark a NULL BTF_ID argument of a global subprogram precise selftests/bpf: precision of a NULL kfunc argument bpf: mark a NULL kfunc argument precise selftests/bpf: precision of a NULL global subprogram memory argument bpf: mark a NULL memory argument of a call precise selftests/bpf: precision of a NULL helper argument bpf: mark a NULL call argument precise selftests/bpf: Test inner map identities in callbacks bpf: Preserve inner map identity in callback frames selftests/bpf: Test imprecise scalar kptr stores bpf: Mark NULL kptr stores precise selftests/bpf: Test rhtab kptr cancellation semantics bpf: Cancel special fields when recycling rhtab elements selftests/bpf: Test timer field on recycled rhtab element ...
Diffstat (limited to 'tools')
-rw-r--r--tools/testing/selftests/bpf/prog_tests/btf_map_keyless.c59
-rw-r--r--tools/testing/selftests/bpf/prog_tests/rhash.c6
-rw-r--r--tools/testing/selftests/bpf/prog_tests/rhash_timer.c141
-rw-r--r--tools/testing/selftests/bpf/prog_tests/snprintf_btf.c79
-rw-r--r--tools/testing/selftests/bpf/prog_tests/timer_mim.c29
-rw-r--r--tools/testing/selftests/bpf/progs/iters.c39
-rw-r--r--tools/testing/selftests/bpf/progs/map_kptr_fail.c37
-rw-r--r--tools/testing/selftests/bpf/progs/percpu_alloc_fail.c59
-rw-r--r--tools/testing/selftests/bpf/progs/preempt_lock.c52
-rw-r--r--tools/testing/selftests/bpf/progs/raw_tp_null_fail.c53
-rw-r--r--tools/testing/selftests/bpf/progs/rbtree_fail.c84
-rw-r--r--tools/testing/selftests/bpf/progs/rcu_read_lock.c6
-rw-r--r--tools/testing/selftests/bpf/progs/refcounted_kptr.c61
-rw-r--r--tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c150
-rw-r--r--tools/testing/selftests/bpf/progs/rhash.c112
-rw-r--r--tools/testing/selftests/bpf/progs/rhash_timer.c98
-rw-r--r--tools/testing/selftests/bpf/progs/snprintf_btf_void.c24
-rw-r--r--tools/testing/selftests/bpf/progs/tailcall_callback.c16
-rw-r--r--tools/testing/selftests/bpf/progs/task_local_data.bpf.h3
-rw-r--r--tools/testing/selftests/bpf/progs/test_bpf_nf.c8
-rw-r--r--tools/testing/selftests/bpf/progs/timer_mim_reject.c84
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_async_cb_context.c98
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_bounds.c41
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_bpf_fastcall.c110
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_cfg.c14
-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_iterating_callbacks.c17
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c193
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_ld_ind.c96
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_netfilter_ctx.c78
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_precision.c100
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_scalar_ids.c13
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_spill_fill.c40
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_subprog_precision.c63
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_zext.c26
-rw-r--r--tools/testing/selftests/bpf/verifier/pseudo_func.c45
37 files changed, 2164 insertions, 23 deletions
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_map_keyless.c b/tools/testing/selftests/bpf/prog_tests/btf_map_keyless.c
new file mode 100644
index 000000000000..3248bccc3557
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/btf_map_keyless.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include <bpf/btf.h>
+
+/*
+ * A hash map with a key-less BTF (btf_key_type_id == 0) used to be accepted
+ * and then NULL-deref in btf_type_show() when dumped through bpffs. A fixed
+ * kernel rejects it at creation; verify that rejection, with a keyed positive
+ * control so the -EINVAL is about the missing key type and not some unrelated
+ * failure.
+ */
+static void check_keyless(int map_type, __u32 map_flags, int btf_fd, int val_id)
+{
+ LIBBPF_OPTS(bpf_map_create_opts, opts);
+ int map_fd;
+
+ opts.map_flags = map_flags;
+ opts.btf_fd = btf_fd;
+ opts.btf_value_type_id = val_id;
+
+ /* Positive control: the same map with a real key type is accepted. */
+ opts.btf_key_type_id = val_id;
+ map_fd = bpf_map_create(map_type, "keyed_map", 4, 4, 8, &opts);
+ if (!ASSERT_GE(map_fd, 0, "keyed create is accepted"))
+ return;
+ close(map_fd);
+
+ /* A key-less BTF must be rejected. */
+ opts.btf_key_type_id = 0;
+ map_fd = bpf_map_create(map_type, "keyless_map", 4, 4, 8, &opts);
+ ASSERT_EQ(map_fd, -EINVAL, "key-less create is rejected");
+ if (map_fd >= 0)
+ close(map_fd);
+}
+
+void test_btf_map_keyless(void)
+{
+ int btf_fd, val_id;
+ struct btf *btf;
+
+ btf = btf__new_empty();
+ if (!ASSERT_OK_PTR(btf, "btf__new_empty"))
+ return;
+
+ val_id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
+ if (!ASSERT_GT(val_id, 0, "btf__add_int"))
+ goto out;
+
+ if (!ASSERT_OK(btf__load_into_kernel(btf), "btf__load_into_kernel"))
+ goto out;
+ btf_fd = btf__fd(btf);
+
+ if (test__start_subtest("hash"))
+ check_keyless(BPF_MAP_TYPE_HASH, 0, btf_fd, val_id);
+ if (test__start_subtest("rhash"))
+ check_keyless(BPF_MAP_TYPE_RHASH, BPF_F_NO_PREALLOC, btf_fd, val_id);
+out:
+ btf__free(btf);
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/rhash.c b/tools/testing/selftests/bpf/prog_tests/rhash.c
index 98bb66907b7f..0641bd5b0a9e 100644
--- a/tools/testing/selftests/bpf/prog_tests/rhash.c
+++ b/tools/testing/selftests/bpf/prog_tests/rhash.c
@@ -172,6 +172,12 @@ void test_rhash(void)
if (test__start_subtest("test_rhash_delete_nonexistent"))
rhash_run("test_rhash_delete_nonexistent");
+ if (test__start_subtest("test_rhash_kptr_update"))
+ rhash_run("test_rhash_kptr_update");
+
+ if (test__start_subtest("test_rhash_kptr_delete"))
+ rhash_run("test_rhash_kptr_delete");
+
if (test__start_subtest("test_rhash_map_extra_presize"))
rhash_map_extra_presize();
diff --git a/tools/testing/selftests/bpf/prog_tests/rhash_timer.c b/tools/testing/selftests/bpf/prog_tests/rhash_timer.c
new file mode 100644
index 000000000000..3aad9fc02e06
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/rhash_timer.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <sched.h>
+
+#include <test_progs.h>
+#include "rhash_timer.skel.h"
+
+#define MAX_ATTEMPTS 256
+#define RCU_SYNC_INTERVAL 64
+
+static int pin_to_first_cpu(cpu_set_t *old_mask)
+{
+ cpu_set_t new_mask;
+ int cpu;
+
+ if (sched_getaffinity(0, sizeof(*old_mask), old_mask))
+ return -errno;
+
+ for (cpu = 0; cpu < CPU_SETSIZE; cpu++)
+ if (CPU_ISSET(cpu, old_mask))
+ break;
+ if (cpu == CPU_SETSIZE)
+ return -EINVAL;
+
+ CPU_ZERO(&new_mask);
+ CPU_SET(cpu, &new_mask);
+ if (sched_setaffinity(0, sizeof(new_mask), &new_mask))
+ return -errno;
+ return 0;
+}
+
+static int update_timer_map(int map_fd, __u64 key)
+{
+ __u64 value[3] = {};
+
+ return bpf_map_update_elem(map_fd, &key, value, BPF_NOEXIST);
+}
+
+static int run_prog(int prog_fd, struct bpf_test_run_opts *opts)
+{
+ int err;
+
+ err = bpf_prog_test_run_opts(prog_fd, opts);
+ if (err)
+ return err;
+ return opts->retval;
+}
+
+void test_rhash_timer(void)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, opts);
+ struct rhash_timer *skel = NULL;
+ cpu_set_t old_mask;
+ int map_fd = -1, arm_fd, cancel_fd;
+ bool affinity_set = false;
+ __u64 key = 1;
+ int attempt, err;
+
+ err = pin_to_first_cpu(&old_mask);
+ if (!ASSERT_OK(err, "pin_to_first_cpu"))
+ return;
+ affinity_set = true;
+
+ skel = rhash_timer__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ goto out;
+
+ map_fd = bpf_map__fd(skel->maps.timer_map);
+ if (!ASSERT_GE(map_fd, 0, "timer_map fd"))
+ goto out;
+ arm_fd = bpf_program__fd(skel->progs.arm_deleted_timer);
+ if (!ASSERT_GE(arm_fd, 0, "arm_deleted_timer fd"))
+ goto out;
+ cancel_fd = bpf_program__fd(skel->progs.cancel_recycled_timer);
+ if (!ASSERT_GE(cancel_fd, 0, "cancel_recycled_timer fd"))
+ goto out;
+
+ err = update_timer_map(map_fd, key);
+ if (!ASSERT_OK(err, "seed_timer_map"))
+ goto out;
+
+ for (attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
+ err = run_prog(arm_fd, &opts);
+ if (err) {
+ ASSERT_OK(err, "arm_deleted_timer");
+ goto out;
+ }
+ if (skel->bss->armed != attempt + 1) {
+ ASSERT_EQ(skel->bss->armed, attempt + 1, "armed");
+ goto out;
+ }
+ if (skel->bss->timer_init_err) {
+ ASSERT_OK(skel->bss->timer_init_err, "timer_init_err");
+ goto out;
+ }
+ if (skel->bss->timer_set_callback_err) {
+ ASSERT_OK(skel->bss->timer_set_callback_err,
+ "timer_set_callback_err");
+ goto out;
+ }
+ if (skel->bss->timer_start_err) {
+ ASSERT_OK(skel->bss->timer_start_err, "timer_start_err");
+ goto out;
+ }
+
+ if ((attempt + 1) % RCU_SYNC_INTERVAL == 0) {
+ err = kern_sync_rcu();
+ if (err) {
+ ASSERT_OK(err, "kern_sync_rcu");
+ goto out;
+ }
+ }
+
+ err = update_timer_map(map_fd, ++key);
+ if (err) {
+ ASSERT_OK(err, "replace_timer_map");
+ goto out;
+ }
+
+ err = run_prog(cancel_fd, &opts);
+ if (err) {
+ ASSERT_OK(err, "cancel_recycled_timer");
+ goto out;
+ }
+ if (skel->bss->timer_cancel_err) {
+ ASSERT_OK(skel->bss->timer_cancel_err, "timer_cancel_err");
+ goto out;
+ }
+ if (skel->bss->cancelled)
+ break;
+ }
+
+ ASSERT_GT(skel->bss->cancelled, 0, "preserved timer");
+out:
+ if (map_fd >= 0)
+ bpf_map_delete_elem(map_fd, &key);
+ rhash_timer__destroy(skel);
+ if (affinity_set)
+ sched_setaffinity(0, sizeof(old_mask), &old_mask);
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/snprintf_btf.c b/tools/testing/selftests/bpf/prog_tests/snprintf_btf.c
index dd41b826be30..edce9c1b54fb 100644
--- a/tools/testing/selftests/bpf/prog_tests/snprintf_btf.c
+++ b/tools/testing/selftests/bpf/prog_tests/snprintf_btf.c
@@ -1,7 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
#include <test_progs.h>
#include <linux/btf.h>
+#include <bpf/btf.h>
#include "netif_receive_skb.skel.h"
+#include "snprintf_btf_void.skel.h"
/* Demonstrate that bpf_snprintf_btf succeeds and that various data types
* are formatted correctly.
@@ -58,3 +60,80 @@ void serial_test_snprintf_btf(void)
cleanup:
netif_receive_skb__destroy(skel);
}
+
+/*
+ * bpf_snprintf_btf() renders a type_id taken straight from the vmlinux BTF.
+ * Two such type_ids used to NULL-deref in the BTF show path:
+ * - a "const void" (a modifier resolving to void) in btf_modifier_show()
+ * - a BTF_KIND_VAR in btf_var_show() (base BTF has no resolved_ids)
+ * A fixed kernel renders both without crashing.
+ */
+static long run(struct snprintf_btf_void *skel, __u32 type_id)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, topts);
+ char ctx[8] = {};
+
+ skel->bss->type_id = type_id;
+ topts.ctx_in = ctx;
+ topts.ctx_size_in = sizeof(ctx);
+ if (!ASSERT_OK(bpf_prog_test_run_opts(bpf_program__fd(skel->progs.dump_type),
+ &topts), "test_run"))
+ return -1;
+ return skel->bss->ret;
+}
+
+void test_snprintf_btf_void(void)
+{
+ const struct btf_type *t;
+ struct snprintf_btf_void *skel;
+ int i, n, cv = 0, var = 0;
+ struct btf *btf;
+
+ btf = btf__parse("/sys/kernel/btf/vmlinux", NULL);
+ if (!btf) {
+ test__skip();
+ return;
+ }
+
+ skel = snprintf_btf_void__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
+ goto out_btf;
+
+ n = btf__type_cnt(btf);
+ for (i = 1; i < n && !(cv && var); i++) {
+ t = btf__type_by_id(btf, i);
+ if (!cv && btf_kind(t) == BTF_KIND_CONST && t->type == 0)
+ cv = i;
+ /* Pick a VAR small enough to render from the program's buffer. */
+ if (!var && btf_kind(t) == BTF_KIND_VAR) {
+ long sz = btf__resolve_size(btf, t->type);
+
+ if (sz > 0 && sz <= (long)sizeof(skel->bss->obj))
+ var = i;
+ }
+ }
+
+ /* "const void" renders the "<unsupported kind:0>" placeholder. */
+ if (test__start_subtest("const_void")) {
+ if (cv) {
+ ASSERT_EQ(run(skel, cv),
+ sizeof("<unsupported kind:0>") - 1, "ret");
+ ASSERT_STREQ(skel->bss->out, "<unsupported kind:0>",
+ "placeholder");
+ } else {
+ test__skip();
+ }
+ }
+
+ /* A BTF_KIND_VAR must resolve and render without error. */
+ if (test__start_subtest("var")) {
+ if (var)
+ ASSERT_GT(run(skel, var), 0, "ret");
+ else
+ test__skip();
+ }
+
+ snprintf_btf_void__destroy(skel);
+out_btf:
+ btf__free(btf);
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/timer_mim.c b/tools/testing/selftests/bpf/prog_tests/timer_mim.c
index c930c7d7105b..fa7bb769ca31 100644
--- a/tools/testing/selftests/bpf/prog_tests/timer_mim.c
+++ b/tools/testing/selftests/bpf/prog_tests/timer_mim.c
@@ -59,10 +59,32 @@ void serial_test_timer_mim(void)
int err;
old_print_fn = libbpf_set_print(NULL);
- timer_reject_skel = timer_mim_reject__open_and_load();
- libbpf_set_print(old_print_fn);
- if (!ASSERT_ERR_PTR(timer_reject_skel, "timer_reject_skel_load"))
+ timer_reject_skel = timer_mim_reject__open();
+ if (!ASSERT_OK_PTR(timer_reject_skel, "timer_reject_skel_open"))
+ goto cleanup;
+ bpf_program__set_autoload(timer_reject_skel->progs.test1, true);
+ err = timer_mim_reject__load(timer_reject_skel);
+ ASSERT_ERR(err, "timer_reject_skel_load");
+ timer_mim_reject__destroy(timer_reject_skel);
+
+ timer_reject_skel = timer_mim_reject__open();
+ if (!ASSERT_OK_PTR(timer_reject_skel, "callback_reject_skel_open"))
goto cleanup;
+ bpf_program__set_autoload(timer_reject_skel->progs.callback_map_uid_mismatch, true);
+ err = timer_mim_reject__load(timer_reject_skel);
+ ASSERT_ERR(err, "callback_reject_skel_load");
+ timer_mim_reject__destroy(timer_reject_skel);
+
+ timer_reject_skel = timer_mim_reject__open();
+ if (!ASSERT_OK_PTR(timer_reject_skel, "callback_accept_skel_open"))
+ goto cleanup;
+ bpf_program__set_autoload(timer_reject_skel->progs.callback_map_uid_match, true);
+ err = timer_mim_reject__load(timer_reject_skel);
+ if (!ASSERT_OK(err, "callback_accept_skel_load"))
+ goto cleanup;
+ timer_mim_reject__destroy(timer_reject_skel);
+ timer_reject_skel = NULL;
+ libbpf_set_print(old_print_fn);
timer_skel = timer_mim__open_and_load();
if (!timer_skel && errno == EOPNOTSUPP) {
@@ -75,6 +97,7 @@ void serial_test_timer_mim(void)
err = timer_mim(timer_skel);
ASSERT_OK(err, "timer_mim");
cleanup:
+ libbpf_set_print(old_print_fn);
timer_mim__destroy(timer_skel);
timer_mim_reject__destroy(timer_reject_skel);
}
diff --git a/tools/testing/selftests/bpf/progs/iters.c b/tools/testing/selftests/bpf/progs/iters.c
index 62d7df9e80be..c6699159dacd 100644
--- a/tools/testing/selftests/bpf/progs/iters.c
+++ b/tools/testing/selftests/bpf/progs/iters.c
@@ -2149,4 +2149,43 @@ __naked int stack_misc_vs_scalar_in_a_loop(void)
);
}
+__used
+static int loop_cb5(int i, __u64 *ctx)
+{
+ /* unsafe on a second iteration */
+ small_arr[*ctx] = i;
+ *ctx = 100500;
+ return 0;
+}
+
+SEC("raw_tp")
+__flag(BPF_F_TEST_STATE_FREQ)
+__failure __msg("memory access is {{.*}} and is outside of the object of size 64")
+__naked void loop_counter_precision_2nd_iter(void)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "*(u64 *)(r10 - 8) = 0;"
+ "r1 = 2;"
+ "if r0 == 42 goto +1;"
+ "r1 = 1;"
+ "r2 = loop_cb5 ll;"
+ "r3 = r10;"
+ "r3 += -8;"
+ "r4 = 0;"
+ /*
+ * Explore with nr_loops=1 on a first path and nr_loops=2 on a second path.
+ * Buggy verifier did not propagate r1 precision properly,
+ * and thus checkpoints created for nr_loops=1 case matched nr_loops=2 case.
+ */
+ "call %[bpf_loop];"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_loop),
+ __imm(bpf_get_prandom_u32)
+ : __clobber_all
+ );
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/map_kptr_fail.c b/tools/testing/selftests/bpf/progs/map_kptr_fail.c
index 5e25ca806060..eee35d203b66 100644
--- a/tools/testing/selftests/bpf/progs/map_kptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/map_kptr_fail.c
@@ -409,4 +409,41 @@ int reject_scalar_store_to_kptr(struct __sk_buff *ctx)
return 0;
}
+SEC("?tc")
+__description("reject imprecise scalar store to kptr after state pruning")
+__failure __msg("invalid kptr access, R7 type=scalar")
+__naked void reject_imprecise_scalar_store_to_kptr(void)
+{
+ asm volatile (
+ "r0 = 0;"
+ "*(u32 *)(r10 - 4) = r0;"
+ "r2 = r10;"
+ "r2 += -4;"
+ "r1 = %[array_map] ll;"
+ "call %[bpf_map_lookup_elem];"
+ "if r0 == 0 goto l2_%=;"
+ "r6 = r0;"
+ "r9 = *(u64 *)(r6 + 0);"
+ "if r9 != 0 goto l0_%=;"
+ "r7 = 0;"
+ ".rept 10;"
+ "r5 = 1;"
+ ".endr;"
+ "goto l1_%=;"
+ "l0_%=:"
+ "r7 = 0x4141414141414141 ll;"
+ ".rept 10;"
+ "r5 = 1;"
+ ".endr;"
+ "l1_%=:"
+ "*(u64 *)(r6 + 8) = r7;"
+ "l2_%=:"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(array_map)
+ : __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/percpu_alloc_fail.c b/tools/testing/selftests/bpf/progs/percpu_alloc_fail.c
index 08379c3b6a03..3701f4ea58c7 100644
--- a/tools/testing/selftests/bpf/progs/percpu_alloc_fail.c
+++ b/tools/testing/selftests/bpf/progs/percpu_alloc_fail.c
@@ -33,6 +33,20 @@ struct {
__type(value, struct elem);
} array SEC(".maps");
+struct kernel_percpu_elem {
+ struct task_struct __percpu_kptr *task;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, int);
+ __type(value, struct kernel_percpu_elem);
+} kernel_percpu_array SEC(".maps");
+
+struct task_struct *bpf_task_from_pid(s32 pid) __ksym;
+void bpf_task_release(struct task_struct *p) __ksym;
+
long ret;
SEC("?fentry/bpf_fentry_test1")
@@ -137,6 +151,51 @@ int BPF_PROG(test_array_map_5)
return 0;
}
+SEC("?syscall")
+__failure __msg("invalid kptr access, R2 type=trusted_ptr_ expected=ptr_task_struct")
+int reject_kernel_ptr_into_percpu_kptr(void *ctx)
+{
+ struct kernel_percpu_elem *e;
+ struct task_struct *p, *old;
+ int index = 0;
+
+ e = bpf_map_lookup_elem(&kernel_percpu_array, &index);
+ if (!e)
+ return 0;
+
+ p = bpf_task_from_pid(1);
+ if (!p)
+ return 0;
+
+ old = bpf_kptr_xchg(&e->task, p);
+ if (old)
+ bpf_task_release(old);
+ return 0;
+}
+
+SEC("?fentry.s/bpf_fentry_test1")
+__failure __msg("invalid kptr access, R2 type=ptr_ expected=ptr_val_t")
+int BPF_PROG(reject_plain_alloc_into_percpu_kptr)
+{
+ struct val_t __percpu_kptr *old;
+ struct val_t *p;
+ struct elem *e;
+ int index = 0;
+
+ e = bpf_map_lookup_elem(&array, &index);
+ if (!e)
+ return 0;
+
+ p = bpf_obj_new(struct val_t);
+ if (!p)
+ return 0;
+
+ old = bpf_kptr_xchg(&e->pc, p);
+ if (old)
+ bpf_percpu_obj_drop(old);
+ return 0;
+}
+
SEC("?fentry.s/bpf_fentry_test1")
__failure __msg("bpf_percpu_obj_new type ID argument must be of a struct of scalars")
int BPF_PROG(test_array_map_6)
diff --git a/tools/testing/selftests/bpf/progs/preempt_lock.c b/tools/testing/selftests/bpf/progs/preempt_lock.c
index 6d5fce7e6ffc..81c459435680 100644
--- a/tools/testing/selftests/bpf/progs/preempt_lock.c
+++ b/tools/testing/selftests/bpf/progs/preempt_lock.c
@@ -115,6 +115,58 @@ int preempt_sleepable_helper(void *ctx)
return 0;
}
+SEC("?uprobe.s")
+__failure __msg("sleepable helper bpf_get_stack#")
+int preempt_sleepable_get_stack(struct pt_regs *ctx)
+{
+ struct bpf_stack_build_id stack;
+
+ bpf_preempt_disable();
+ bpf_get_stack(ctx, &stack, sizeof(stack),
+ BPF_F_USER_STACK | BPF_F_USER_BUILD_ID);
+ bpf_preempt_enable();
+ return 0;
+}
+
+SEC("?uprobe.s")
+__failure __msg("sleepable helper bpf_get_task_stack#")
+int preempt_sleepable_get_task_stack(void *ctx)
+{
+ struct bpf_stack_build_id stack;
+ struct task_struct *task;
+
+ task = bpf_get_current_task_btf();
+ bpf_preempt_disable();
+ bpf_get_task_stack(task, &stack, sizeof(stack),
+ BPF_F_USER_STACK | BPF_F_USER_BUILD_ID);
+ bpf_preempt_enable();
+ return 0;
+}
+
+SEC("?uprobe.s")
+__success
+int sleepable_get_stack(struct pt_regs *ctx)
+{
+ struct bpf_stack_build_id stack;
+
+ bpf_get_stack(ctx, &stack, sizeof(stack),
+ BPF_F_USER_STACK | BPF_F_USER_BUILD_ID);
+ return 0;
+}
+
+SEC("?uprobe.s")
+__success
+int sleepable_get_task_stack(void *ctx)
+{
+ struct bpf_stack_build_id stack;
+ struct task_struct *task;
+
+ task = bpf_get_current_task_btf();
+ bpf_get_task_stack(task, &stack, sizeof(stack),
+ BPF_F_USER_STACK | BPF_F_USER_BUILD_ID);
+ return 0;
+}
+
SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
__failure __msg("kernel func bpf_copy_from_user_str is sleepable within non-preemptible region")
int preempt_sleepable_kfunc(void *ctx)
diff --git a/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c b/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c
index 0d58114a4955..725d73c9ffe1 100644
--- a/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c
+++ b/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c
@@ -22,3 +22,56 @@ int test_raw_tp_null_sched_pi_setprio_arg_2(void *ctx) {
asm volatile("r1 = *(u64 *)(r1 +8); r1 = *(u64 *)(r1 +0);" ::: __clobber_all);
return 0;
}
+
+/* Plain raw tracepoint arguments remain scalar values. */
+SEC("raw_tp/signal_generate")
+__success
+int test_raw_tp_signal_generate_info_scalar(void *ctx)
+{
+ asm volatile("r1 = *(u64 *)(r1 +8); if r1 != 1 goto +0;" ::: __clobber_all);
+ return 0;
+}
+
+/* tp_btf programs may inspect the sentinel as a scalar value. */
+SEC("tp_btf/signal_generate")
+__success
+int test_tp_btf_signal_generate_info_scalar(void *ctx)
+{
+ asm volatile("r1 = *(u64 *)(r1 +8); if r1 != 1 goto +0;" ::: __clobber_all);
+ return 0;
+}
+
+/* SEND_SIG_PRIV is non-NULL, so a NULL check cannot make info safe. */
+SEC("tp_btf/signal_generate")
+__failure __msg("R1 invalid mem access 'scalar'")
+int test_tp_btf_signal_generate_info_no_deref(void *ctx)
+{
+ asm volatile("r1 = *(u64 *)(r1 +8); if r1 == 0 goto +1; "
+ "r1 = *(u32 *)(r1 +0);" ::: __clobber_all);
+ return 0;
+}
+
+SEC("tp_btf/signal_deliver")
+__failure __msg("R1 invalid mem access 'scalar'")
+int test_tp_btf_signal_deliver_info_no_deref(void *ctx)
+{
+ asm volatile("r1 = *(u64 *)(r1 +8); r1 = *(u32 *)(r1 +0);" ::: __clobber_all);
+ return 0;
+}
+
+SEC("tp_btf/sched_process_wait")
+__failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'")
+int test_raw_tp_null_sched_process_wait_arg_1(void *ctx)
+{
+ asm volatile("r1 = *(u64 *)(r1 +0); r1 = *(u32 *)(r1 +0);" ::: __clobber_all);
+ return 0;
+}
+
+SEC("tp_btf/sched_process_wait")
+__success
+int test_raw_tp_null_sched_process_wait_arg_1_checked(void *ctx)
+{
+ asm volatile("r1 = *(u64 *)(r1 +0); if r1 == 0 goto +1; "
+ "r1 = *(u32 *)(r1 +0);" ::: __clobber_all);
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/rbtree_fail.c b/tools/testing/selftests/bpf/progs/rbtree_fail.c
index 555379952dcc..4504608196ab 100644
--- a/tools/testing/selftests/bpf/progs/rbtree_fail.c
+++ b/tools/testing/selftests/bpf/progs/rbtree_fail.c
@@ -16,6 +16,7 @@ struct node_data {
private(A) struct bpf_spin_lock glock;
private(A) struct bpf_rb_root groot __contains(node_data, node);
private(A) struct bpf_rb_root groot2 __contains(node_data, node);
+private(B) struct bpf_res_spin_lock res_glock;
static bool less(struct bpf_rb_node *a, const struct bpf_rb_node *b)
{
@@ -265,6 +266,53 @@ static bool less__bad_fn_call_first_unlock_after(struct bpf_rb_node *a, const st
return node_a->key < node_b->key;
}
+static bool less__bad_res_spin_unlock(struct bpf_rb_node *a, const struct bpf_rb_node *b)
+{
+ bpf_res_spin_unlock(&res_glock);
+ return false;
+}
+
+static __noinline void rbtree_cb_unlock_relock(void)
+{
+ bpf_spin_unlock(&glock);
+ bpf_spin_lock(&glock);
+}
+
+static __noinline void rbtree_cb_nested_unlock(void)
+{
+ rbtree_cb_unlock_relock();
+ asm volatile ("");
+}
+
+static bool less__bad_subprog_unlock(struct bpf_rb_node *a, const struct bpf_rb_node *b)
+{
+ struct node_data *node_a;
+ struct node_data *node_b;
+
+ node_a = container_of(a, struct node_data, node);
+ node_b = container_of(b, struct node_data, node);
+ rbtree_cb_nested_unlock();
+
+ return node_a->key < node_b->key;
+}
+
+static __noinline void rbtree_cb_noop(void)
+{
+ asm volatile ("");
+}
+
+static bool less__subprog_allowed(struct bpf_rb_node *a, const struct bpf_rb_node *b)
+{
+ struct node_data *node_a;
+ struct node_data *node_b;
+
+ node_a = container_of(a, struct node_data, node);
+ node_b = container_of(b, struct node_data, node);
+ rbtree_cb_noop();
+
+ return node_a->key < node_b->key;
+}
+
static __always_inline
long add_with_cb(bool (cb)(struct bpf_rb_node *a, const struct bpf_rb_node *b))
{
@@ -301,4 +349,40 @@ long rbtree_api_add_bad_cb_bad_fn_call_first_unlock_after(void *ctx)
return add_with_cb(less__bad_fn_call_first_unlock_after);
}
+SEC("?tc")
+__failure __msg("can't res_spin_{lock,unlock} in rbtree cb")
+long rbtree_api_add_bad_cb_res_spin_unlock(void *ctx)
+{
+ struct node_data *n;
+
+ n = bpf_obj_new(typeof(*n));
+ if (!n)
+ return 1;
+
+ bpf_spin_lock(&glock);
+ if (bpf_res_spin_lock(&res_glock)) {
+ bpf_spin_unlock(&glock);
+ bpf_obj_drop(n);
+ return 1;
+ }
+ bpf_rbtree_add(&groot, &n->node, less__bad_res_spin_unlock);
+ bpf_res_spin_unlock(&res_glock);
+ bpf_spin_unlock(&glock);
+ return 0;
+}
+
+SEC("?tc")
+__failure __msg("can't spin_{lock,unlock} in rbtree cb")
+long rbtree_api_add_bad_cb_subprog_unlock(void *ctx)
+{
+ return add_with_cb(less__bad_subprog_unlock);
+}
+
+SEC("?tc")
+__success
+long rbtree_api_add_cb_subprog_allowed(void *ctx)
+{
+ return add_with_cb(less__subprog_allowed);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/rcu_read_lock.c b/tools/testing/selftests/bpf/progs/rcu_read_lock.c
index 31d4081c3a9f..cdb255addbc3 100644
--- a/tools/testing/selftests/bpf/progs/rcu_read_lock.c
+++ b/tools/testing/selftests/bpf/progs/rcu_read_lock.c
@@ -592,9 +592,9 @@ int non_own_ref_untrusted_ld(void *ctx)
}
bpf_rcu_read_unlock();
/*
- * The unlock leaves node as PTR_TO_BTF_ID | MEM_ALLOC | PTR_UNTRUSTED
- * | NON_OWN_REF, and the load below has to get the BPF_PROBE_MEM
- * rewrite for it, otherwise a bad address panics the kernel.
+ * The unlock leaves node as PTR_TO_BTF_ID | MEM_ALLOC | PTR_UNTRUSTED,
+ * and the load below has to get the BPF_PROBE_MEM rewrite for it,
+ * otherwise a bad address panics the kernel.
*/
non_own_ref_key = node->key;
return 0;
diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr.c b/tools/testing/selftests/bpf/progs/refcounted_kptr.c
index 61906f48025c..cae00f7b0a24 100644
--- a/tools/testing/selftests/bpf/progs/refcounted_kptr.c
+++ b/tools/testing/selftests/bpf/progs/refcounted_kptr.c
@@ -23,6 +23,15 @@ struct map_value {
struct node_data __kptr *node;
};
+struct node_refcount_only {
+ long key;
+ struct bpf_refcount refcount;
+};
+
+struct map_value_refcount_only {
+ struct node_refcount_only __kptr *node;
+};
+
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, int);
@@ -30,6 +39,13 @@ struct {
__uint(max_entries, 2);
} stashed_nodes SEC(".maps");
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, int);
+ __type(value, struct map_value_refcount_only);
+ __uint(max_entries, 1);
+} stashed_refcount_only SEC(".maps");
+
struct node_acquire {
long key;
long data;
@@ -832,6 +848,51 @@ long rbtree_refcounted_node_ref_escapes_owning_input(void *ctx)
return 0;
}
+SEC("tc")
+__success
+long refcount_acquire_owning_input_no_null_check(void *ctx)
+{
+ struct node_refcount_only *n, *m;
+
+ n = bpf_obj_new(typeof(*n));
+ if (!n)
+ return 1;
+
+ m = bpf_refcount_acquire(n);
+ bpf_obj_drop(m);
+ bpf_obj_drop(n);
+
+ return 0;
+}
+
+SEC("?syscall")
+__success
+long refcount_acquire_rcu_map_kptr_null_checked(void *ctx)
+{
+ struct map_value_refcount_only *mapval;
+ struct node_refcount_only *n, *m;
+ int idx = 0;
+
+ mapval = bpf_map_lookup_elem(&stashed_refcount_only, &idx);
+ if (!mapval)
+ return 1;
+
+ bpf_rcu_read_lock();
+ n = mapval->node;
+ if (!n) {
+ bpf_rcu_read_unlock();
+ return 2;
+ }
+ m = bpf_refcount_acquire(n);
+ bpf_rcu_read_unlock();
+
+ if (!m)
+ return 3;
+ bpf_obj_drop(m);
+
+ return 0;
+}
+
static long __stash_map_empty_xchg(struct node_data *n, int idx)
{
struct map_value *mapval = bpf_map_lookup_elem(&stashed_nodes, &idx);
diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
index eaaed0859f94..338e43822ffe 100644
--- a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
@@ -19,6 +19,26 @@ struct node_refcounted {
struct bpf_refcount refcount;
};
+struct node_refcount_only {
+ long key;
+ struct bpf_refcount refcount;
+};
+
+struct map_value_refcount_only {
+ struct node_refcount_only __kptr *node;
+};
+
+struct rcu_graph_node {
+ struct bpf_rb_node node;
+ long data;
+};
+
+struct rcu_graph_node *just_here_because_btf_bug;
+
+struct map_value_rcu_graph {
+ struct rcu_graph_node __kptr *node;
+};
+
extern void bpf_rcu_read_lock(void) __ksym;
extern void bpf_rcu_read_unlock(void) __ksym;
@@ -27,6 +47,22 @@ private(A) struct bpf_spin_lock glock;
private(A) struct bpf_rb_root groot __contains(node_acquire, node);
private(B) struct bpf_spin_lock lock;
private(B) struct bpf_list_head head __contains(node_refcounted, list);
+private(C) struct bpf_spin_lock graph_lock;
+private(C) struct bpf_rb_root graph_root __contains(rcu_graph_node, node);
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, int);
+ __type(value, struct map_value_refcount_only);
+ __uint(max_entries, 1);
+} stashed_refcount_only SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, int);
+ __type(value, struct map_value_rcu_graph);
+ __uint(max_entries, 1);
+} stashed_rcu_graph SEC(".maps");
static bool less(struct bpf_rb_node *a, const struct bpf_rb_node *b)
{
@@ -89,6 +125,120 @@ long refcount_acquire_non_object(void *ctx)
return bpf_refcount_acquire(ctx) != NULL;
}
+SEC("?syscall")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
+long refcount_acquire_rcu_map_kptr_unchecked_drop(void *ctx)
+{
+ struct map_value_refcount_only *mapval;
+ struct node_refcount_only *tmp, *n, *m;
+ int idx = 0;
+
+ /* Force Clang to emit complete BTF for struct node_refcount_only. */
+ tmp = bpf_obj_new(typeof(*tmp));
+ if (!tmp)
+ return 3;
+ bpf_obj_drop(tmp);
+
+ mapval = bpf_map_lookup_elem(&stashed_refcount_only, &idx);
+ if (!mapval)
+ return 1;
+
+ bpf_rcu_read_lock();
+ n = mapval->node;
+ if (!n) {
+ bpf_rcu_read_unlock();
+ return 2;
+ }
+ m = bpf_refcount_acquire(n);
+ bpf_rcu_read_unlock();
+
+ bpf_obj_drop(m);
+
+ return 0;
+}
+
+SEC("?syscall")
+__failure
+__msg("bpf_rbtree_remove can only take non-owning or refcounted "
+ "bpf_rb_node pointer")
+long rbtree_remove_after_rcu_unlock(void *ctx)
+{
+ struct map_value_rcu_graph *mapval;
+ struct bpf_rb_node *rb_node;
+ struct rcu_graph_node *node;
+ int idx = 0;
+
+ mapval = bpf_map_lookup_elem(&stashed_rcu_graph, &idx);
+ if (!mapval)
+ return 0;
+
+ bpf_rcu_read_lock();
+ node = mapval->node;
+ if (!node) {
+ bpf_rcu_read_unlock();
+ return 0;
+ }
+ bpf_rcu_read_unlock();
+
+ bpf_spin_lock(&graph_lock);
+ rb_node = bpf_rbtree_remove(&graph_root, &node->node);
+ bpf_spin_unlock(&graph_lock);
+ if (rb_node)
+ bpf_obj_drop(container_of(rb_node, struct rcu_graph_node, node));
+
+ return 0;
+}
+
+SEC("?syscall")
+__failure __msg("R1 is neither owning or non-owning ref")
+long refcount_acquire_after_rcu_unlock(void *ctx)
+{
+ struct map_value_refcount_only *mapval;
+ struct node_refcount_only *node, *ref;
+ int idx = 0;
+
+ mapval = bpf_map_lookup_elem(&stashed_refcount_only, &idx);
+ if (!mapval)
+ return 0;
+
+ bpf_rcu_read_lock();
+ node = mapval->node;
+ if (!node) {
+ bpf_rcu_read_unlock();
+ return 0;
+ }
+ bpf_rcu_read_unlock();
+
+ ref = bpf_refcount_acquire(node);
+ if (ref)
+ bpf_obj_drop(ref);
+
+ return 0;
+}
+
+SEC("?syscall")
+__failure __msg("invalid mem access 'scalar'")
+long graph_kptr_after_spin_unlock(void *ctx)
+{
+ struct map_value_rcu_graph *mapval;
+ struct rcu_graph_node *node;
+ int idx = 0;
+
+ mapval = bpf_map_lookup_elem(&stashed_rcu_graph, &idx);
+ if (!mapval)
+ return 0;
+
+ bpf_spin_lock(&graph_lock);
+ node = mapval->node;
+ if (!node) {
+ bpf_spin_unlock(&graph_lock);
+ return 0;
+ }
+ bpf_spin_unlock(&graph_lock);
+
+ return node->data;
+}
+
SEC("?tc")
__failure __msg("Unreleased reference id=3 alloc_insn={{[0-9]+}}")
long rbtree_refcounted_node_ref_escapes_owning_input(void *ctx)
diff --git a/tools/testing/selftests/bpf/progs/rhash.c b/tools/testing/selftests/bpf/progs/rhash.c
index fc2dac3a719e..aea4de8dc781 100644
--- a/tools/testing/selftests/bpf/progs/rhash.c
+++ b/tools/testing/selftests/bpf/progs/rhash.c
@@ -19,6 +19,11 @@ struct elem {
int val;
};
+struct special_elem {
+ struct task_struct __kptr *task;
+ int val;
+};
+
struct {
__uint(type, BPF_MAP_TYPE_RHASH);
__uint(map_flags, BPF_F_NO_PREALLOC);
@@ -27,6 +32,17 @@ struct {
__type(value, struct elem);
} rhmap SEC(".maps");
+struct {
+ __uint(type, BPF_MAP_TYPE_RHASH);
+ __uint(map_flags, BPF_F_NO_PREALLOC);
+ __uint(max_entries, 1);
+ __type(key, int);
+ __type(value, struct special_elem);
+} special_fields SEC(".maps");
+
+extern struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym;
+extern void bpf_task_release(struct task_struct *p) __ksym;
+
SEC("syscall")
int test_rhash_lookup_update(void *ctx)
{
@@ -246,3 +262,99 @@ int test_rhash_delete_nonexistent(void *ctx)
err = 0;
return 0;
}
+
+SEC("syscall")
+int test_rhash_kptr_update(void *ctx)
+{
+ struct special_elem val1 = { .val = 1 };
+ struct special_elem val2 = { .val = 2 };
+ struct task_struct *task, *old;
+ struct special_elem *elem;
+ int key = 0;
+
+ err = 1;
+ if (bpf_map_update_elem(&special_fields, &key, &val1, BPF_NOEXIST))
+ return 1;
+
+ err = 2;
+ elem = bpf_map_lookup_elem(&special_fields, &key);
+ if (!elem)
+ return 2;
+
+ err = 3;
+ task = bpf_task_acquire(bpf_get_current_task_btf());
+ if (!task)
+ return 3;
+
+ err = 4;
+ old = bpf_kptr_xchg(&elem->task, task);
+ if (old) {
+ bpf_task_release(old);
+ return 4;
+ }
+
+ err = 5;
+ if (bpf_map_update_elem(&special_fields, &key, &val2, BPF_EXIST))
+ return 5;
+
+ err = 6;
+ elem = bpf_map_lookup_elem(&special_fields, &key);
+ if (!elem || elem->val != 2)
+ return 6;
+
+ err = 7;
+ old = bpf_kptr_xchg(&elem->task, NULL);
+ if (!old)
+ return 7;
+ bpf_task_release(old);
+
+ err = 8;
+ if (bpf_map_delete_elem(&special_fields, &key))
+ return 8;
+
+ err = 0;
+ return 0;
+}
+
+SEC("syscall")
+int test_rhash_kptr_delete(void *ctx)
+{
+ struct special_elem val = {};
+ struct task_struct *task, *old;
+ struct special_elem *elem;
+ int key = 0;
+
+ err = 1;
+ if (bpf_map_update_elem(&special_fields, &key, &val, BPF_NOEXIST))
+ return 1;
+
+ err = 2;
+ elem = bpf_map_lookup_elem(&special_fields, &key);
+ if (!elem)
+ return 2;
+
+ err = 3;
+ task = bpf_task_acquire(bpf_get_current_task_btf());
+ if (!task)
+ return 3;
+
+ err = 4;
+ old = bpf_kptr_xchg(&elem->task, task);
+ if (old) {
+ bpf_task_release(old);
+ return 4;
+ }
+
+ err = 5;
+ if (bpf_map_delete_elem(&special_fields, &key))
+ return 5;
+
+ err = 6;
+ old = bpf_kptr_xchg(&elem->task, NULL);
+ if (!old)
+ return 6;
+ bpf_task_release(old);
+
+ err = 0;
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/rhash_timer.c b/tools/testing/selftests/bpf/progs/rhash_timer.c
new file mode 100644
index 000000000000..2e06a463c605
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/rhash_timer.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <errno.h>
+#include <bpf/bpf_helpers.h>
+
+#define CLOCK_MONOTONIC 1
+#define TIMER_NSEC (60ULL * 1000 * 1000 * 1000)
+
+struct timer_value {
+ struct bpf_timer timer;
+ u64 data;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_RHASH);
+ __uint(map_flags, BPF_F_NO_PREALLOC);
+ __uint(max_entries, 1);
+ __type(key, u64);
+ __type(value, struct timer_value);
+} timer_map SEC(".maps");
+
+u64 armed;
+u64 cancelled;
+long timer_init_err;
+long timer_set_callback_err;
+long timer_start_err;
+long timer_cancel_err;
+
+static int timer_cb(void *map, u64 *key, struct timer_value *value)
+{
+ return 0;
+}
+
+static long arm_timer_cb(struct bpf_map *map, u64 *key,
+ struct timer_value *value, void *ctx)
+{
+ u64 key_copy = *key;
+ long err;
+
+ err = bpf_map_delete_elem(map, &key_copy);
+ if (err)
+ return 1;
+
+ err = bpf_timer_init(&value->timer, map, CLOCK_MONOTONIC);
+ if (err) {
+ timer_init_err = err;
+ return 1;
+ }
+
+ err = bpf_timer_set_callback(&value->timer, timer_cb);
+ if (err) {
+ timer_set_callback_err = err;
+ return 1;
+ }
+
+ err = bpf_timer_start(&value->timer, TIMER_NSEC, BPF_F_TIMER_CPU_PIN);
+ if (err) {
+ timer_start_err = err;
+ return 1;
+ }
+
+ __sync_fetch_and_add(&armed, 1);
+ return 1;
+}
+
+static long cancel_timer_cb(struct bpf_map *map, u64 *key,
+ struct timer_value *value, void *ctx)
+{
+ long err;
+
+ err = bpf_timer_cancel(&value->timer);
+ if (err == -EINVAL)
+ return 1;
+ if (err < 0) {
+ timer_cancel_err = err;
+ return 1;
+ }
+
+ __sync_fetch_and_add(&cancelled, 1);
+ return 1;
+}
+
+SEC("syscall")
+int arm_deleted_timer(void *ctx)
+{
+ bpf_for_each_map_elem(&timer_map, arm_timer_cb, NULL, 0);
+ return 0;
+}
+
+SEC("syscall")
+int cancel_recycled_timer(void *ctx)
+{
+ bpf_for_each_map_elem(&timer_map, cancel_timer_cb, NULL, 0);
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/snprintf_btf_void.c b/tools/testing/selftests/bpf/progs/snprintf_btf_void.c
new file mode 100644
index 000000000000..44af80fbbb80
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/snprintf_btf_void.c
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "btf_ptr.h"
+#include <bpf/bpf_helpers.h>
+
+__u32 type_id;
+/* A buffer we own to render the selected type from, kept in bounds. */
+char obj[256];
+char out[64];
+long ret;
+
+SEC("raw_tp/sys_enter")
+int dump_type(void *ctx)
+{
+ struct btf_ptr ptr = {
+ .ptr = obj,
+ .type_id = type_id,
+ .flags = 0,
+ };
+
+ ret = bpf_snprintf_btf(out, sizeof(out), &ptr, sizeof(ptr), 0);
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/tailcall_callback.c b/tools/testing/selftests/bpf/progs/tailcall_callback.c
index c41632cf423b..14fa7a87028e 100644
--- a/tools/testing/selftests/bpf/progs/tailcall_callback.c
+++ b/tools/testing/selftests/bpf/progs/tailcall_callback.c
@@ -45,6 +45,13 @@ int callback_loop(int index, void **cb_ctx)
}
static __noinline
+int callback_tail(int index, void **cb_ctx)
+{
+ bpf_tail_call_static(*cb_ctx, &jmp_table, 0);
+ return 0;
+}
+
+static __noinline
int callback_empty(int index, void *data)
{
return 0;
@@ -78,4 +85,13 @@ int tailcall_callback_2(struct __sk_buff *skb)
return 0;
}
+/* callback with a direct tail call is rejected without a verifier bug */
+SEC("tc")
+__failure __msg("cannot tail call within callback")
+int tailcall_callback_3(struct __sk_buff *skb)
+{
+ bpf_loop(1, callback_tail, &skb, 0);
+ return 0;
+}
+
char __license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/task_local_data.bpf.h b/tools/testing/selftests/bpf/progs/task_local_data.bpf.h
index 0df8a12fd61e..a31a399870be 100644
--- a/tools/testing/selftests/bpf/progs/task_local_data.bpf.h
+++ b/tools/testing/selftests/bpf/progs/task_local_data.bpf.h
@@ -61,6 +61,7 @@
#define TLD_ROUND_UP(x, y) ((((x) - 1) | TLD_ROUND_MASK(x, y)) + 1)
#define TLD_MAX_DATA_CNT (__PAGE_SIZE / sizeof(struct tld_metadata) - 1)
+#define TLD_DATA_SIZE (__PAGE_SIZE - sizeof(__u64))
#ifndef TLD_NAME_LEN
#define TLD_NAME_LEN 62
@@ -189,6 +190,8 @@ static int __tld_fetch_key(struct tld_object *tld_obj, const char *name, int i_s
return start + off;
off += TLD_ROUND_UP(metadata[i].size, 8);
+ if (off > TLD_DATA_SIZE)
+ break;
}
return -cnt;
diff --git a/tools/testing/selftests/bpf/progs/test_bpf_nf.c b/tools/testing/selftests/bpf/progs/test_bpf_nf.c
index df43649ecb78..eda9b7bbab75 100644
--- a/tools/testing/selftests/bpf/progs/test_bpf_nf.c
+++ b/tools/testing/selftests/bpf/progs/test_bpf_nf.c
@@ -190,8 +190,8 @@ nf_ct_test(struct nf_conn *(*lookup_fn)(void *, struct bpf_sock_tuple *, u32,
ct = alloc_fn(ctx, &bpf_tuple, sizeof(bpf_tuple.ipv4), &opts_def,
sizeof(opts_def));
if (ct) {
- __u16 sport = bpf_get_prandom_u32();
- __u16 dport = bpf_get_prandom_u32();
+ __u16 sport = bpf_get_prandom_u32() % 65535 + 1;
+ __u16 dport = bpf_get_prandom_u32() % 65535 + 1;
union nf_inet_addr saddr = {};
union nf_inet_addr daddr = {};
struct nf_conn *ct_ins;
@@ -293,8 +293,8 @@ nf_ct_opts_new_test(struct nf_conn *(*lookup_fn)(void *, struct bpf_sock_tuple *
ct = alloc_fn(ctx, &bpf_tuple, sizeof(bpf_tuple.ipv4), &opts_def,
sizeof(opts_def));
if (ct) {
- __u16 sport = bpf_get_prandom_u32();
- __u16 dport = bpf_get_prandom_u32();
+ __u16 sport = bpf_get_prandom_u32() % 65535 + 1;
+ __u16 dport = bpf_get_prandom_u32() % 65535 + 1;
union nf_inet_addr saddr = {};
union nf_inet_addr daddr = {};
struct nf_conn *ct_ins;
diff --git a/tools/testing/selftests/bpf/progs/timer_mim_reject.c b/tools/testing/selftests/bpf/progs/timer_mim_reject.c
index dd3f1ed6d6e6..83f31138336b 100644
--- a/tools/testing/selftests/bpf/progs/timer_mim_reject.c
+++ b/tools/testing/selftests/bpf/progs/timer_mim_reject.c
@@ -43,7 +43,7 @@ static int timer_cb(void *map, int *key, struct hmap_elem *val)
return 0;
}
-SEC("fentry/bpf_fentry_test1")
+SEC("?fentry/bpf_fentry_test1")
int BPF_PROG(test1, int a)
{
struct hmap_elem init = {};
@@ -72,3 +72,85 @@ int BPF_PROG(test1, int a)
err |= 8;
return 0;
}
+
+struct callback_ctx {
+ void *map;
+};
+
+static int mismatch_iter_cb(void *map, int *key, struct hmap_elem *val, struct callback_ctx *ctx)
+{
+ bpf_timer_init(&val->timer, ctx->map, CLOCK_MONOTONIC);
+ return 0;
+}
+
+static int timer_mismatch_cb(void *map, int *key, struct hmap_elem *val)
+{
+ struct callback_ctx ctx = { .map = map };
+ struct bpf_map *inner_map2;
+ int array_key2 = ARRAY_KEY2;
+
+ inner_map2 = bpf_map_lookup_elem(&outer_arr, &array_key2);
+ if (!inner_map2)
+ return 0;
+ bpf_for_each_map_elem(inner_map2, mismatch_iter_cb, &ctx, 0);
+ return 0;
+}
+
+static int match_iter_cb(void *map, int *key, struct hmap_elem *val, struct callback_ctx *ctx)
+{
+ bpf_timer_init(&val->timer, map, CLOCK_MONOTONIC);
+ return 0;
+}
+
+static int timer_match_cb(void *map, int *key, struct hmap_elem *val)
+{
+ struct callback_ctx ctx = {};
+ struct bpf_map *inner_map2;
+ int array_key2 = ARRAY_KEY2;
+
+ inner_map2 = bpf_map_lookup_elem(&outer_arr, &array_key2);
+ if (!inner_map2)
+ return 0;
+ bpf_for_each_map_elem(inner_map2, match_iter_cb, &ctx, 0);
+ return 0;
+}
+
+SEC("?fentry/bpf_fentry_test1")
+int BPF_PROG(callback_map_uid_mismatch, int a)
+{
+ struct hmap_elem *val;
+ struct bpf_map *inner_map;
+ int array_key = ARRAY_KEY;
+ int hash_key = HASH_KEY;
+
+ inner_map = bpf_map_lookup_elem(&outer_arr, &array_key);
+ if (!inner_map)
+ return 0;
+ val = bpf_map_lookup_elem(inner_map, &hash_key);
+ if (!val)
+ return 0;
+
+ bpf_timer_init(&val->timer, inner_map, CLOCK_MONOTONIC);
+ bpf_timer_set_callback(&val->timer, timer_mismatch_cb);
+ return 0;
+}
+
+SEC("?fentry/bpf_fentry_test1")
+int BPF_PROG(callback_map_uid_match, int a)
+{
+ struct hmap_elem *val;
+ struct bpf_map *inner_map;
+ int array_key = ARRAY_KEY;
+ int hash_key = HASH_KEY;
+
+ inner_map = bpf_map_lookup_elem(&outer_arr, &array_key);
+ if (!inner_map)
+ return 0;
+ val = bpf_map_lookup_elem(inner_map, &hash_key);
+ if (!val)
+ return 0;
+
+ bpf_timer_init(&val->timer, inner_map, CLOCK_MONOTONIC);
+ bpf_timer_set_callback(&val->timer, timer_match_cb);
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c b/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c
index 6bf95550a024..e0926767bbd3 100644
--- a/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c
+++ b/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c
@@ -62,6 +62,104 @@ int timer_sleepable_prog(void *ctx)
return 0;
}
+static int timer_sys_bpf_cb(void *map, int *key, struct bpf_timer *timer)
+{
+ __u64 attr = 0;
+
+ bpf_sys_bpf(BPF_MAP_FREEZE, &attr, sizeof(attr));
+ return 0;
+}
+
+SEC("syscall")
+__failure __msg("sleepable helper bpf_sys_bpf#{{[0-9]+}} in non-sleepable prog")
+int timer_sys_bpf_prog(void *ctx)
+{
+ struct timer_elem *val;
+ int key = 0;
+
+ val = bpf_map_lookup_elem(&timer_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_timer_init(&val->t, &timer_map, 0);
+ bpf_timer_set_callback(&val->t, timer_sys_bpf_cb);
+ return 0;
+}
+
+static int timer_sys_close_cb(void *map, int *key, struct bpf_timer *timer)
+{
+ bpf_sys_close(0);
+ return 0;
+}
+
+SEC("syscall")
+__failure __msg("sleepable helper bpf_sys_close#{{[0-9]+}} in non-sleepable prog")
+int timer_sys_close_prog(void *ctx)
+{
+ struct timer_elem *val;
+ int key = 0;
+
+ val = bpf_map_lookup_elem(&timer_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_timer_init(&val->t, &timer_map, 0);
+ bpf_timer_set_callback(&val->t, timer_sys_close_cb);
+ return 0;
+}
+
+static int timer_btf_find_cb(void *map, int *key, struct bpf_timer *timer)
+{
+ char name[] = "task_struct";
+
+ bpf_btf_find_by_name_kind(name, sizeof(name), BTF_KIND_STRUCT, 0);
+ return 0;
+}
+
+SEC("syscall")
+__failure __msg("sleepable helper bpf_btf_find_by_name_kind#{{[0-9]+}} in non-sleepable prog")
+int timer_btf_find_prog(void *ctx)
+{
+ struct timer_elem *val;
+ int key = 0;
+
+ val = bpf_map_lookup_elem(&timer_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_timer_init(&val->t, &timer_map, 0);
+ bpf_timer_set_callback(&val->t, timer_btf_find_cb);
+ return 0;
+}
+
+SEC("syscall")
+__success
+int syscall_sys_bpf_prog(void *ctx)
+{
+ __u64 attr = 0;
+
+ bpf_sys_bpf(BPF_MAP_FREEZE, &attr, sizeof(attr));
+ return 0;
+}
+
+SEC("syscall")
+__success
+int syscall_sys_close_prog(void *ctx)
+{
+ bpf_sys_close(0);
+ return 0;
+}
+
+SEC("syscall")
+__success
+int syscall_btf_find_prog(void *ctx)
+{
+ char name[] = "task_struct";
+
+ bpf_btf_find_by_name_kind(name, sizeof(name), BTF_KIND_STRUCT, 0);
+ return 0;
+}
+
/* Workqueue tests */
struct wq_elem {
diff --git a/tools/testing/selftests/bpf/progs/verifier_bounds.c b/tools/testing/selftests/bpf/progs/verifier_bounds.c
index 1a273e416fed..df8d5309657e 100644
--- a/tools/testing/selftests/bpf/progs/verifier_bounds.c
+++ b/tools/testing/selftests/bpf/progs/verifier_bounds.c
@@ -2267,6 +2267,47 @@ __naked void deduce64_from_32_wrapping_32bit(void)
: __clobber_all);
}
+/*
+ * Unprivileged variable pointer arithmetic on a PTR_TO_MAP_VALUE whose
+ * offset collapses to a constant. The Spectre-v1 speculative path snapshots
+ * the pointer while its r32 has just been blanked but its offset not yet
+ * synced; the following register move used to trip reg_bounds_sanity_check()
+ * ("const subreg tnum out of sync with range bounds"). With
+ * BPF_F_TEST_REG_INVARIANTS that violation turns into a load failure, so the
+ * unprivileged program must still load.
+ */
+SEC("socket")
+__success __success_unpriv
+__flag(BPF_F_TEST_REG_INVARIANTS)
+__naked void spec_ptr_alu_const_offset(void)
+{
+ asm volatile (" \
+ call %[bpf_ktime_get_ns]; \
+ *(u64*)(r10 - 16) = r0; \
+ r1 = 0; \
+ *(u64*)(r10 - 8) = r1; \
+ r2 = r10; \
+ r2 += -8; \
+ r1 = %[map_hash_8b] ll; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == 0 goto l0_%=; \
+ r1 = *(u64*)(r10 - 16); \
+ r2 = 0x40000000; \
+ if r1 > r2 goto l0_%=; \
+ if r1 s> 1 goto l0_%=; /* r1 in [0, 1] */ \
+ r0 += r1; /* ptr += bounded scalar */ \
+ r9 = r0; /* used to trip the warning */ \
+ *(u8*)(r0 + 0) = r1; \
+l0_%=: r0 = 0; \
+ exit; \
+ "
+ :
+ : __imm(bpf_ktime_get_ns),
+ __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_8b)
+ : __clobber_all);
+}
+
/* Check that range_within() compares cnum ranges, not min/max projections. */
SEC("socket")
__failure __msg("div by zero")
diff --git a/tools/testing/selftests/bpf/progs/verifier_bpf_fastcall.c b/tools/testing/selftests/bpf/progs/verifier_bpf_fastcall.c
index 328cf630210a..a73b837553fb 100644
--- a/tools/testing/selftests/bpf/progs/verifier_bpf_fastcall.c
+++ b/tools/testing/selftests/bpf/progs/verifier_bpf_fastcall.c
@@ -621,6 +621,116 @@ __naked void helper_call_does_not_prevent_bpf_fastcall(void)
: __clobber_all);
}
+/* A jump to the first spill executes the whole pattern, rewrite is safe. */
+SEC("raw_tp")
+__arch_x86_64
+__log_level(4)
+__msg("subprog 0 (jump_to_first_spill) main {{.*}} stack 0")
+__xlated("2: if r0 == 0x2a goto pc+0")
+__xlated("3: r0 = ")
+__xlated("4: r0 = &(void __percpu *)(r0)")
+__success
+__naked void jump_to_first_spill(void)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "r1 = 1;"
+ "if r0 == 42 goto l0_%=;"
+"l0_%=:"
+ "*(u64 *)(r10 - 8) = r1;"
+ "call %[bpf_get_smp_processor_id];"
+ "r1 = *(u64 *)(r10 - 8);"
+ "exit;"
+ :
+ : __imm(bpf_get_prandom_u32),
+ __imm(bpf_get_smp_processor_id)
+ : __clobber_all);
+}
+
+/* A jump to the call skips the spill, the pattern must be kept. */
+SEC("raw_tp")
+__arch_x86_64
+__log_level(4)
+__msg("subprog 0 (jump_to_call) main {{.*}} stack 8")
+__xlated("2: if r0 == 0x2a goto pc+1")
+__xlated("3: *(u64 *)(r10 -8) = r1")
+__xlated("...")
+__xlated("7: r1 = *(u64 *)(r10 -8)")
+__success
+__naked void jump_to_call(void)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "r1 = 1;"
+ "if r0 == 42 goto l0_%=;"
+ "*(u64 *)(r10 - 8) = r1;"
+"l0_%=:"
+ "call %[bpf_get_smp_processor_id];"
+ "r1 = *(u64 *)(r10 - 8);"
+ "exit;"
+ :
+ : __imm(bpf_get_prandom_u32),
+ __imm(bpf_get_smp_processor_id)
+ : __clobber_all);
+}
+
+/* A jump to the fill skips the spill, the pattern must be kept. */
+SEC("raw_tp")
+__arch_x86_64
+__log_level(4)
+__msg("subprog 0 (jump_to_fill) main {{.*}} stack 8")
+__xlated("2: if r0 == 0x2a goto pc+4")
+__xlated("3: *(u64 *)(r10 -8) = r1")
+__xlated("...")
+__xlated("7: r1 = *(u64 *)(r10 -8)")
+__success
+__naked void jump_to_fill(void)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "r1 = 1;"
+ "if r0 == 42 goto l0_%=;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "call %[bpf_get_smp_processor_id];"
+"l0_%=:"
+ "r1 = *(u64 *)(r10 - 8);"
+ "exit;"
+ :
+ : __imm(bpf_get_prandom_u32),
+ __imm(bpf_get_smp_processor_id)
+ : __clobber_all);
+}
+
+/* Same as above, but the fill is entered by an unconditional jump. */
+SEC("raw_tp")
+__arch_x86_64
+__log_level(4)
+__msg("subprog 0 (unconditional_jump_to_fill) main {{.*}} stack 8")
+__xlated("3: *(u64 *)(r10 -8) = r1")
+__xlated("...")
+__xlated("7: r1 = *(u64 *)(r10 -8)")
+__xlated("8: exit")
+__xlated("9: goto pc-3")
+__success
+__naked void unconditional_jump_to_fill(void)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "r1 = 1;"
+ "if r0 == 42 goto l1_%=;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "call %[bpf_get_smp_processor_id];"
+"l0_%=:"
+ "r1 = *(u64 *)(r10 - 8);"
+ "exit;"
+"l1_%=:"
+ "goto l0_%=;"
+ :
+ : __imm(bpf_get_prandom_u32),
+ __imm(bpf_get_smp_processor_id)
+ : __clobber_all);
+}
+
SEC("raw_tp")
__arch_x86_64
__log_level(4)
diff --git a/tools/testing/selftests/bpf/progs/verifier_cfg.c b/tools/testing/selftests/bpf/progs/verifier_cfg.c
index c1f55e1d80a4..3c3bb03e8217 100644
--- a/tools/testing/selftests/bpf/progs/verifier_cfg.c
+++ b/tools/testing/selftests/bpf/progs/verifier_cfg.c
@@ -3,6 +3,7 @@
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
+#include "../../../include/linux/filter.h"
#include "bpf_misc.h"
SEC("socket")
@@ -56,6 +57,19 @@ __naked void out_of_range_jump2(void)
}
SEC("socket")
+__description("invalid DW LDSX instruction in diagnostics")
+__failure __msg("BUG_ldx_99")
+__log_level(2)
+__naked void invalid_dw_ldsx(void)
+{
+ asm volatile (" \
+ .8byte %[ldsx_dw]; \
+" :
+ : __imm_insn(ldsx_dw, BPF_RAW_INSN(BPF_LDX | BPF_MEMSX | BPF_DW, BPF_REG_0, BPF_REG_0, 0, 0))
+ : __clobber_all);
+}
+
+SEC("socket")
__description("loop (back-edge)")
__failure __msg("unreachable insn 1")
__msg_unpriv("back-edge")
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_iterating_callbacks.c b/tools/testing/selftests/bpf/progs/verifier_iterating_callbacks.c
index 75dd922e4e9f..1fbcc5228306 100644
--- a/tools/testing/selftests/bpf/progs/verifier_iterating_callbacks.c
+++ b/tools/testing/selftests/bpf/progs/verifier_iterating_callbacks.c
@@ -169,6 +169,23 @@ static int iter_limit_cb(__u32 idx, struct num_context *ctx)
}
SEC("?raw_tp")
+__failure __msg("R1 type=ctx expected=scalar")
+__naked void bpf_loop_reject_pointer(void)
+{
+ asm volatile (
+ "r2 = %[iter_limit_cb];"
+ "r3 = 0;"
+ "r4 = 0;"
+ "call %[bpf_loop];"
+ "exit;"
+ :
+ : __imm_ptr(iter_limit_cb),
+ __imm(bpf_loop)
+ : __clobber_common
+ );
+}
+
+SEC("?raw_tp")
__success
int bpf_loop_iter_limit_ok(void *unused)
{
diff --git a/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c b/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c
index 3d1e8de4390c..3c789c565b18 100644
--- a/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c
+++ b/tools/testing/selftests/bpf/progs/verifier_jeq_infer_not_null.c
@@ -3,7 +3,9 @@
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
+#include <stdbool.h>
#include "bpf_misc.h"
+#include "bpf_kfuncs.h"
struct {
__uint(type, BPF_MAP_TYPE_XSKMAP);
@@ -12,6 +14,13 @@ struct {
__type(value, int);
} map_xskmap SEC(".maps");
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(max_entries, 1);
+ __type(key, int);
+ __type(value, int);
+} map_hash SEC(".maps");
+
/* This is equivalent to the following program:
*
* r6 = skb->sk;
@@ -264,4 +273,188 @@ __naked void jne_reg_reg_null_check(void)
: __clobber_all);
}
+/*
+ * A comparison between PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED and
+ * PTR_TO_MAP_VALUE_OR_NULL should not infer that map pointer is not null.
+ * A bug in check_cond_jmp_op() made such inference possible.
+ */
+SEC("raw_tp")
+__failure
+__msg("error: invalid dereference of R0 (a nullable map value pointer)")
+__msg(">>> 11 | (61) r0 = *(u32 *)(r0 +0)")
+__naked void untrusted_mem_does_not_infer_map_value_non_null(void)
+{
+ asm volatile (" \
+ /* r6 = bpf_rdonly_cast(0, 0); */ \
+ r1 = 0; \
+ r2 = 0; \
+ call %[bpf_rdonly_cast]; \
+ r6 = r0; \
+ /* r0 = bpf_map_lookup_elem(map_hash, &key); */ \
+ *(u64 *)(r10 - 8) = 0; \
+ r1 = %[map_hash] ll; \
+ r2 = r10; \
+ r2 += -8; \
+ call %[bpf_map_lookup_elem]; \
+ /* \
+ * buggy verifier assumed that r6 can't be null \
+ * and marked r0 non-null as well. \
+ */ \
+ if r6 != r0 goto 1f; \
+ r0 = *(u32 *)(r0 + 0); \
+1: r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_rdonly_cast),
+ __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash)
+ : __clobber_all);
+}
+
+/*
+ * A pointer with an offset that is not bounded from above may be null at
+ * runtime, hence it is not a witness for the pointer it is compared with.
+ */
+SEC("socket")
+__failure
+__msg("error: invalid dereference of R7 (a nullable map value pointer)")
+__naked void unbounded_offset_does_not_infer_map_value_non_null(void)
+{
+ asm volatile (" \
+ /* r6 = bpf_map_lookup_elem(map_hash, &0); */ \
+ *(u64 *)(r10 - 8) = 0; \
+ r1 = %[map_hash] ll; \
+ r2 = r10; \
+ r2 += -8; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == 0 goto 1f; \
+ r6 = r0; \
+ /* r7 = bpf_map_lookup_elem(map_hash, &1); */ \
+ *(u64 *)(r10 - 8) = 1; \
+ r1 = %[map_hash] ll; \
+ r2 = r10; \
+ r2 += -8; \
+ call %[bpf_map_lookup_elem]; \
+ r7 = r0; \
+ /* pointer - pointer is an unknown scalar */ \
+ r8 = r7; \
+ r8 -= r6; \
+ /* r8 is in [0, S64_MAX] */ \
+ r8 <<= 1; \
+ r8 >>= 1; \
+ /* r6 may wrap to zero at runtime */ \
+ r6 += r8; \
+ if r7 != r6 goto 1f; \
+ r0 = *(u8 *)(r7 + 0); \
+1: r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash)
+ : __clobber_all);
+}
+
+/* Same, but the offset is bounded, so the inference is still done. */
+SEC("socket")
+__success
+__naked void bounded_offset_infers_map_value_non_null(void)
+{
+ asm volatile (" \
+ /* r6 = bpf_map_lookup_elem(map_hash, &0); */ \
+ *(u64 *)(r10 - 8) = 0; \
+ r1 = %[map_hash] ll; \
+ r2 = r10; \
+ r2 += -8; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == 0 goto 1f; \
+ r6 = r0; \
+ /* r7 = bpf_map_lookup_elem(map_hash, &1); */ \
+ *(u64 *)(r10 - 8) = 1; \
+ r1 = %[map_hash] ll; \
+ r2 = r10; \
+ r2 += -8; \
+ call %[bpf_map_lookup_elem]; \
+ r7 = r0; \
+ /* pointer - pointer is an unknown scalar */ \
+ r8 = r7; \
+ r8 -= r6; \
+ /* r8 is in [0, 3] */ \
+ r8 &= 3; \
+ r6 += r8; \
+ if r7 != r6 goto 1f; \
+ r0 = *(u8 *)(r7 + 0); \
+1: r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash)
+ : __clobber_all);
+}
+
+/*
+ * The low 32 bits of a map value pointer may be zero, hence a 32-bit
+ * compare with zero cannot be predicted from the pointer being non-NULL
+ * and both successors of such a jump have to be verified.
+ */
+SEC("socket")
+__failure __msg("invalid access to map value, value_size=4 off=32 size=4")
+__naked void jmp32_ptr_vs_zero_jne(void)
+{
+ asm volatile (" \
+ /* r0 = bpf_map_lookup_elem(map_hash, &key); */ \
+ *(u64 *)(r10 - 8) = 0; \
+ r1 = %[map_hash] ll; \
+ r2 = r10; \
+ r2 += -8; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == 0 goto 1f; \
+ if w0 != 0 goto 1f; \
+ r0 = *(u32 *)(r0 + 32); \
+1: r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash)
+ : __clobber_all);
+}
+
+/*
+ * The below program is explored in two paths: r6 == 0 and r6 == 1.
+ * On the first path comparison "if r0 == r6 goto 2f" should mark r6 as precise,
+ * otherwise unsafe path with r6 == 1 would be incorrectly pruned.
+ */
+SEC("socket")
+__failure
+__flag(BPF_F_TEST_STATE_FREQ)
+__msg("error: invalid dereference of R0 (a nullable map value pointer)")
+__naked void imprecise_zero_does_not_infer_map_value_non_null(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ /* r6 is 0 on the path explored first, 1 on the other */\
+ r6 = 1; \
+ if r0 == 0 goto 1f; \
+ r6 = 0; \
+ /* r0 = bpf_map_lookup_elem(map_hash, &0); */ \
+1: *(u64 *)(r10 - 8) = 0; \
+ r1 = %[map_hash] ll; \
+ r2 = r10; \
+ r2 += -8; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == r6 goto 2f; \
+ r0 = *(u8 *)(r0 + 0); \
+2: r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32),
+ __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash)
+ : __clobber_all);
+}
+
+void kfunc_root(void)
+{
+ bpf_rdonly_cast(0, 0);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_ld_ind.c b/tools/testing/selftests/bpf/progs/verifier_ld_ind.c
index 09e81b99eecb..32989f981fb6 100644
--- a/tools/testing/selftests/bpf/progs/verifier_ld_ind.c
+++ b/tools/testing/selftests/bpf/progs/verifier_ld_ind.c
@@ -194,6 +194,102 @@ __naked void ld_ind_subprog_both_paths_safe(void)
::: __clobber_all);
}
+__naked __noinline __used
+static int ld_abs_callback(void)
+{
+ asm volatile (
+ "r6 = *(u64 *)(r2 + 0);"
+ ".8byte %[ld_abs];"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(ld_abs, BPF_LD_ABS(BPF_W, 0))
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("ld_abs: reject in callback")
+__failure __msg("cannot use BPF_LD_[ABS|IND] within callback")
+int ld_abs_callback_reject(struct __sk_buff *skb)
+{
+ bpf_loop(1, ld_abs_callback, &skb, 0);
+ return 0;
+}
+
+__naked __noinline __used
+static int ld_ind_callback_subprog(void)
+{
+ asm volatile (
+ "r6 = r1;"
+ "r7 = 0;"
+ ".8byte %[ld_ind];"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(ld_ind, BPF_LD_IND(BPF_W, BPF_REG_7, 0))
+ : __clobber_all);
+}
+
+__naked __noinline __used
+static int ld_ind_callback(void)
+{
+ asm volatile (
+ "r1 = *(u64 *)(r2 + 0);"
+ "call ld_ind_callback_subprog;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+SEC("socket")
+__description("ld_ind: reject in callback subprog")
+__failure __msg("cannot use BPF_LD_[ABS|IND] within callback")
+int ld_ind_callback_subprog_reject(struct __sk_buff *skb)
+{
+ bpf_loop(1, ld_ind_callback, &skb, 0);
+ return 0;
+}
+
+static __noinline int ld_ind_global_static(struct __sk_buff *skb)
+{
+ asm volatile (
+ "r6 = %[skb];"
+ "r7 = 0;"
+ ".8byte %[ld_ind];"
+ :
+ : [skb] "r"(skb),
+ __imm_insn(ld_ind, BPF_LD_IND(BPF_W, BPF_REG_7, 0))
+ : __clobber_common, "r6", "r7");
+ return skb->mark;
+}
+
+__noinline int ld_ind_global(struct __sk_buff *skb)
+{
+ return ld_ind_global_static(skb);
+}
+
+static int ld_ind_global_callback(__u32 index, struct __sk_buff **ctx)
+{
+ ld_ind_global(*ctx);
+ return 0;
+}
+
+SEC("socket")
+__description("ld_ind: reject in callback global subprog")
+__failure __msg("cannot use BPF_LD_[ABS|IND] within callback")
+int ld_ind_global_callback_reject(struct __sk_buff *skb)
+{
+ bpf_loop(1, ld_ind_global_callback, &skb, 0);
+ return 0;
+}
+
+SEC("socket")
+__description("ld_ind: allow in non-callback global subprog")
+__success
+int ld_ind_global_subprog_ok(struct __sk_buff *skb)
+{
+ return ld_ind_global(skb);
+}
+
/*
* ld_{abs,ind} in subprogs require scalar (int) return type in BTF.
* A test with void return must be rejected.
diff --git a/tools/testing/selftests/bpf/progs/verifier_netfilter_ctx.c b/tools/testing/selftests/bpf/progs/verifier_netfilter_ctx.c
index e2cbc5bda65e..b5d7f567d0d4 100644
--- a/tools/testing/selftests/bpf/progs/verifier_netfilter_ctx.c
+++ b/tools/testing/selftests/bpf/progs/verifier_netfilter_ctx.c
@@ -113,4 +113,82 @@ int with_valid_ctx_access_test6(struct bpf_nf_ctx *ctx)
return th->dest == bpf_htons(22) ? NF_ACCEPT : NF_DROP;
}
+SEC("netfilter")
+__description("netfilter test prog with skb write access")
+__failure __msg("only read is supported")
+int skb_len_write(struct bpf_nf_ctx *ctx)
+{
+ ctx->skb->len = 1;
+ return 1;
+}
+
+SEC("netfilter")
+__description("netfilter test prog with skb data write access")
+__failure __msg("cannot write into rdonly_untrusted_mem")
+int skb_data_write(struct bpf_nf_ctx *ctx)
+{
+ ctx->skb->data[0] = 0;
+ return 1;
+}
+
+SEC("netfilter")
+__description("netfilter test prog with bpf_dynptr_write")
+__success __failure_unpriv
+__retval(0)
+int with_dynptr_write(struct bpf_nf_ctx *ctx)
+{
+ struct __sk_buff *skb = (struct __sk_buff *)ctx->skb;
+ struct bpf_dynptr ptr;
+ u8 buffer[1] = {};
+
+ if (bpf_dynptr_from_skb(skb, 0, &ptr))
+ return 1;
+
+ if (bpf_dynptr_write(&ptr, 0, buffer, sizeof(buffer), 0))
+ return 0; /* must always fail */
+
+ return 1;
+}
+
+SEC("netfilter")
+__description("netfilter test prog with bpf_dynptr_slice_rdwr")
+__failure __msg("the prog does not allow writes to packet data")
+int with_dynptr_rdwr(struct bpf_nf_ctx *ctx)
+{
+ struct __sk_buff *skb = (struct __sk_buff *)ctx->skb;
+ u8 buffer_iph[20] = {};
+ struct bpf_dynptr ptr;
+ struct iphdr *iph;
+
+ if (bpf_dynptr_from_skb(skb, 0, &ptr))
+ return 1;
+
+ iph = bpf_dynptr_slice_rdwr(&ptr, 0, buffer_iph, sizeof(buffer_iph));
+ if (!iph)
+ return 0;
+
+ return 1;
+}
+
+SEC("netfilter")
+__description("netfilter test prog with bpf_dynptr_slice + write")
+__failure __msg("cannot write into rdonly_mem")
+int with_dynptr_store(struct bpf_nf_ctx *ctx)
+{
+ struct __sk_buff *skb = (struct __sk_buff *)ctx->skb;
+ u8 buffer_iph[20] = {};
+ struct bpf_dynptr ptr;
+ struct iphdr *iph;
+
+ if (bpf_dynptr_from_skb(skb, 0, &ptr))
+ return 1;
+
+ iph = bpf_dynptr_slice(&ptr, 0, buffer_iph, sizeof(buffer_iph));
+ if (!iph)
+ return 0;
+ iph->protocol = 42;
+
+ return 1;
+}
+
char _license[] SEC("license") = "GPL";
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_scalar_ids.c b/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c
index 663d15fc5fd2..256547048cc4 100644
--- a/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c
+++ b/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c
@@ -380,13 +380,14 @@ SEC("socket")
__success __log_level(2)
__flag(BPF_F_TEST_STATE_FREQ)
/*
- * check that r0 and r5 have different IDs after 'if',
- * collect_linked_regs() can't tie more than 5 registers for a single insn.
+ * check that r5 is unlinked after 'if', collect_linked_regs() can't tie
+ * more than 5 registers for a single insn and the register compared by
+ * the jump is not exempt from that.
*/
-__msg("7: (25) if r0 > 0x7 goto pc+0 ; R0=scalar(id=1")
+__msg("7: (25) if r5 > 0x7 goto pc+0 ; R5=scalar(smin=")
__msg("12: (bf) r5 = r5 ; R5=scalar(id=2")
/* check that r{0-4} are marked precise after 'if' */
-__msg("frame0: regs=r0 stack= before 7: (25) if r0 > 0x7 goto pc+0")
+__msg("frame0: regs=r0 stack= before 7: (25) if r5 > 0x7 goto pc+0")
__msg("frame0: parent state regs=r0,r1,r2,r3,r4 stack=:")
__naked void linked_regs_too_many_regs(void)
{
@@ -400,8 +401,8 @@ __naked void linked_regs_too_many_regs(void)
"r3 = r0;"
"r4 = r0;"
"r5 = r0;"
- /* propagate range for r{0-5} */
- "if r0 > 7 goto +0;"
+ /* r{0-4} fill the record, r5 does not fit and is unlinked */
+ "if r5 > 7 goto +0;"
/* keep r{1-4} live */
"r1 = r1;"
"r2 = r2;"
diff --git a/tools/testing/selftests/bpf/progs/verifier_spill_fill.c b/tools/testing/selftests/bpf/progs/verifier_spill_fill.c
index 8b166c42c4e0..39a1766dae3f 100644
--- a/tools/testing/selftests/bpf/progs/verifier_spill_fill.c
+++ b/tools/testing/selftests/bpf/progs/verifier_spill_fill.c
@@ -1403,6 +1403,46 @@ __naked void partial_fill_from_cleaned_pointer_spill(void)
::: __clobber_all);
}
+SEC("raw_tp")
+__failure
+__msg("access may be outside object bounds")
+__flag(BPF_F_TEST_STATE_FREQ)
+__naked void imprecise_scalar_spill_half_dead(void)
+{
+ asm volatile (
+ /*
+ * Fork two paths: the one explored first spills an imprecise zero,
+ * the one explored second, an imprecise non-zero scalar.
+ */
+ "call %[bpf_get_prandom_u32];"
+ "if r0 > 42 goto 1f;"
+ "r6 = 0;"
+ "goto 2f;"
+"1:"
+ /* causes out of bounds access on a second path. */
+ "r6 = 100500;"
+"2:"
+ /* Force a checkpoint before the spill. */
+ "goto +0;"
+ "*(u64 *)(r10 - 8) = r6;"
+ /*
+ * Force stack cleanup, only the low half of the spill is alive,
+ * so the dead high half is degraded to raw stack bytes.
+ * Buggy verifier converted it to STACK_ZERO w/o proper precision propagation.
+ */
+ "goto +0;"
+ "r7 = *(u32 *)(r10 - 4);"
+ /* Use r7 as an offset into a one-byte buffer. */
+ "r1 = %[single_byte_buf] ll;"
+ "r1 += r7;"
+ "r0 = *(u8 *)(r1 + 0);"
+ "exit;"
+:
+: __imm(bpf_get_prandom_u32),
+ __imm_addr(single_byte_buf)
+: __clobber_all);
+}
+
/* check valid spill/fill, ptr to tp buffer */
SEC("raw_tracepoint.w")
__success
diff --git a/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c b/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c
index d21d32f6a676..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=:")
@@ -846,4 +846,55 @@ __naked int subprog_result_tail_call(void)
);
}
+__naked __noinline __used
+static int ld_abs_subprog(void)
+{
+ asm volatile (
+ "r6 = r1;"
+ "r7 = r1;"
+ ".8byte %[ld_abs];"
+ "exit;"
+ :
+ : __imm_insn(ld_abs, BPF_LD_ABS(BPF_W, 0))
+ : __clobber_all);
+}
+
+/*
+ * Buggy verifier did not properly backtrack early subprogram exit
+ * modelled for BPF_LD | BPF_ABS instruction, causing a segfault.
+ */
+SEC("socket")
+__success
+__log_level(2)
+/* early exit path */
+__msg("3: (0f) r1 += r7")
+__msg("mark_precise: frame0: regs=r7 stack= before 2: (bf) r1 = r10")
+__msg("mark_precise: frame0: regs=r7 stack= before 9: (20) r0 = *(u32 *)skb[0]")
+__msg("mark_precise: frame1: regs= stack= before 8: (bf) r7 = r1")
+__msg("mark_precise: frame1: regs= stack= before 7: (bf) r6 = r1")
+__msg("mark_precise: frame1: regs= stack= before 1: (85) call pc+5")
+__msg("mark_precise: frame0: regs=r7 stack= before 0: (b7) r7 = -8")
+/* fallthrough path */
+__msg("3: (0f) r1 += r7")
+__msg("mark_precise: frame0: regs=r7 stack= before 2: (bf) r1 = r10")
+__msg("mark_precise: frame0: regs=r7 stack= before 10: (95) exit")
+__msg("mark_precise: frame1: regs= stack= before 9: (20) r0 = *(u32 *)skb[0]")
+__msg("mark_precise: frame1: regs= stack= before 8: (bf) r7 = r1")
+__msg("mark_precise: frame1: regs= stack= before 7: (bf) r6 = r1")
+__msg("mark_precise: frame1: regs= stack= before 1: (85) call pc+5")
+__msg("mark_precise: frame0: regs=r7 stack= before 0: (b7) r7 = -8")
+__naked int ld_abs_backtrack_both_paths(void)
+{
+ asm volatile (
+ "r7 = -8;"
+ "call ld_abs_subprog;"
+ "r1 = r10;"
+ "r1 += r7;" /* mark r7 as precise */
+ "*(u64 *)(r1 + 0) = 0;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all
+ );
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_zext.c b/tools/testing/selftests/bpf/progs/verifier_zext.c
index 8f2362da91d6..572017fe28fb 100644
--- a/tools/testing/selftests/bpf/progs/verifier_zext.c
+++ b/tools/testing/selftests/bpf/progs/verifier_zext.c
@@ -356,6 +356,32 @@ __naked void arena_ptr(void)
: __clobber_all);
}
+/*
+ * Result of a 32-bit cmpxchg is always explicitly zero extended.
+ * Check that this holds for arenas (BPF_PROBE_ATOMIC instruction flavor).
+ */
+SEC("socket")
+__success
+__xlated("probe r0 = atomic_cmpxchg((u32 *)(r1 +0), r0, r2)")
+__xlated("w0 = w0")
+__naked void zext_arena_cmpxchg32(void)
+{
+ asm volatile (" \
+ r9 = %[arena] ll; /* associate the arena with the program */ \
+ r1 = 0; \
+ r1 = addr_space_cast(r1, 0, 1); \
+ r0 = 0; \
+ r2 = 0; \
+ .8byte %[cmpxchg32]; \
+ r0 >>= 32; /* make the upper half live */ \
+ exit; \
+" :
+ : __imm_addr(arena),
+ __imm_insn(cmpxchg32,
+ BPF_ATOMIC_OP(BPF_W, BPF_CMPXCHG, BPF_REG_1, BPF_REG_2, 0))
+ : __clobber_all);
+}
+
#endif
/* Check if probe mem loads keep their zero extension. */
diff --git a/tools/testing/selftests/bpf/verifier/pseudo_func.c b/tools/testing/selftests/bpf/verifier/pseudo_func.c
new file mode 100644
index 000000000000..63c5c67d51de
--- /dev/null
+++ b/tools/testing/selftests/bpf/verifier/pseudo_func.c
@@ -0,0 +1,45 @@
+/*
+ * Buggy verifier accepted the program below while not patching BPF_PSEUDO_FUNC
+ * load instruction to contain a real address. Which resulted in a function call
+ * to a bogus address.
+ */
+{
+ "BPF_PSEUDO_FUNC reference to the main program",
+ .insns = {
+ /* r6 = bpf_map_lookup_elem(&timer_map, &(int){0}); */
+ BPF_ST_MEM(BPF_W, BPF_REG_10, -4, 0),
+ BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
+ BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),
+ BPF_LD_MAP_FD(BPF_REG_1, 0),
+ BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem),
+ BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 10),
+ BPF_MOV64_REG(BPF_REG_6, BPF_REG_0),
+ /* bpf_timer_init(r6, &timer_map, 0); */
+ BPF_MOV64_REG(BPF_REG_1, BPF_REG_6),
+ BPF_LD_MAP_FD(BPF_REG_2, 0),
+ BPF_MOV64_IMM(BPF_REG_3, 0),
+ BPF_EMIT_CALL(BPF_FUNC_timer_init),
+ /* bpf_timer_set_callback(r6, <insn #0>); */
+ BPF_MOV64_REG(BPF_REG_1, BPF_REG_6),
+ BPF_RAW_INSN(BPF_LD | BPF_IMM | BPF_DW, BPF_REG_2, BPF_PSEUDO_FUNC, 0, -15),
+ BPF_RAW_INSN(0, 0, 0, 0, 0),
+ BPF_EMIT_CALL(BPF_FUNC_timer_set_callback),
+ BPF_MOV64_IMM(BPF_REG_0, 0),
+ BPF_EXIT_INSN(),
+ },
+ .prog_type = BPF_PROG_TYPE_TRACEPOINT,
+ .fixup_map_timer = { 3, 9 },
+ .result = REJECT,
+ .errstr = "callback function cannot be the main program",
+ .func_info = { { 0, 4 /* main_prog */ } },
+ .func_info_cnt = 1,
+ .btf_strings = "\0int\0ctx\0main_prog",
+ .btf_types = {
+ /* 1: int */ BTF_TYPE_INT_ENC(1, BTF_INT_SIGNED, 0, 32, 4),
+ /* 2: void* */ BTF_PTR_ENC(0),
+ /* 3: int __(void *) */ BTF_FUNC_PROTO_ENC(1, 1),
+ BTF_FUNC_PROTO_ARG_ENC(5, 2),
+ /* 4: main_prog */ BTF_FUNC_ENC(9, 3),
+ BTF_END_RAW
+ }
+},