summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/bpf/progs
diff options
context:
space:
mode:
Diffstat (limited to 'tools/testing/selftests/bpf/progs')
-rw-r--r--tools/testing/selftests/bpf/progs/arena_atomics.c20
-rw-r--r--tools/testing/selftests/bpf/progs/arena_kfunc.c243
-rw-r--r--tools/testing/selftests/bpf/progs/arena_kfunc_jit.c118
-rw-r--r--tools/testing/selftests/bpf/progs/arena_list.c11
-rw-r--r--tools/testing/selftests/bpf/progs/arena_mem_usage.c40
-rw-r--r--tools/testing/selftests/bpf/progs/arena_spin_lock.c10
-rw-r--r--tools/testing/selftests/bpf/progs/bench_bpf_timing.bpf.h69
-rw-r--r--tools/testing/selftests/bpf/progs/bench_local_storage_create.c11
-rw-r--r--tools/testing/selftests/bpf/progs/bpf_arena_spin_lock.h542
-rw-r--r--tools/testing/selftests/bpf/progs/bpf_cc_cubic.c8
-rw-r--r--tools/testing/selftests/bpf/progs/bpf_cubic.c47
-rw-r--r--tools/testing/selftests/bpf/progs/bpf_for_bench.c32
-rw-r--r--tools/testing/selftests/bpf/progs/bpf_gotox.c31
-rw-r--r--tools/testing/selftests/bpf/progs/bpf_iter_bpf_rhash_map.c34
-rw-r--r--tools/testing/selftests/bpf/progs/bpf_iter_task_vmas.c2
-rw-r--r--tools/testing/selftests/bpf/progs/bpf_iter_unix.c10
-rw-r--r--tools/testing/selftests/bpf/progs/bpf_misc.h79
-rw-r--r--tools/testing/selftests/bpf/progs/bpf_nop_bench.c14
-rw-r--r--tools/testing/selftests/bpf/progs/bpf_qdisc_dynptr_use_after_invalidate_clone.c74
-rw-r--r--tools/testing/selftests/bpf/progs/bpf_qdisc_fail__invalid_dynptr.c68
-rw-r--r--tools/testing/selftests/bpf/progs/bpf_qdisc_fail__invalid_dynptr_cross_frame.c74
-rw-r--r--tools/testing/selftests/bpf/progs/bpf_qdisc_fail__invalid_dynptr_slice.c70
-rw-r--r--tools/testing/selftests/bpf/progs/bpf_qdisc_fail__untrusted_write.c65
-rw-r--r--tools/testing/selftests/bpf/progs/bpf_qdisc_fq.c11
-rw-r--r--tools/testing/selftests/bpf/progs/bpf_smc.c28
-rw-r--r--tools/testing/selftests/bpf/progs/bpf_tracing_net.h5
-rw-r--r--tools/testing/selftests/bpf/progs/btf__stack_arg_precision.c24
-rw-r--r--tools/testing/selftests/bpf/progs/btf__verifier_stack_arg_order.c49
-rw-r--r--tools/testing/selftests/bpf/progs/btf_data.c40
-rw-r--r--tools/testing/selftests/bpf/progs/cgroup_hierarchical_stats.c6
-rw-r--r--tools/testing/selftests/bpf/progs/cgroup_iter_memcg.c45
-rw-r--r--tools/testing/selftests/bpf/progs/cgroup_storage.c43
-rw-r--r--tools/testing/selftests/bpf/progs/cgrp_kfunc_failure.c18
-rw-r--r--tools/testing/selftests/bpf/progs/cgrp_ls_sleepable.c18
-rw-r--r--tools/testing/selftests/bpf/progs/clone_attach_btf_id.c13
-rw-r--r--tools/testing/selftests/bpf/progs/compute_live_registers.c43
-rw-r--r--tools/testing/selftests/bpf/progs/connect_force_port4.c10
-rw-r--r--tools/testing/selftests/bpf/progs/connect_force_port6.c10
-rw-r--r--tools/testing/selftests/bpf/progs/cpumask_common.h2
-rw-r--r--tools/testing/selftests/bpf/progs/cpumask_failure.c43
-rw-r--r--tools/testing/selftests/bpf/progs/cpumask_success.c8
-rw-r--r--tools/testing/selftests/bpf/progs/crypto_bench.c21
-rw-r--r--tools/testing/selftests/bpf/progs/crypto_sanity.c21
-rw-r--r--tools/testing/selftests/bpf/progs/dmabuf_iter.c2
-rw-r--r--tools/testing/selftests/bpf/progs/dynptr_fail.c229
-rw-r--r--tools/testing/selftests/bpf/progs/dynptr_success.c26
-rw-r--r--tools/testing/selftests/bpf/progs/empty_skb.c7
-rw-r--r--tools/testing/selftests/bpf/progs/exceptions.c158
-rw-r--r--tools/testing/selftests/bpf/progs/exceptions_assert.c40
-rw-r--r--tools/testing/selftests/bpf/progs/exceptions_fail.c112
-rw-r--r--tools/testing/selftests/bpf/progs/fentry_sleepable.c18
-rw-r--r--tools/testing/selftests/bpf/progs/fexit_bpf2bpf.c13
-rw-r--r--tools/testing/selftests/bpf/progs/fib_lookup.c57
-rw-r--r--tools/testing/selftests/bpf/progs/file_reader.c4
-rw-r--r--tools/testing/selftests/bpf/progs/file_reader_fail.c64
-rw-r--r--tools/testing/selftests/bpf/progs/free_timer.c10
-rw-r--r--tools/testing/selftests/bpf/progs/freplace_cls_redirect.c34
-rw-r--r--tools/testing/selftests/bpf/progs/freplace_int_with_void.c11
-rw-r--r--tools/testing/selftests/bpf/progs/freplace_void.c10
-rw-r--r--tools/testing/selftests/bpf/progs/fsession_test.c179
-rw-r--r--tools/testing/selftests/bpf/progs/get_func_args_fsession_test.c37
-rw-r--r--tools/testing/selftests/bpf/progs/get_func_args_test.c46
-rw-r--r--tools/testing/selftests/bpf/progs/get_func_ip_fsession_test.c21
-rw-r--r--tools/testing/selftests/bpf/progs/get_smp_processor_id.c20
-rw-r--r--tools/testing/selftests/bpf/progs/htab_reuse.c16
-rw-r--r--tools/testing/selftests/bpf/progs/htab_update.c4
-rw-r--r--tools/testing/selftests/bpf/progs/icmp_send.c123
-rw-r--r--tools/testing/selftests/bpf/progs/irq.c8
-rw-r--r--tools/testing/selftests/bpf/progs/iter_buf_null_fail.c39
-rw-r--r--tools/testing/selftests/bpf/progs/iters.c274
-rw-r--r--tools/testing/selftests/bpf/progs/iters_css.c9
-rw-r--r--tools/testing/selftests/bpf/progs/iters_state_safety.c40
-rw-r--r--tools/testing/selftests/bpf/progs/iters_task_failure.c46
-rw-r--r--tools/testing/selftests/bpf/progs/iters_testmod.c4
-rw-r--r--tools/testing/selftests/bpf/progs/iters_testmod_seq.c16
-rw-r--r--tools/testing/selftests/bpf/progs/kfunc_call_fail.c72
-rw-r--r--tools/testing/selftests/bpf/progs/kfunc_call_test.c110
-rw-r--r--tools/testing/selftests/bpf/progs/kfunc_implicit_args.c41
-rw-r--r--tools/testing/selftests/bpf/progs/kfunc_implicit_args_tracing.c77
-rw-r--r--tools/testing/selftests/bpf/progs/kprobe_multi_override.c15
-rw-r--r--tools/testing/selftests/bpf/progs/kprobe_multi_session.c10
-rw-r--r--tools/testing/selftests/bpf/progs/kprobe_multi_session_cookie.c15
-rw-r--r--tools/testing/selftests/bpf/progs/kprobe_multi_sleepable.c25
-rw-r--r--tools/testing/selftests/bpf/progs/kprobe_write_ctx.c19
-rw-r--r--tools/testing/selftests/bpf/progs/kptr_xchg_inline.c4
-rw-r--r--tools/testing/selftests/bpf/progs/ksock_common.h78
-rw-r--r--tools/testing/selftests/bpf/progs/ksock_lsm.c72
-rw-r--r--tools/testing/selftests/bpf/progs/ksock_lsm_verifier.c35
-rw-r--r--tools/testing/selftests/bpf/progs/ksock_wq.c62
-rw-r--r--tools/testing/selftests/bpf/progs/linked_list.c71
-rw-r--r--tools/testing/selftests/bpf/progs/linked_list_fail.c23
-rw-r--r--tools/testing/selftests/bpf/progs/local_storage.c19
-rw-r--r--tools/testing/selftests/bpf/progs/lpm_trie_bench.c2
-rw-r--r--tools/testing/selftests/bpf/progs/lru_lock_nmi.c33
-rw-r--r--tools/testing/selftests/bpf/progs/lsm_bdev.c96
-rw-r--r--tools/testing/selftests/bpf/progs/lsm_cgroup.c30
-rw-r--r--tools/testing/selftests/bpf/progs/lwt_misc.c22
-rw-r--r--tools/testing/selftests/bpf/progs/map_kptr.c113
-rw-r--r--tools/testing/selftests/bpf/progs/map_kptr_fail.c69
-rw-r--r--tools/testing/selftests/bpf/progs/map_kptr_race.c197
-rw-r--r--tools/testing/selftests/bpf/progs/map_ptr_kern.c17
-rw-r--r--tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c255
-rw-r--r--tools/testing/selftests/bpf/progs/modify_return.c13
-rw-r--r--tools/testing/selftests/bpf/progs/mptcpify.c2
-rw-r--r--tools/testing/selftests/bpf/progs/percpu_alloc_array.c32
-rw-r--r--tools/testing/selftests/bpf/progs/percpu_alloc_fail.c63
-rw-r--r--tools/testing/selftests/bpf/progs/preempt_lock.c58
-rw-r--r--tools/testing/selftests/bpf/progs/profiler.h2
-rw-r--r--tools/testing/selftests/bpf/progs/profiler.inc.h6
-rw-r--r--tools/testing/selftests/bpf/progs/pyperf.h12
-rw-r--r--tools/testing/selftests/bpf/progs/raw_tp_null_fail.c53
-rw-r--r--tools/testing/selftests/bpf/progs/rbtree_fail.c90
-rw-r--r--tools/testing/selftests/bpf/progs/rbtree_search_kptr.c290
-rw-r--r--tools/testing/selftests/bpf/progs/rcu_read_lock.c86
-rw-r--r--tools/testing/selftests/bpf/progs/rcu_tasks_trace_gp.c30
-rw-r--r--tools/testing/selftests/bpf/progs/read_bpf_task_storage_busy.c38
-rw-r--r--tools/testing/selftests/bpf/progs/refcounted_kptr.c506
-rw-r--r--tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c197
-rw-r--r--tools/testing/selftests/bpf/progs/rhash.c360
-rw-r--r--tools/testing/selftests/bpf/progs/rhash_timer.c98
-rw-r--r--tools/testing/selftests/bpf/progs/setget_sockopt.c25
-rw-r--r--tools/testing/selftests/bpf/progs/sk_bypass_prot_mem.c2
-rw-r--r--tools/testing/selftests/bpf/progs/sk_storage_omem_uncharge.c12
-rw-r--r--tools/testing/selftests/bpf/progs/snprintf_btf_void.c24
-rw-r--r--tools/testing/selftests/bpf/progs/sock_destroy_prog.c30
-rw-r--r--tools/testing/selftests/bpf/progs/sock_ops_get_sk.c117
-rw-r--r--tools/testing/selftests/bpf/progs/sock_read_xattr.c54
-rw-r--r--tools/testing/selftests/bpf/progs/sockmap_parse_prog.c22
-rw-r--r--tools/testing/selftests/bpf/progs/sockmap_verdict_prog.c14
-rw-r--r--tools/testing/selftests/bpf/progs/sockopt_sk.c16
-rw-r--r--tools/testing/selftests/bpf/progs/stack_arg.c273
-rw-r--r--tools/testing/selftests/bpf/progs/stack_arg_fail.c114
-rw-r--r--tools/testing/selftests/bpf/progs/stack_arg_kfunc.c166
-rw-r--r--tools/testing/selftests/bpf/progs/stack_arg_precision.c135
-rw-r--r--tools/testing/selftests/bpf/progs/stacktrace_ips.c27
-rw-r--r--tools/testing/selftests/bpf/progs/stream.c202
-rw-r--r--tools/testing/selftests/bpf/progs/stream_fail.c8
-rw-r--r--tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c6
-rw-r--r--tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c1
-rw-r--r--tools/testing/selftests/bpf/progs/string_kfuncs_success.c7
-rw-r--r--tools/testing/selftests/bpf/progs/struct_ops_arena.c139
-rw-r--r--tools/testing/selftests/bpf/progs/struct_ops_arena_attach.c25
-rw-r--r--tools/testing/selftests/bpf/progs/struct_ops_arena_fail.c20
-rw-r--r--tools/testing/selftests/bpf/progs/struct_ops_assoc.c105
-rw-r--r--tools/testing/selftests/bpf/progs/struct_ops_assoc_in_timer.c77
-rw-r--r--tools/testing/selftests/bpf/progs/struct_ops_assoc_reuse.c75
-rw-r--r--tools/testing/selftests/bpf/progs/struct_ops_kptr_return_fail__wrong_type.c2
-rw-r--r--tools/testing/selftests/bpf/progs/struct_ops_multi_args.c35
-rw-r--r--tools/testing/selftests/bpf/progs/struct_ops_private_stack.c6
-rw-r--r--tools/testing/selftests/bpf/progs/struct_ops_private_stack_fail.c6
-rw-r--r--tools/testing/selftests/bpf/progs/struct_ops_private_stack_recur.c6
-rw-r--r--tools/testing/selftests/bpf/progs/tailcall3.c5
-rw-r--r--tools/testing/selftests/bpf/progs/tailcall_bpf2bpf2.c5
-rw-r--r--tools/testing/selftests/bpf/progs/tailcall_bpf2bpf_hierarchy1.c13
-rw-r--r--tools/testing/selftests/bpf/progs/tailcall_bpf2bpf_hierarchy2.c24
-rw-r--r--tools/testing/selftests/bpf/progs/tailcall_bpf2bpf_hierarchy3.c13
-rw-r--r--tools/testing/selftests/bpf/progs/tailcall_bpf2bpf_hierarchy_fentry.c13
-rw-r--r--tools/testing/selftests/bpf/progs/tailcall_callback.c97
-rw-r--r--tools/testing/selftests/bpf/progs/tailcall_cgrp_storage.c44
-rw-r--r--tools/testing/selftests/bpf/progs/tailcall_cgrp_storage_no_storage.c26
-rw-r--r--tools/testing/selftests/bpf/progs/tailcall_cgrp_storage_owner.c32
-rw-r--r--tools/testing/selftests/bpf/progs/tailcall_sleepable.c43
-rw-r--r--tools/testing/selftests/bpf/progs/task_kfunc_common.h14
-rw-r--r--tools/testing/selftests/bpf/progs/task_kfunc_failure.c135
-rw-r--r--tools/testing/selftests/bpf/progs/task_kfunc_success.c208
-rw-r--r--tools/testing/selftests/bpf/progs/task_local_data.bpf.h14
-rw-r--r--tools/testing/selftests/bpf/progs/task_local_storage.c19
-rw-r--r--tools/testing/selftests/bpf/progs/task_ls_recursion.c14
-rw-r--r--tools/testing/selftests/bpf/progs/task_storage_nodeadlock.c7
-rw-r--r--tools/testing/selftests/bpf/progs/task_work.c7
-rw-r--r--tools/testing/selftests/bpf/progs/task_work_fail.c14
-rw-r--r--tools/testing/selftests/bpf/progs/task_work_stress.c4
-rw-r--r--tools/testing/selftests/bpf/progs/tcp_ca_kfunc.c16
-rw-r--r--tools/testing/selftests/bpf/progs/tcp_ca_untrusted_btf_write.c26
-rw-r--r--tools/testing/selftests/bpf/progs/test_access_variable_array.c19
-rw-r--r--tools/testing/selftests/bpf/progs/test_bpf_nf.c41
-rw-r--r--tools/testing/selftests/bpf/progs/test_bpf_nf_fail.c57
-rw-r--r--tools/testing/selftests/bpf/progs/test_btf_decl_tag.c2
-rw-r--r--tools/testing/selftests/bpf/progs/test_ctx.c48
-rw-r--r--tools/testing/selftests/bpf/progs/test_d_path.c23
-rw-r--r--tools/testing/selftests/bpf/progs/test_dst_clear.c57
-rw-r--r--tools/testing/selftests/bpf/progs/test_fill_link_info.c8
-rw-r--r--tools/testing/selftests/bpf/progs/test_global_func3.c68
-rw-r--r--tools/testing/selftests/bpf/progs/test_global_func7.c2
-rw-r--r--tools/testing/selftests/bpf/progs/test_global_func_deep_stack.c95
-rw-r--r--tools/testing/selftests/bpf/progs/test_global_percpu_data.c89
-rw-r--r--tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c16
-rw-r--r--tools/testing/selftests/bpf/progs/test_kfunc_param_nullable.c2
-rw-r--r--tools/testing/selftests/bpf/progs/test_lirc_mode2_kern.c4
-rw-r--r--tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c155
-rw-r--r--tools/testing/selftests/bpf/progs/test_misc_tcp_hdr_options.c15
-rw-r--r--tools/testing/selftests/bpf/progs/test_module_attach.c63
-rw-r--r--tools/testing/selftests/bpf/progs/test_probe_user.c13
-rw-r--r--tools/testing/selftests/bpf/progs/test_ringbuf_map_key.c11
-rw-r--r--tools/testing/selftests/bpf/progs/test_signed_loader.c19
-rw-r--r--tools/testing/selftests/bpf/progs/test_signed_loader_data.c20
-rw-r--r--tools/testing/selftests/bpf/progs/test_signed_loader_lsm.c30
-rw-r--r--tools/testing/selftests/bpf/progs/test_signed_loader_map.c28
-rw-r--r--tools/testing/selftests/bpf/progs/test_sleepable_tracepoints.c112
-rw-r--r--tools/testing/selftests/bpf/progs/test_sleepable_tracepoints_fail.c18
-rw-r--r--tools/testing/selftests/bpf/progs/test_sockmap_kern.h56
-rw-r--r--tools/testing/selftests/bpf/progs/test_sockmap_ktls.c40
-rw-r--r--tools/testing/selftests/bpf/progs/test_sockmap_msg_pop_data.c27
-rw-r--r--tools/testing/selftests/bpf/progs/test_sockmap_pass_prog.c14
-rw-r--r--tools/testing/selftests/bpf/progs/test_sockmap_strp.c7
-rw-r--r--tools/testing/selftests/bpf/progs/test_sockmap_update.c48
-rw-r--r--tools/testing/selftests/bpf/progs/test_spin_lock_fail.c67
-rw-r--r--tools/testing/selftests/bpf/progs/test_tc_change_tail_pmtu.c129
-rw-r--r--tools/testing/selftests/bpf/progs/test_tc_peer.c22
-rw-r--r--tools/testing/selftests/bpf/progs/test_tc_qevent.c23
-rw-r--r--tools/testing/selftests/bpf/progs/test_tc_tunnel.c112
-rw-r--r--tools/testing/selftests/bpf/progs/test_trampoline_count.c12
-rw-r--r--tools/testing/selftests/bpf/progs/test_tunnel_kern.c13
-rw-r--r--tools/testing/selftests/bpf/progs/test_usdt.c37
-rw-r--r--tools/testing/selftests/bpf/progs/test_vmlinux.c4
-rw-r--r--tools/testing/selftests/bpf/progs/test_wakeup_source.c92
-rw-r--r--tools/testing/selftests/bpf/progs/test_xdp_meta.c135
-rw-r--r--tools/testing/selftests/bpf/progs/timer.c118
-rw-r--r--tools/testing/selftests/bpf/progs/timer_mim_reject.c84
-rw-r--r--tools/testing/selftests/bpf/progs/timer_start_deadlock.c70
-rw-r--r--tools/testing/selftests/bpf/progs/timer_start_delete_race.c66
-rw-r--r--tools/testing/selftests/bpf/progs/token_kallsyms.c19
-rw-r--r--tools/testing/selftests/bpf/progs/trace_printk.c10
-rw-r--r--tools/testing/selftests/bpf/progs/tracing_failure.c6
-rw-r--r--tools/testing/selftests/bpf/progs/tracing_multi_attach.c39
-rw-r--r--tools/testing/selftests/bpf/progs/tracing_multi_attach_module.c25
-rw-r--r--tools/testing/selftests/bpf/progs/tracing_multi_bench.c12
-rw-r--r--tools/testing/selftests/bpf/progs/tracing_multi_check.c214
-rw-r--r--tools/testing/selftests/bpf/progs/tracing_multi_fail.c18
-rw-r--r--tools/testing/selftests/bpf/progs/tracing_multi_intersect_attach.c49
-rw-r--r--tools/testing/selftests/bpf/progs/tracing_multi_rollback.c43
-rw-r--r--tools/testing/selftests/bpf/progs/tracing_multi_session_attach.c65
-rw-r--r--tools/testing/selftests/bpf/progs/tracing_multi_verifier.c31
-rw-r--r--tools/testing/selftests/bpf/progs/tracing_struct_int128.c18
-rw-r--r--tools/testing/selftests/bpf/progs/trigger_bench.c54
-rw-r--r--tools/testing/selftests/bpf/progs/uninit_stack.c1
-rw-r--r--tools/testing/selftests/bpf/progs/uprobe_multi_session.c7
-rw-r--r--tools/testing/selftests/bpf/progs/uprobe_multi_session_cookie.c15
-rw-r--r--tools/testing/selftests/bpf/progs/uprobe_multi_session_recursive.c11
-rw-r--r--tools/testing/selftests/bpf/progs/user_ringbuf_fail.c4
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_align.c581
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_arena.c541
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_arena_globals1.c87
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_arena_globals2.c49
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_arena_large.c52
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_async_cb_context.c216
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_basic_stack.c47
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_bits_iter.c4
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_bounds.c506
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_bpf_fastcall.c165
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_bswap.c65
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_btf_flex_array.c56
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_call_large_imm.c66
-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_ctx.c569
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_ctx_ptr_param.c68
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_default_trusted_ptr.c29
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c65
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_div_mod_bounds.c1151
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_flow_keys.c97
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c100
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_global_subprogs.c152
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_gotox.c81
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c6
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_helper_packet_access.c2
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_helper_restricted.c111
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_helper_value_access.c6
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_int_ptr.c2
-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.c247
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_jit_inline.c35
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_kfunc_perfmon.c75
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_kfunc_prog_types.c6
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_ld_ind.c238
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_ldsx.c8
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_linked_scalars.c683
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_live_stack.c2604
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_liveness_exp.c139
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_load_acquire.c44
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_loops1.c39
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_lsm.c55
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_map_in_map.c43
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_map_lookup_refine.c73
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_map_ptr.c39
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_may_goto_1.c79
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_mem_size_reg.c28
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_meta_access.c2
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_netfilter_ctx.c78
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_percpu_addr.c72
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_precision.c445
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_private_stack.c39
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c27
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c16
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_ref_tracking.c8
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_scalar_ids.c229
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_sdiv.c122
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_set_retval.c107
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_sock.c53
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_sockmap_mutate.c12
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_spill_fill.c222
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_stack_arg.c447
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_stack_arg_order.c185
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_stack_ptr.c4
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_subprog_insn_stats.c223
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_subprog_precision.c71
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_subprog_topo.c226
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_subreg.c320
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c1
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_uninit.c1
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_unpriv.c87
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c7
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_value_ptr_arith.c10
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_var_off.c116
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_vfs_reject.c14
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_xdp.c35
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c99
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_zext.c418
-rw-r--r--tools/testing/selftests/bpf/progs/veristat_bar.c3
-rw-r--r--tools/testing/selftests/bpf/progs/veristat_foo.c31
-rw-r--r--tools/testing/selftests/bpf/progs/wakeup_source.h22
-rw-r--r--tools/testing/selftests/bpf/progs/wakeup_source_fail.c76
-rw-r--r--tools/testing/selftests/bpf/progs/wq_failures.c6
-rw-r--r--tools/testing/selftests/bpf/progs/xdp_dummy.c6
-rw-r--r--tools/testing/selftests/bpf/progs/xdp_flowtable.c7
-rw-r--r--tools/testing/selftests/bpf/progs/xdp_lb_bench.c647
-rw-r--r--tools/testing/selftests/bpf/progs/xdping_kern.c183
-rw-r--r--tools/testing/selftests/bpf/progs/xsk_xdp_progs.c4
328 files changed, 24680 insertions, 2071 deletions
diff --git a/tools/testing/selftests/bpf/progs/arena_atomics.c b/tools/testing/selftests/bpf/progs/arena_atomics.c
index d1841aac94a2..73bc2b835f3f 100644
--- a/tools/testing/selftests/bpf/progs/arena_atomics.c
+++ b/tools/testing/selftests/bpf/progs/arena_atomics.c
@@ -5,7 +5,7 @@
#include <bpf/bpf_tracing.h>
#include <stdbool.h>
#include <stdatomic.h>
-#include "bpf_arena_common.h"
+#include <bpf_arena_common.h>
#include "../../../include/linux/filter.h"
#include "bpf_misc.h"
@@ -28,8 +28,10 @@ bool skip_all_tests = true;
#if defined(ENABLE_ATOMICS_TESTS) && \
defined(__BPF_FEATURE_ADDR_SPACE_CAST) && \
- (defined(__TARGET_ARCH_arm64) || defined(__TARGET_ARCH_x86) || \
- (defined(__TARGET_ARCH_riscv) && __riscv_xlen == 64))
+ (defined(__TARGET_ARCH_arm64) || \
+ defined(__TARGET_ARCH_x86) || \
+ (defined(__TARGET_ARCH_riscv) && __riscv_xlen == 64) || \
+ defined(__TARGET_ARCH_s390))
bool skip_lacq_srel_tests __attribute((__section__(".data"))) = false;
#else
bool skip_lacq_srel_tests = true;
@@ -315,8 +317,10 @@ int load_acquire(const void *ctx)
{
#if defined(ENABLE_ATOMICS_TESTS) && \
defined(__BPF_FEATURE_ADDR_SPACE_CAST) && \
- (defined(__TARGET_ARCH_arm64) || defined(__TARGET_ARCH_x86) || \
- (defined(__TARGET_ARCH_riscv) && __riscv_xlen == 64))
+ (defined(__TARGET_ARCH_arm64) || \
+ defined(__TARGET_ARCH_x86) || \
+ (defined(__TARGET_ARCH_riscv) && __riscv_xlen == 64) || \
+ defined(__TARGET_ARCH_s390))
#define LOAD_ACQUIRE_ARENA(SIZEOP, SIZE, SRC, DST) \
{ asm volatile ( \
@@ -367,8 +371,10 @@ int store_release(const void *ctx)
{
#if defined(ENABLE_ATOMICS_TESTS) && \
defined(__BPF_FEATURE_ADDR_SPACE_CAST) && \
- (defined(__TARGET_ARCH_arm64) || defined(__TARGET_ARCH_x86) || \
- (defined(__TARGET_ARCH_riscv) && __riscv_xlen == 64))
+ (defined(__TARGET_ARCH_arm64) || \
+ defined(__TARGET_ARCH_x86) || \
+ (defined(__TARGET_ARCH_riscv) && __riscv_xlen == 64) || \
+ defined(__TARGET_ARCH_s390))
#define STORE_RELEASE_ARENA(SIZEOP, DST, VAL) \
{ asm volatile ( \
diff --git a/tools/testing/selftests/bpf/progs/arena_kfunc.c b/tools/testing/selftests/bpf/progs/arena_kfunc.c
new file mode 100644
index 000000000000..bf0d304e0e59
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/arena_kfunc.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#define BPF_NO_KFUNC_PROTOTYPES
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+#include "bpf_experimental.h"
+#include <bpf_arena_common.h>
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARENA);
+ __uint(map_flags, BPF_F_MMAPABLE);
+ /* page 0 hosts the arena global, page 1 is for allocations */
+ __uint(max_entries, 2);
+} arena SEC(".maps");
+
+/*
+ * Occupies page 0 so no allocation lands at arena offset 0, which the
+ * nullable tests below must be able to tell apart from NULL.
+ */
+u64 __arena arena_pad;
+
+/* volatile to force the scalar reloads below */
+volatile u64 stash;
+
+SEC("syscall")
+__arch_x86_64
+__arch_arm64
+__success __retval(0)
+int arena_arg_forms(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ u64 __arena *val;
+ u64 ret;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ /* PTR_TO_ARENA argument */
+ *val = 41;
+ ret = bpf_kfunc_arena_arg_test((u64 *)val);
+ if (ret != 41 || *val != 42)
+ return 2;
+
+ /* the low 32 bits as a scalar */
+ stash = (u32)(u64)val;
+ ret = bpf_kfunc_arena_arg_test((u64 *)stash);
+ if (ret != 42 || *val != 43)
+ return 3;
+
+ /* the full user address as a scalar */
+ stash = (u64)val;
+ bpf_addr_space_cast(stash, 1, 0);
+ ret = bpf_kfunc_arena_arg_test((u64 *)stash);
+ if (ret != 43 || *val != 44)
+ return 4;
+
+ bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+ return 0;
+}
+
+/*
+ * Pin the rebase semantics using the capture kfuncs, which return the raw
+ * argument value: __arena rebases unconditionally, so zero low 32 bits
+ * arrive as the arena kernel base, while __arena__nullable turns them into
+ * NULL.
+ */
+SEC("syscall")
+__arch_x86_64
+__arch_arm64
+__success __retval(0)
+int arena_arg_rebase(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ u64 __arena *val;
+ u64 base, off;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ base = bpf_kfunc_arena_cap_test(NULL);
+ if (!base)
+ return 2;
+
+ /* only the low 32 bits contribute */
+ stash = 0xbadc0ffe00000000;
+ if (bpf_kfunc_arena_cap_test((u64 *)stash) != base)
+ return 3;
+
+ off = (u32)(u64)val;
+ if (bpf_kfunc_arena_cap_test((u64 *)val) != base + off)
+ return 4;
+
+ if (bpf_kfunc_arena_cap_nullable_test(NULL) != 0)
+ return 5;
+
+ stash = 0xbadc0ffe00000000;
+ if (bpf_kfunc_arena_cap_nullable_test((u64 *)stash) != 0)
+ return 6;
+
+ if (bpf_kfunc_arena_cap_nullable_test((u64 *)val) != base + off)
+ return 7;
+
+ bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+ return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__arch_arm64
+__success __retval(0)
+int arena_args5(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ u64 __arena *val;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ val[0] = 1;
+ val[1] = 2;
+ val[2] = 4;
+ val[3] = 8;
+ val[4] = 16;
+
+ if (bpf_kfunc_arena_args5_test((u64 *)&val[0], (u64 *)&val[1],
+ (u64 *)&val[2], (u64 *)&val[3],
+ (u64 *)&val[4]) != 31)
+ return 2;
+ if (bpf_kfunc_arena_args5_test((u64 *)&val[0], (u64 *)&val[1],
+ (u64 *)&val[2], (u64 *)&val[3], NULL) != 15)
+ return 3;
+
+ bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+ return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__arch_arm64
+__success __retval(0)
+int arena_arg_mixed(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ u64 __arena *val;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ val[0] = 7;
+ val[1] = 5;
+
+ if (bpf_kfunc_arena_mixed_test((u64 *)&val[0], NULL) != 7)
+ return 2;
+
+ if (bpf_kfunc_arena_mixed_test((u64 *)&val[0], (u64 *)&val[1]) != 12)
+ return 3;
+
+ bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+ return 0;
+}
+
+/* kernel-side faults on unpopulated pages recover via the scratch page */
+SEC("syscall")
+__arch_x86_64
+__arch_arm64
+__success __retval(0)
+int arena_arg_unpopulated(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ u64 __arena *val;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ stash = (u64)val + PAGE_SIZE;
+ bpf_kfunc_arena_arg_test((u64 *)stash);
+
+ bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+ return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__arch_arm64
+__failure __msg("arena pointer requires a program with an associated arena")
+int arena_arg_no_arena(void *ctx)
+{
+ bpf_kfunc_arena_arg_test((u64 *)1);
+ return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__arch_arm64
+__failure __msg("is not a pointer to arena or scalar")
+int arena_arg_bad_reg(void *ctx)
+{
+ u64 buf = 0;
+
+ /* use the arena so the program passes the arena presence check */
+ bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ bpf_kfunc_arena_arg_test(&buf);
+ return 0;
+}
+
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) && \
+ defined(__BPF_FEATURE_STACK_ARGUMENT)
+SEC("syscall")
+__arch_x86_64
+__arch_arm64
+__failure __msg("arena pointer cannot be a stack argument")
+int arena_arg_stack(void *ctx)
+{
+ bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ bpf_kfunc_arena_stack_arg_test(1, 2, 3, 4, 5, (u64 *)1);
+ return 0;
+}
+#else
+SEC("syscall")
+__arch_x86_64
+__arch_arm64
+__description("arena_arg_stack: not supported, dummy test")
+__success
+int arena_arg_stack(void *ctx)
+{
+ return 0;
+}
+#endif
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c b/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c
new file mode 100644
index 000000000000..b5a01cbc33a7
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+/*
+ * Verify the JIT-emitted rebase sequences for __arena and __arena__nullable
+ * kfunc arguments. The capture kfuncs take the argument without
+ * dereferencing it, so these tests pin only the emitted code.
+ */
+#define BPF_NO_KFUNC_PROTOTYPES
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+#include "bpf_experimental.h"
+#include <bpf_arena_common.h>
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARENA);
+ __uint(map_flags, BPF_F_MMAPABLE);
+ __uint(max_entries, 1);
+} arena SEC(".maps");
+
+/* volatile to force the scalar reloads below */
+volatile u64 stash;
+
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+
+SEC("syscall")
+__arch_x86_64
+__jited("...")
+__jited(" movl %edi, %edi")
+__jited(" addq %r12, %rdi")
+__jited("...")
+__jited(" callq {{.*}}")
+__arch_arm64
+__jited("...")
+__jited(" add x0, x28, w0, uxtw")
+__jited(" {{(bl|mov) .*}}")
+__success
+int arena_arg_jit_rebase(void *ctx)
+{
+ stash = (u64)bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ bpf_kfunc_arena_cap_test((u64 *)stash);
+ return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__jited("...")
+__jited(" movl %edi, %edi")
+__jited(" testl %edi, %edi")
+__jited(" je L0")
+__jited(" addq %r12, %rdi")
+__jited("L0: callq {{.*}}")
+__arch_arm64
+__jited("...")
+__jited(" mov w0, w0")
+__jited(" cbz w0, L0")
+__jited(" add x0, x28, w0, uxtw")
+__jited("L0: {{.*}}")
+__success
+int arena_arg_jit_nullable(void *ctx)
+{
+ stash = (u64)bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ bpf_kfunc_arena_cap_nullable_test((u64 *)stash);
+ return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__jited("...")
+__jited(" movl %edi, %edi")
+__jited(" addq %r12, %rdi")
+__jited(" movl %esi, %esi")
+__jited(" addq %r12, %rsi")
+__jited(" movl %edx, %edx")
+__jited(" addq %r12, %rdx")
+__jited(" movl %ecx, %ecx")
+__jited(" addq %r12, %rcx")
+__jited(" movl %r8d, %r8d")
+__jited(" testl %r8d, %r8d")
+__jited(" je L0")
+__jited(" addq %r12, %r8")
+__jited("L0: callq {{.*}}")
+__arch_arm64
+__jited("...")
+__jited(" add x0, x28, w0, uxtw")
+__jited(" add x1, x28, w1, uxtw")
+__jited(" add x2, x28, w2, uxtw")
+__jited(" add x3, x28, w3, uxtw")
+__jited(" mov w4, w4")
+__jited(" cbz w4, L0")
+__jited(" add x4, x28, w4, uxtw")
+__jited("L0: {{.*}}")
+__success
+int arena_arg_jit_args5(void *ctx)
+{
+ u64 __arena *val;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ val[0] = 1;
+ val[1] = 2;
+ val[2] = 4;
+ val[3] = 8;
+ val[4] = 16;
+
+ bpf_kfunc_arena_args5_test((u64 *)&val[0], (u64 *)&val[1],
+ (u64 *)&val[2], (u64 *)&val[3],
+ (u64 *)&val[4]);
+ return 0;
+}
+
+#endif /* __BPF_FEATURE_ADDR_SPACE_CAST */
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/arena_list.c b/tools/testing/selftests/bpf/progs/arena_list.c
index 3a2ddcacbea6..235d8cc95bdd 100644
--- a/tools/testing/selftests/bpf/progs/arena_list.c
+++ b/tools/testing/selftests/bpf/progs/arena_list.c
@@ -30,6 +30,7 @@ struct arena_list_head __arena *list_head;
int list_sum;
int cnt;
bool skip = false;
+const volatile bool nonsleepable = false;
#ifdef __BPF_FEATURE_ADDR_SPACE_CAST
long __arena arena_sum;
@@ -42,6 +43,9 @@ int test_val SEC(".addr_space.1");
int zero;
+void bpf_rcu_read_lock(void) __ksym;
+void bpf_rcu_read_unlock(void) __ksym;
+
SEC("syscall")
int arena_list_add(void *ctx)
{
@@ -71,6 +75,10 @@ int arena_list_del(void *ctx)
struct elem __arena *n;
int sum = 0;
+ /* Take rcu_read_lock to test non-sleepable context */
+ if (nonsleepable)
+ bpf_rcu_read_lock();
+
arena_sum = 0;
list_for_each_entry(n, list_head, node) {
sum += n->value;
@@ -79,6 +87,9 @@ int arena_list_del(void *ctx)
bpf_free(n);
}
list_sum = sum;
+
+ if (nonsleepable)
+ bpf_rcu_read_unlock();
#else
skip = true;
#endif
diff --git a/tools/testing/selftests/bpf/progs/arena_mem_usage.c b/tools/testing/selftests/bpf/progs/arena_mem_usage.c
new file mode 100644
index 000000000000..455ecd669a5a
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/arena_mem_usage.c
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_arena_common.h"
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARENA);
+ __uint(map_flags, BPF_F_MMAPABLE);
+ __uint(max_entries, 1000); /* number of pages */
+#ifdef __TARGET_ARCH_arm64
+ __ulong(map_extra, 0x1ull << 32); /* start of mmap() region */
+#else
+ __ulong(map_extra, 0x1ull << 44); /* start of mmap() region */
+#endif
+} arena SEC(".maps");
+
+void __arena *ptr;
+int alloc_cnt; /* in: pages to allocate */
+long free_byte_off; /* in: byte offset within ptr to start freeing */
+int free_cnt; /* in: pages to free */
+
+SEC("syscall")
+int alloc(void *ctx)
+{
+ ptr = bpf_arena_alloc_pages(&arena, NULL, alloc_cnt, NUMA_NO_NODE, 0);
+ /* Success/failure is checked from user space via skel->bss->ptr. */
+ return 0;
+}
+
+SEC("syscall")
+int free_pages(void *ctx)
+{
+ if (!ptr)
+ return 1;
+ bpf_arena_free_pages(&arena, (char __arena *)ptr + free_byte_off, free_cnt);
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/arena_spin_lock.c b/tools/testing/selftests/bpf/progs/arena_spin_lock.c
index 086b57a426cf..92e75ec3844c 100644
--- a/tools/testing/selftests/bpf/progs/arena_spin_lock.c
+++ b/tools/testing/selftests/bpf/progs/arena_spin_lock.c
@@ -4,7 +4,8 @@
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
-#include "bpf_arena_spin_lock.h"
+#include <bpf_arena_common.h>
+#include <bpf_arena_spin_lock.h>
struct {
__uint(type, BPF_MAP_TYPE_ARENA);
@@ -22,6 +23,13 @@ int cs_count;
#if defined(ENABLE_ATOMICS_TESTS) && defined(__BPF_FEATURE_ADDR_SPACE_CAST)
arena_spinlock_t __arena lock;
int test_skip = 1;
+
+/*
+ * Storage for the queue nodes declared by bpf_arena_spin_lock.h. Each program
+ * linking the arena spinlock provides exactly one definition; libarena's lives
+ * in libarena/src/common.bpf.c.
+ */
+struct arena_qnode __arena __hidden qnodes[_Q_MAX_CPUS][_Q_MAX_NODES];
#else
int test_skip = 2;
#endif
diff --git a/tools/testing/selftests/bpf/progs/bench_bpf_timing.bpf.h b/tools/testing/selftests/bpf/progs/bench_bpf_timing.bpf.h
new file mode 100644
index 000000000000..6a1ad75f1fd7
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/bench_bpf_timing.bpf.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#ifndef __BENCH_BPF_TIMING_BPF_H__
+#define __BENCH_BPF_TIMING_BPF_H__
+
+#include <stdbool.h>
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf_may_goto.h>
+
+#ifndef BENCH_NR_SAMPLES
+#define BENCH_NR_SAMPLES 4096
+#endif
+#ifndef BENCH_NR_CPUS
+#define BENCH_NR_CPUS 256
+#endif
+#define BENCH_CPU_MASK (BENCH_NR_CPUS - 1)
+
+__u64 timing_samples[BENCH_NR_CPUS][BENCH_NR_SAMPLES];
+__u32 timing_idx[BENCH_NR_CPUS];
+
+volatile __u32 batch_iters;
+volatile __u32 timing_enabled;
+
+static __always_inline void bench_record_sample(__u64 elapsed_ns)
+{
+ __u32 cpu, idx;
+
+ if (!timing_enabled)
+ return;
+
+ cpu = bpf_get_smp_processor_id() & BENCH_CPU_MASK;
+ idx = timing_idx[cpu];
+
+ if (idx >= BENCH_NR_SAMPLES)
+ return;
+
+ timing_samples[cpu][idx] = elapsed_ns;
+ timing_idx[cpu] = idx + 1;
+}
+
+/*
+ * @body: expression to time; return value (int) stored in __bench_result.
+ * @reset: undo body's side-effects so each iteration starts identically.
+ * May reference __bench_result. Use ({}) for empty reset.
+ *
+ * Runs batch_iters timed iterations, then one untimed iteration whose
+ * return value the macro evaluates to (for validation).
+ */
+#define BENCH_BPF_LOOP(body, reset) ({ \
+ __u64 __bench_start = bpf_ktime_get_ns(); \
+ __u32 __bench_i; \
+ int __bench_result; \
+ \
+ for (__bench_i = 0; \
+ __bench_i < batch_iters && can_loop; \
+ __bench_i++) { \
+ __bench_result = (body); \
+ reset; \
+ } \
+ \
+ bench_record_sample(bpf_ktime_get_ns() - __bench_start); \
+ \
+ __bench_result = (body); \
+ __bench_result; \
+})
+
+#endif /* __BENCH_BPF_TIMING_BPF_H__ */
diff --git a/tools/testing/selftests/bpf/progs/bench_local_storage_create.c b/tools/testing/selftests/bpf/progs/bench_local_storage_create.c
index c8ec0d0368e4..25ca6045fea3 100644
--- a/tools/testing/selftests/bpf/progs/bench_local_storage_create.c
+++ b/tools/testing/selftests/bpf/progs/bench_local_storage_create.c
@@ -8,7 +8,6 @@
long create_errs = 0;
long create_cnts = 0;
-long kmalloc_cnts = 0;
__u32 bench_pid = 0;
struct storage {
@@ -29,16 +28,6 @@ struct {
__type(value, struct storage);
} task_storage_map SEC(".maps");
-SEC("raw_tp/kmalloc")
-int BPF_PROG(kmalloc, unsigned long call_site, const void *ptr,
- size_t bytes_req, size_t bytes_alloc, gfp_t gfp_flags,
- int node)
-{
- __sync_fetch_and_add(&kmalloc_cnts, 1);
-
- return 0;
-}
-
SEC("tp_btf/sched_process_fork")
int BPF_PROG(sched_process_fork, struct task_struct *parent, struct task_struct *child)
{
diff --git a/tools/testing/selftests/bpf/progs/bpf_arena_spin_lock.h b/tools/testing/selftests/bpf/progs/bpf_arena_spin_lock.h
deleted file mode 100644
index f90531cf3ee5..000000000000
--- a/tools/testing/selftests/bpf/progs/bpf_arena_spin_lock.h
+++ /dev/null
@@ -1,542 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
-#ifndef BPF_ARENA_SPIN_LOCK_H
-#define BPF_ARENA_SPIN_LOCK_H
-
-#include <vmlinux.h>
-#include <bpf/bpf_helpers.h>
-#include "bpf_atomic.h"
-
-#define arch_mcs_spin_lock_contended_label(l, label) smp_cond_load_acquire_label(l, VAL, label)
-#define arch_mcs_spin_unlock_contended(l) smp_store_release((l), 1)
-
-#if defined(ENABLE_ATOMICS_TESTS) && defined(__BPF_FEATURE_ADDR_SPACE_CAST)
-
-#define EBUSY 16
-#define EOPNOTSUPP 95
-#define ETIMEDOUT 110
-
-#ifndef __arena
-#define __arena __attribute__((address_space(1)))
-#endif
-
-extern unsigned long CONFIG_NR_CPUS __kconfig;
-
-/*
- * Typically, we'd just rely on the definition in vmlinux.h for qspinlock, but
- * PowerPC overrides the definition to define lock->val as u32 instead of
- * atomic_t, leading to compilation errors. Import a local definition below so
- * that we don't depend on the vmlinux.h version.
- */
-
-struct __qspinlock {
- union {
- atomic_t val;
-#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
- struct {
- u8 locked;
- u8 pending;
- };
- struct {
- u16 locked_pending;
- u16 tail;
- };
-#else
- struct {
- u16 tail;
- u16 locked_pending;
- };
- struct {
- u8 reserved[2];
- u8 pending;
- u8 locked;
- };
-#endif
- };
-};
-
-#define arena_spinlock_t struct __qspinlock
-/* FIXME: Using typedef causes CO-RE relocation error */
-/* typedef struct qspinlock arena_spinlock_t; */
-
-struct arena_mcs_spinlock {
- struct arena_mcs_spinlock __arena *next;
- int locked;
- int count;
-};
-
-struct arena_qnode {
- struct arena_mcs_spinlock mcs;
-};
-
-#define _Q_MAX_NODES 4
-#define _Q_PENDING_LOOPS 1
-
-/*
- * Bitfields in the atomic value:
- *
- * 0- 7: locked byte
- * 8: pending
- * 9-15: not used
- * 16-17: tail index
- * 18-31: tail cpu (+1)
- */
-#define _Q_MAX_CPUS 1024
-
-#define _Q_SET_MASK(type) (((1U << _Q_ ## type ## _BITS) - 1)\
- << _Q_ ## type ## _OFFSET)
-#define _Q_LOCKED_OFFSET 0
-#define _Q_LOCKED_BITS 8
-#define _Q_LOCKED_MASK _Q_SET_MASK(LOCKED)
-
-#define _Q_PENDING_OFFSET (_Q_LOCKED_OFFSET + _Q_LOCKED_BITS)
-#define _Q_PENDING_BITS 8
-#define _Q_PENDING_MASK _Q_SET_MASK(PENDING)
-
-#define _Q_TAIL_IDX_OFFSET (_Q_PENDING_OFFSET + _Q_PENDING_BITS)
-#define _Q_TAIL_IDX_BITS 2
-#define _Q_TAIL_IDX_MASK _Q_SET_MASK(TAIL_IDX)
-
-#define _Q_TAIL_CPU_OFFSET (_Q_TAIL_IDX_OFFSET + _Q_TAIL_IDX_BITS)
-#define _Q_TAIL_CPU_BITS (32 - _Q_TAIL_CPU_OFFSET)
-#define _Q_TAIL_CPU_MASK _Q_SET_MASK(TAIL_CPU)
-
-#define _Q_TAIL_OFFSET _Q_TAIL_IDX_OFFSET
-#define _Q_TAIL_MASK (_Q_TAIL_IDX_MASK | _Q_TAIL_CPU_MASK)
-
-#define _Q_LOCKED_VAL (1U << _Q_LOCKED_OFFSET)
-#define _Q_PENDING_VAL (1U << _Q_PENDING_OFFSET)
-
-struct arena_qnode __arena qnodes[_Q_MAX_CPUS][_Q_MAX_NODES];
-
-static inline u32 encode_tail(int cpu, int idx)
-{
- u32 tail;
-
- tail = (cpu + 1) << _Q_TAIL_CPU_OFFSET;
- tail |= idx << _Q_TAIL_IDX_OFFSET; /* assume < 4 */
-
- return tail;
-}
-
-static inline struct arena_mcs_spinlock __arena *decode_tail(u32 tail)
-{
- u32 cpu = (tail >> _Q_TAIL_CPU_OFFSET) - 1;
- u32 idx = (tail & _Q_TAIL_IDX_MASK) >> _Q_TAIL_IDX_OFFSET;
-
- return &qnodes[cpu][idx].mcs;
-}
-
-static inline
-struct arena_mcs_spinlock __arena *grab_mcs_node(struct arena_mcs_spinlock __arena *base, int idx)
-{
- return &((struct arena_qnode __arena *)base + idx)->mcs;
-}
-
-#define _Q_LOCKED_PENDING_MASK (_Q_LOCKED_MASK | _Q_PENDING_MASK)
-
-/**
- * xchg_tail - Put in the new queue tail code word & retrieve previous one
- * @lock : Pointer to queued spinlock structure
- * @tail : The new queue tail code word
- * Return: The previous queue tail code word
- *
- * xchg(lock, tail)
- *
- * p,*,* -> n,*,* ; prev = xchg(lock, node)
- */
-static __always_inline u32 xchg_tail(arena_spinlock_t __arena *lock, u32 tail)
-{
- u32 old, new;
-
- old = atomic_read(&lock->val);
- do {
- new = (old & _Q_LOCKED_PENDING_MASK) | tail;
- /*
- * We can use relaxed semantics since the caller ensures that
- * the MCS node is properly initialized before updating the
- * tail.
- */
- /* These loops are not expected to stall, but we still need to
- * prove to the verifier they will terminate eventually.
- */
- cond_break_label(out);
- } while (!atomic_try_cmpxchg_relaxed(&lock->val, &old, new));
-
- return old;
-out:
- bpf_printk("RUNTIME ERROR: %s unexpected cond_break exit!!!", __func__);
- return old;
-}
-
-/**
- * clear_pending - clear the pending bit.
- * @lock: Pointer to queued spinlock structure
- *
- * *,1,* -> *,0,*
- */
-static __always_inline void clear_pending(arena_spinlock_t __arena *lock)
-{
- WRITE_ONCE(lock->pending, 0);
-}
-
-/**
- * clear_pending_set_locked - take ownership and clear the pending bit.
- * @lock: Pointer to queued spinlock structure
- *
- * *,1,0 -> *,0,1
- *
- * Lock stealing is not allowed if this function is used.
- */
-static __always_inline void clear_pending_set_locked(arena_spinlock_t __arena *lock)
-{
- WRITE_ONCE(lock->locked_pending, _Q_LOCKED_VAL);
-}
-
-/**
- * set_locked - Set the lock bit and own the lock
- * @lock: Pointer to queued spinlock structure
- *
- * *,*,0 -> *,0,1
- */
-static __always_inline void set_locked(arena_spinlock_t __arena *lock)
-{
- WRITE_ONCE(lock->locked, _Q_LOCKED_VAL);
-}
-
-static __always_inline
-u32 arena_fetch_set_pending_acquire(arena_spinlock_t __arena *lock)
-{
- u32 old, new;
-
- old = atomic_read(&lock->val);
- do {
- new = old | _Q_PENDING_VAL;
- /*
- * These loops are not expected to stall, but we still need to
- * prove to the verifier they will terminate eventually.
- */
- cond_break_label(out);
- } while (!atomic_try_cmpxchg_acquire(&lock->val, &old, new));
-
- return old;
-out:
- bpf_printk("RUNTIME ERROR: %s unexpected cond_break exit!!!", __func__);
- return old;
-}
-
-/**
- * arena_spin_trylock - try to acquire the queued spinlock
- * @lock : Pointer to queued spinlock structure
- * Return: 1 if lock acquired, 0 if failed
- */
-static __always_inline int arena_spin_trylock(arena_spinlock_t __arena *lock)
-{
- int val = atomic_read(&lock->val);
-
- if (unlikely(val))
- return 0;
-
- return likely(atomic_try_cmpxchg_acquire(&lock->val, &val, _Q_LOCKED_VAL));
-}
-
-__noinline
-int arena_spin_lock_slowpath(arena_spinlock_t __arena __arg_arena *lock, u32 val)
-{
- struct arena_mcs_spinlock __arena *prev, *next, *node0, *node;
- int ret = -ETIMEDOUT;
- u32 old, tail;
- int idx;
-
- /*
- * Wait for in-progress pending->locked hand-overs with a bounded
- * number of spins so that we guarantee forward progress.
- *
- * 0,1,0 -> 0,0,1
- */
- if (val == _Q_PENDING_VAL) {
- int cnt = _Q_PENDING_LOOPS;
- val = atomic_cond_read_relaxed_label(&lock->val,
- (VAL != _Q_PENDING_VAL) || !cnt--,
- release_err);
- }
-
- /*
- * If we observe any contention; queue.
- */
- if (val & ~_Q_LOCKED_MASK)
- goto queue;
-
- /*
- * trylock || pending
- *
- * 0,0,* -> 0,1,* -> 0,0,1 pending, trylock
- */
- val = arena_fetch_set_pending_acquire(lock);
-
- /*
- * If we observe contention, there is a concurrent locker.
- *
- * Undo and queue; our setting of PENDING might have made the
- * n,0,0 -> 0,0,0 transition fail and it will now be waiting
- * on @next to become !NULL.
- */
- if (unlikely(val & ~_Q_LOCKED_MASK)) {
-
- /* Undo PENDING if we set it. */
- if (!(val & _Q_PENDING_MASK))
- clear_pending(lock);
-
- goto queue;
- }
-
- /*
- * We're pending, wait for the owner to go away.
- *
- * 0,1,1 -> *,1,0
- *
- * this wait loop must be a load-acquire such that we match the
- * store-release that clears the locked bit and create lock
- * sequentiality; this is because not all
- * clear_pending_set_locked() implementations imply full
- * barriers.
- */
- if (val & _Q_LOCKED_MASK)
- (void)smp_cond_load_acquire_label(&lock->locked, !VAL, release_err);
-
- /*
- * take ownership and clear the pending bit.
- *
- * 0,1,0 -> 0,0,1
- */
- clear_pending_set_locked(lock);
- return 0;
-
- /*
- * End of pending bit optimistic spinning and beginning of MCS
- * queuing.
- */
-queue:
- node0 = &(qnodes[bpf_get_smp_processor_id()])[0].mcs;
- idx = node0->count++;
- tail = encode_tail(bpf_get_smp_processor_id(), idx);
-
- /*
- * 4 nodes are allocated based on the assumption that there will not be
- * nested NMIs taking spinlocks. That may not be true in some
- * architectures even though the chance of needing more than 4 nodes
- * will still be extremely unlikely. When that happens, we simply return
- * an error. Original qspinlock has a trylock fallback in this case.
- */
- if (unlikely(idx >= _Q_MAX_NODES)) {
- ret = -EBUSY;
- goto release_node_err;
- }
-
- node = grab_mcs_node(node0, idx);
-
- /*
- * Ensure that we increment the head node->count before initialising
- * the actual node. If the compiler is kind enough to reorder these
- * stores, then an IRQ could overwrite our assignments.
- */
- barrier();
-
- node->locked = 0;
- node->next = NULL;
-
- /*
- * We touched a (possibly) cold cacheline in the per-cpu queue node;
- * attempt the trylock once more in the hope someone let go while we
- * weren't watching.
- */
- if (arena_spin_trylock(lock))
- goto release;
-
- /*
- * Ensure that the initialisation of @node is complete before we
- * publish the updated tail via xchg_tail() and potentially link
- * @node into the waitqueue via WRITE_ONCE(prev->next, node) below.
- */
- smp_wmb();
-
- /*
- * Publish the updated tail.
- * We have already touched the queueing cacheline; don't bother with
- * pending stuff.
- *
- * p,*,* -> n,*,*
- */
- old = xchg_tail(lock, tail);
- next = NULL;
-
- /*
- * if there was a previous node; link it and wait until reaching the
- * head of the waitqueue.
- */
- if (old & _Q_TAIL_MASK) {
- prev = decode_tail(old);
-
- /* Link @node into the waitqueue. */
- WRITE_ONCE(prev->next, node);
-
- (void)arch_mcs_spin_lock_contended_label(&node->locked, release_node_err);
-
- /*
- * While waiting for the MCS lock, the next pointer may have
- * been set by another lock waiter. We cannot prefetch here
- * due to lack of equivalent instruction in BPF ISA.
- */
- next = READ_ONCE(node->next);
- }
-
- /*
- * we're at the head of the waitqueue, wait for the owner & pending to
- * go away.
- *
- * *,x,y -> *,0,0
- *
- * this wait loop must use a load-acquire such that we match the
- * store-release that clears the locked bit and create lock
- * sequentiality; this is because the set_locked() function below
- * does not imply a full barrier.
- */
- val = atomic_cond_read_acquire_label(&lock->val, !(VAL & _Q_LOCKED_PENDING_MASK),
- release_node_err);
-
- /*
- * claim the lock:
- *
- * n,0,0 -> 0,0,1 : lock, uncontended
- * *,*,0 -> *,*,1 : lock, contended
- *
- * If the queue head is the only one in the queue (lock value == tail)
- * and nobody is pending, clear the tail code and grab the lock.
- * Otherwise, we only need to grab the lock.
- */
-
- /*
- * In the PV case we might already have _Q_LOCKED_VAL set, because
- * of lock stealing; therefore we must also allow:
- *
- * n,0,1 -> 0,0,1
- *
- * Note: at this point: (val & _Q_PENDING_MASK) == 0, because of the
- * above wait condition, therefore any concurrent setting of
- * PENDING will make the uncontended transition fail.
- */
- if ((val & _Q_TAIL_MASK) == tail) {
- if (atomic_try_cmpxchg_relaxed(&lock->val, &val, _Q_LOCKED_VAL))
- goto release; /* No contention */
- }
-
- /*
- * Either somebody is queued behind us or _Q_PENDING_VAL got set
- * which will then detect the remaining tail and queue behind us
- * ensuring we'll see a @next.
- */
- set_locked(lock);
-
- /*
- * contended path; wait for next if not observed yet, release.
- */
- if (!next)
- next = smp_cond_load_relaxed_label(&node->next, (VAL), release_node_err);
-
- arch_mcs_spin_unlock_contended(&next->locked);
-
-release:;
- /*
- * release the node
- *
- * Doing a normal dec vs this_cpu_dec is fine. An upper context always
- * decrements count it incremented before returning, thus we're fine.
- * For contexts interrupting us, they either observe our dec or not.
- * Just ensure the compiler doesn't reorder this statement, as a
- * this_cpu_dec implicitly implied that.
- */
- barrier();
- node0->count--;
- return 0;
-release_node_err:
- barrier();
- node0->count--;
- goto release_err;
-release_err:
- return ret;
-}
-
-/**
- * arena_spin_lock - acquire a queued spinlock
- * @lock: Pointer to queued spinlock structure
- *
- * On error, returned value will be negative.
- * On success, zero is returned.
- *
- * The return value _must_ be tested against zero for success,
- * instead of checking it against negative, for passing the
- * BPF verifier.
- *
- * The user should do:
- * if (arena_spin_lock(...) != 0) // failure
- * or
- * if (arena_spin_lock(...) == 0) // success
- * or
- * if (arena_spin_lock(...)) // failure
- * or
- * if (!arena_spin_lock(...)) // success
- * instead of:
- * if (arena_spin_lock(...) < 0) // failure
- *
- * The return value can still be inspected later.
- */
-static __always_inline int arena_spin_lock(arena_spinlock_t __arena *lock)
-{
- int val = 0;
-
- if (CONFIG_NR_CPUS > 1024)
- return -EOPNOTSUPP;
-
- bpf_preempt_disable();
- if (likely(atomic_try_cmpxchg_acquire(&lock->val, &val, _Q_LOCKED_VAL)))
- return 0;
-
- val = arena_spin_lock_slowpath(lock, val);
- /* FIXME: bpf_assert_range(-MAX_ERRNO, 0) once we have it working for all cases. */
- if (val)
- bpf_preempt_enable();
- return val;
-}
-
-/**
- * arena_spin_unlock - release a queued spinlock
- * @lock : Pointer to queued spinlock structure
- */
-static __always_inline void arena_spin_unlock(arena_spinlock_t __arena *lock)
-{
- /*
- * unlock() needs release semantics:
- */
- smp_store_release(&lock->locked, 0);
- bpf_preempt_enable();
-}
-
-#define arena_spin_lock_irqsave(lock, flags) \
- ({ \
- int __ret; \
- bpf_local_irq_save(&(flags)); \
- __ret = arena_spin_lock((lock)); \
- if (__ret) \
- bpf_local_irq_restore(&(flags)); \
- (__ret); \
- })
-
-#define arena_spin_unlock_irqrestore(lock, flags) \
- ({ \
- arena_spin_unlock((lock)); \
- bpf_local_irq_restore(&(flags)); \
- })
-
-#endif
-
-#endif /* BPF_ARENA_SPIN_LOCK_H */
diff --git a/tools/testing/selftests/bpf/progs/bpf_cc_cubic.c b/tools/testing/selftests/bpf/progs/bpf_cc_cubic.c
index 9af19dfe4e80..bccf677b94b6 100644
--- a/tools/testing/selftests/bpf/progs/bpf_cc_cubic.c
+++ b/tools/testing/selftests/bpf/progs/bpf_cc_cubic.c
@@ -23,7 +23,7 @@
#define TCP_REORDERING (12)
extern void cubictcp_init(struct sock *sk) __ksym;
-extern void cubictcp_cwnd_event(struct sock *sk, enum tcp_ca_event event) __ksym;
+extern void cubictcp_cwnd_event_tx_start(struct sock *sk) __ksym;
extern __u32 cubictcp_recalc_ssthresh(struct sock *sk) __ksym;
extern void cubictcp_state(struct sock *sk, __u8 new_state) __ksym;
extern __u32 tcp_reno_undo_cwnd(struct sock *sk) __ksym;
@@ -108,9 +108,9 @@ void BPF_PROG(bpf_cubic_init, struct sock *sk)
}
SEC("struct_ops")
-void BPF_PROG(bpf_cubic_cwnd_event, struct sock *sk, enum tcp_ca_event event)
+void BPF_PROG(bpf_cubic_cwnd_event_tx_start, struct sock *sk)
{
- cubictcp_cwnd_event(sk, event);
+ cubictcp_cwnd_event_tx_start(sk);
}
SEC("struct_ops")
@@ -172,7 +172,7 @@ struct tcp_congestion_ops cc_cubic = {
.cong_control = (void *)bpf_cubic_cong_control,
.set_state = (void *)bpf_cubic_state,
.undo_cwnd = (void *)bpf_cubic_undo_cwnd,
- .cwnd_event = (void *)bpf_cubic_cwnd_event,
+ .cwnd_event_tx_start = (void *)bpf_cubic_cwnd_event_tx_start,
.pkts_acked = (void *)bpf_cubic_acked,
.name = "bpf_cc_cubic",
};
diff --git a/tools/testing/selftests/bpf/progs/bpf_cubic.c b/tools/testing/selftests/bpf/progs/bpf_cubic.c
index 46fb2b37d3a7..ebd5a1e69f56 100644
--- a/tools/testing/selftests/bpf/progs/bpf_cubic.c
+++ b/tools/testing/selftests/bpf/progs/bpf_cubic.c
@@ -16,6 +16,7 @@
#include "bpf_tracing_net.h"
#include <bpf/bpf_tracing.h>
+#include <errno.h>
char _license[] SEC("license") = "GPL";
@@ -170,10 +171,18 @@ static void bictcp_hystart_reset(struct sock *sk)
ca->sample_cnt = 0;
}
+bool nodelay_init_reject = false;
+bool nodelay_cwnd_event_tx_start_reject = false;
+
SEC("struct_ops")
void BPF_PROG(bpf_cubic_init, struct sock *sk)
{
struct bpf_bictcp *ca = inet_csk_ca(sk);
+ int true_val = 1, ret;
+
+ ret = bpf_setsockopt(sk, SOL_TCP, TCP_NODELAY, &true_val, sizeof(true_val));
+ if (ret == -EOPNOTSUPP)
+ nodelay_init_reject = true;
bictcp_reset(ca);
@@ -185,24 +194,26 @@ void BPF_PROG(bpf_cubic_init, struct sock *sk)
}
SEC("struct_ops")
-void BPF_PROG(bpf_cubic_cwnd_event, struct sock *sk, enum tcp_ca_event event)
+void BPF_PROG(bpf_cubic_cwnd_event_tx_start, struct sock *sk)
{
- if (event == CA_EVENT_TX_START) {
- struct bpf_bictcp *ca = inet_csk_ca(sk);
- __u32 now = tcp_jiffies32;
- __s32 delta;
-
- delta = now - tcp_sk(sk)->lsndtime;
-
- /* We were application limited (idle) for a while.
- * Shift epoch_start to keep cwnd growth to cubic curve.
- */
- if (ca->epoch_start && delta > 0) {
- ca->epoch_start += delta;
- if (after(ca->epoch_start, now))
- ca->epoch_start = now;
- }
- return;
+ struct bpf_bictcp *ca = inet_csk_ca(sk);
+ __u32 now = tcp_jiffies32;
+ int true_val = 1, ret;
+ __s32 delta;
+
+ ret = bpf_setsockopt(sk, SOL_TCP, TCP_NODELAY, &true_val, sizeof(true_val));
+ if (ret == -EOPNOTSUPP)
+ nodelay_cwnd_event_tx_start_reject = true;
+
+ delta = now - tcp_sk(sk)->lsndtime;
+
+ /* We were application limited (idle) for a while.
+ * Shift epoch_start to keep cwnd growth to cubic curve.
+ */
+ if (ca->epoch_start && delta > 0) {
+ ca->epoch_start += delta;
+ if (after(ca->epoch_start, now))
+ ca->epoch_start = now;
}
}
@@ -537,7 +548,7 @@ struct tcp_congestion_ops cubic = {
.cong_avoid = (void *)bpf_cubic_cong_avoid,
.set_state = (void *)bpf_cubic_state,
.undo_cwnd = (void *)bpf_cubic_undo_cwnd,
- .cwnd_event = (void *)bpf_cubic_cwnd_event,
+ .cwnd_event_tx_start = (void *)bpf_cubic_cwnd_event_tx_start,
.pkts_acked = (void *)bpf_cubic_acked,
.name = "bpf_cubic",
};
diff --git a/tools/testing/selftests/bpf/progs/bpf_for_bench.c b/tools/testing/selftests/bpf/progs/bpf_for_bench.c
new file mode 100644
index 000000000000..f9c723051fc7
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/bpf_for_bench.c
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+int nr_loops;
+long hits;
+
+static int outer_loop(__u32 index, void *data)
+{
+ int i;
+
+ /*
+ * Empty body: the work being measured is the open-coded numeric iterator itself
+ * (bpf_iter_num_new/next/destroy behind bpf_for()).
+ */
+ bpf_for(i, 0, nr_loops)
+ ;
+ __sync_add_and_fetch(&hits, nr_loops);
+ return 0;
+}
+
+SEC("fentry/" SYS_PREFIX "sys_getpgid")
+int benchmark(void *ctx)
+{
+ bpf_loop(1000, outer_loop, NULL, 0);
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/bpf_gotox.c b/tools/testing/selftests/bpf/progs/bpf_gotox.c
index 216c71b94c64..99b3c9c9a01c 100644
--- a/tools/testing/selftests/bpf/progs/bpf_gotox.c
+++ b/tools/testing/selftests/bpf/progs/bpf_gotox.c
@@ -421,6 +421,36 @@ int use_nonstatic_global_other_sec(void *ctx)
return __nonstatic_global(in_user);
}
+SEC("syscall")
+int load_with_nonzero_offset(struct simple_ctx *ctx)
+{
+ void *jj[] = { &&l1, &&l2, &&l3 };
+
+ /*
+ * This makes LLVM to generate a load from the jj map with an offset:
+ * r1 = 0x0 ll
+ * r1 = *(u64 *)(r1 + 0x10)
+ * gotox r1
+ */
+ if (ctx->x == 2)
+ goto *jj[ctx->x];
+
+ ret_user = 1;
+ return 1;
+
+l1:
+ /* never reached, but leave it here to outsmart LLVM */
+ ret_user = 0;
+ return 0;
+l2:
+ /* never reached, but leave it here to outsmart LLVM */
+ ret_user = 3;
+ return 3;
+l3:
+ ret_user = 5;
+ return 5;
+}
+
#else /* __BPF_FEATURE_GOTOX */
#define SKIP_TEST(TEST_NAME) \
@@ -442,6 +472,7 @@ SKIP_TEST(use_static_global_other_sec);
SKIP_TEST(use_nonstatic_global1);
SKIP_TEST(use_nonstatic_global2);
SKIP_TEST(use_nonstatic_global_other_sec);
+SKIP_TEST(load_with_nonzero_offset);
#endif /* __BPF_FEATURE_GOTOX */
diff --git a/tools/testing/selftests/bpf/progs/bpf_iter_bpf_rhash_map.c b/tools/testing/selftests/bpf/progs/bpf_iter_bpf_rhash_map.c
new file mode 100644
index 000000000000..86f6c0d5eadb
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/bpf_iter_bpf_rhash_map.c
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+char _license[] SEC("license") = "GPL";
+
+struct {
+ __uint(type, BPF_MAP_TYPE_RHASH);
+ __uint(map_flags, BPF_F_NO_PREALLOC);
+ __uint(max_entries, 64);
+ __type(key, __u32);
+ __type(value, __u64);
+} rhashmap SEC(".maps");
+
+__u32 key_sum = 0;
+__u64 val_sum = 0;
+__u32 elem_count = 0;
+__u32 err = 0;
+
+SEC("iter/bpf_map_elem")
+int dump_bpf_rhash_map(struct bpf_iter__bpf_map_elem *ctx)
+{
+ __u32 *key = ctx->key;
+ __u64 *val = ctx->value;
+
+ if (!key || !val)
+ return 0;
+
+ key_sum += *key;
+ val_sum += *val;
+ elem_count++;
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/bpf_iter_task_vmas.c b/tools/testing/selftests/bpf/progs/bpf_iter_task_vmas.c
index d64ba7ddaed5..d7fb561ed4fb 100644
--- a/tools/testing/selftests/bpf/progs/bpf_iter_task_vmas.c
+++ b/tools/testing/selftests/bpf/progs/bpf_iter_task_vmas.c
@@ -52,7 +52,7 @@ SEC("iter/task_vma") int proc_maps(struct bpf_iter__task_vma *ctx)
bpf_d_path(&file->f_path, d_path_buf, D_PATH_BUF_SIZE);
BPF_SEQ_PRINTF(seq, "%08llx ", vma->vm_pgoff << 12);
- BPF_SEQ_PRINTF(seq, "%02x:%02x %u", MAJOR(dev), MINOR(dev),
+ BPF_SEQ_PRINTF(seq, "%02x:%02x %llu", MAJOR(dev), MINOR(dev),
file->f_inode->i_ino);
BPF_SEQ_PRINTF(seq, "\t%s\n", d_path_buf);
} else {
diff --git a/tools/testing/selftests/bpf/progs/bpf_iter_unix.c b/tools/testing/selftests/bpf/progs/bpf_iter_unix.c
index fea275df9e22..a2652c8c3616 100644
--- a/tools/testing/selftests/bpf/progs/bpf_iter_unix.c
+++ b/tools/testing/selftests/bpf/progs/bpf_iter_unix.c
@@ -7,6 +7,13 @@
char _license[] SEC("license") = "GPL";
+SEC(".maps") struct {
+ __uint(type, BPF_MAP_TYPE_SOCKMAP);
+ __uint(max_entries, 1);
+ __type(key, __u32);
+ __type(value, __u64);
+} sockmap;
+
static long sock_i_ino(const struct sock *sk)
{
const struct socket *sk_socket = sk->sk_socket;
@@ -76,5 +83,8 @@ int dump_unix(struct bpf_iter__unix *ctx)
BPF_SEQ_PRINTF(seq, "\n");
+ /* Test for deadlock. */
+ bpf_map_update_elem(&sockmap, &(int){0}, sk, 0);
+
return 0;
}
diff --git a/tools/testing/selftests/bpf/progs/bpf_misc.h b/tools/testing/selftests/bpf/progs/bpf_misc.h
index c9bfbe1bafc1..5eacf1b43252 100644
--- a/tools/testing/selftests/bpf/progs/bpf_misc.h
+++ b/tools/testing/selftests/bpf/progs/bpf_misc.h
@@ -103,8 +103,8 @@
* - TEST_DATA_LEN
* __retval_unpriv Same, but load program in unprivileged mode.
*
- * __description Text to be used instead of a program name for display
- * and filtering purposes.
+ * __description Text to be used for display and as an additional filter
+ * alias, while the original program name stays matchable.
*
* __log_level Log level to use for the program, numeric value expected.
*
@@ -130,39 +130,43 @@
* __linear_size Specify the size of the linear area of non-linear skbs, or
* 0 for linear skbs.
*/
-#define __msg(msg) __attribute__((btf_decl_tag("comment:test_expect_msg=" XSTR(__COUNTER__) "=" msg)))
-#define __not_msg(msg) __attribute__((btf_decl_tag("comment:test_expect_not_msg=" XSTR(__COUNTER__) "=" msg)))
-#define __xlated(msg) __attribute__((btf_decl_tag("comment:test_expect_xlated=" XSTR(__COUNTER__) "=" msg)))
-#define __jited(msg) __attribute__((btf_decl_tag("comment:test_jited=" XSTR(__COUNTER__) "=" msg)))
-#define __failure __attribute__((btf_decl_tag("comment:test_expect_failure")))
-#define __success __attribute__((btf_decl_tag("comment:test_expect_success")))
-#define __description(desc) __attribute__((btf_decl_tag("comment:test_description=" desc)))
-#define __msg_unpriv(msg) __attribute__((btf_decl_tag("comment:test_expect_msg_unpriv=" XSTR(__COUNTER__) "=" msg)))
-#define __not_msg_unpriv(msg) __attribute__((btf_decl_tag("comment:test_expect_not_msg_unpriv=" XSTR(__COUNTER__) "=" msg)))
-#define __xlated_unpriv(msg) __attribute__((btf_decl_tag("comment:test_expect_xlated_unpriv=" XSTR(__COUNTER__) "=" msg)))
-#define __jited_unpriv(msg) __attribute__((btf_decl_tag("comment:test_jited=" XSTR(__COUNTER__) "=" msg)))
-#define __failure_unpriv __attribute__((btf_decl_tag("comment:test_expect_failure_unpriv")))
-#define __success_unpriv __attribute__((btf_decl_tag("comment:test_expect_success_unpriv")))
-#define __log_level(lvl) __attribute__((btf_decl_tag("comment:test_log_level="#lvl)))
-#define __flag(flag) __attribute__((btf_decl_tag("comment:test_prog_flags="#flag)))
-#define __retval(val) __attribute__((btf_decl_tag("comment:test_retval="XSTR(val))))
-#define __retval_unpriv(val) __attribute__((btf_decl_tag("comment:test_retval_unpriv="XSTR(val))))
-#define __auxiliary __attribute__((btf_decl_tag("comment:test_auxiliary")))
-#define __auxiliary_unpriv __attribute__((btf_decl_tag("comment:test_auxiliary_unpriv")))
-#define __btf_path(path) __attribute__((btf_decl_tag("comment:test_btf_path=" path)))
-#define __arch(arch) __attribute__((btf_decl_tag("comment:test_arch=" arch)))
+#define __test_tag(tag) __attribute__((btf_decl_tag("comment:" XSTR(__COUNTER__) ":" tag)))
+
+#define __msg(msg) __test_tag("test_expect_msg=" msg)
+#define __not_msg(msg) __test_tag("test_expect_not_msg=" msg)
+#define __xlated(msg) __test_tag("test_expect_xlated=" msg)
+#define __jited(msg) __test_tag("test_jited=" msg)
+#define __failure __test_tag("test_expect_failure")
+#define __success __test_tag("test_expect_success")
+#define __description(desc) __test_tag("test_description=" desc)
+#define __msg_unpriv(msg) __test_tag("test_expect_msg_unpriv=" msg)
+#define __not_msg_unpriv(msg) __test_tag("test_expect_not_msg_unpriv=" msg)
+#define __xlated_unpriv(msg) __test_tag("test_expect_xlated_unpriv=" msg)
+#define __jited_unpriv(msg) __test_tag("test_jited_unpriv=" msg)
+#define __failure_unpriv __test_tag("test_expect_failure_unpriv")
+#define __success_unpriv __test_tag("test_expect_success_unpriv")
+#define __log_level(lvl) __test_tag("test_log_level=" #lvl)
+#define __flag(flag) __test_tag("test_prog_flags=" #flag)
+#define __retval(val) __test_tag("test_retval=" XSTR(val))
+#define __retval_unpriv(val) __test_tag("test_retval_unpriv=" XSTR(val))
+#define __auxiliary __test_tag("test_auxiliary")
+#define __auxiliary_unpriv __test_tag("test_auxiliary_unpriv")
+#define __btf_path(path) __test_tag("test_btf_path=" path)
+#define __btf_func_path(path) __test_tag("test_btf_func_path=" path)
+#define __arch(arch) __test_tag("test_arch=" arch)
#define __arch_x86_64 __arch("X86_64")
#define __arch_arm64 __arch("ARM64")
#define __arch_riscv64 __arch("RISCV64")
#define __arch_s390x __arch("s390x")
-#define __caps_unpriv(caps) __attribute__((btf_decl_tag("comment:test_caps_unpriv=" EXPAND_QUOTE(caps))))
-#define __load_if_JITed() __attribute__((btf_decl_tag("comment:load_mode=jited")))
-#define __load_if_no_JITed() __attribute__((btf_decl_tag("comment:load_mode=no_jited")))
-#define __stderr(msg) __attribute__((btf_decl_tag("comment:test_expect_stderr=" XSTR(__COUNTER__) "=" msg)))
-#define __stderr_unpriv(msg) __attribute__((btf_decl_tag("comment:test_expect_stderr_unpriv=" XSTR(__COUNTER__) "=" msg)))
-#define __stdout(msg) __attribute__((btf_decl_tag("comment:test_expect_stdout=" XSTR(__COUNTER__) "=" msg)))
-#define __stdout_unpriv(msg) __attribute__((btf_decl_tag("comment:test_expect_stdout_unpriv=" XSTR(__COUNTER__) "=" msg)))
-#define __linear_size(sz) __attribute__((btf_decl_tag("comment:test_linear_size=" XSTR(sz))))
+#define __arch_loongarch __arch("LOONGARCH")
+#define __caps_unpriv(caps) __test_tag("test_caps_unpriv=" EXPAND_QUOTE(caps))
+#define __load_if_JITed() __test_tag("load_mode=jited")
+#define __load_if_no_JITed() __test_tag("load_mode=no_jited")
+#define __stderr(msg) __test_tag("test_expect_stderr=" msg)
+#define __stderr_unpriv(msg) __test_tag("test_expect_stderr_unpriv=" msg)
+#define __stdout(msg) __test_tag("test_expect_stdout=" msg)
+#define __stdout_unpriv(msg) __test_tag("test_expect_stdout_unpriv=" msg)
+#define __linear_size(sz) __test_tag("test_linear_size=" XSTR(sz))
/* Define common capabilities tested using __caps_unpriv */
#define CAP_NET_ADMIN 12
@@ -188,6 +192,10 @@
#define POINTER_VALUE 0xbadcafe
#define TEST_DATA_LEN 64
+#ifndef __aligned
+#define __aligned(x) __attribute__((aligned(x)))
+#endif
+
#ifndef __used
#define __used __attribute__((used))
#endif
@@ -256,9 +264,12 @@
#endif
#if __clang_major__ >= 18 && defined(ENABLE_ATOMICS_TESTS) && \
- (defined(__TARGET_ARCH_arm64) || defined(__TARGET_ARCH_x86) || \
- (defined(__TARGET_ARCH_riscv) && __riscv_xlen == 64)) || \
- (defined(__TARGET_ARCH_powerpc))
+ (defined(__TARGET_ARCH_arm64) || \
+ defined(__TARGET_ARCH_x86) || \
+ (defined(__TARGET_ARCH_riscv) && __riscv_xlen == 64) || \
+ defined(__TARGET_ARCH_s390) || \
+ defined(__TARGET_ARCH_powerpc) || \
+ defined(__TARGET_ARCH_loongarch))
#define CAN_USE_LOAD_ACQ_STORE_REL
#endif
diff --git a/tools/testing/selftests/bpf/progs/bpf_nop_bench.c b/tools/testing/selftests/bpf/progs/bpf_nop_bench.c
new file mode 100644
index 000000000000..01ed284c1bb3
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/bpf_nop_bench.c
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bench_bpf_timing.bpf.h"
+
+SEC("syscall")
+int bench_nop(void *ctx)
+{
+ return BENCH_BPF_LOOP(0, ({}));
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/bpf_qdisc_dynptr_use_after_invalidate_clone.c b/tools/testing/selftests/bpf/progs/bpf_qdisc_dynptr_use_after_invalidate_clone.c
new file mode 100644
index 000000000000..ac626cfa2a98
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/bpf_qdisc_dynptr_use_after_invalidate_clone.c
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include "bpf_experimental.h"
+#include "bpf_qdisc_common.h"
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+int proto;
+
+SEC("struct_ops")
+__success
+int BPF_PROG(dynptr_use_after_invalidate_clone, struct sk_buff *skb, struct Qdisc *sch,
+ struct bpf_sk_buff_ptr *to_free)
+{
+ struct bpf_dynptr ptr, ptr_clone;
+ struct ethhdr *hdr;
+
+ bpf_dynptr_from_skb((struct __sk_buff *)skb, 0, &ptr);
+
+ bpf_dynptr_clone(&ptr, &ptr_clone);
+
+ hdr = bpf_dynptr_slice(&ptr_clone, 0, NULL, sizeof(*hdr));
+ if (!hdr) {
+ bpf_qdisc_skb_drop(skb, to_free);
+ return NET_XMIT_DROP;
+ }
+
+ *(int *)&ptr = 0;
+
+ proto = hdr->h_proto;
+
+ bpf_qdisc_skb_drop(skb, to_free);
+
+ return NET_XMIT_DROP;
+}
+
+SEC("struct_ops")
+__auxiliary
+struct sk_buff *BPF_PROG(bpf_qdisc_test_dequeue, struct Qdisc *sch)
+{
+ return NULL;
+}
+
+SEC("struct_ops")
+__auxiliary
+int BPF_PROG(bpf_qdisc_test_init, struct Qdisc *sch, struct nlattr *opt,
+ struct netlink_ext_ack *extack)
+{
+ return 0;
+}
+
+SEC("struct_ops")
+__auxiliary
+void BPF_PROG(bpf_qdisc_test_reset, struct Qdisc *sch)
+{
+}
+
+SEC("struct_ops")
+__auxiliary
+void BPF_PROG(bpf_qdisc_test_destroy, struct Qdisc *sch)
+{
+}
+
+SEC(".struct_ops")
+struct Qdisc_ops test = {
+ .enqueue = (void *)dynptr_use_after_invalidate_clone,
+ .dequeue = (void *)bpf_qdisc_test_dequeue,
+ .init = (void *)bpf_qdisc_test_init,
+ .reset = (void *)bpf_qdisc_test_reset,
+ .destroy = (void *)bpf_qdisc_test_destroy,
+ .id = "bpf_qdisc_test",
+};
diff --git a/tools/testing/selftests/bpf/progs/bpf_qdisc_fail__invalid_dynptr.c b/tools/testing/selftests/bpf/progs/bpf_qdisc_fail__invalid_dynptr.c
new file mode 100644
index 000000000000..1d96f7987a3f
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/bpf_qdisc_fail__invalid_dynptr.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include "bpf_experimental.h"
+#include "bpf_qdisc_common.h"
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+int proto;
+
+SEC("struct_ops")
+__failure __msg("Expected an initialized dynptr as R1")
+int BPF_PROG(invalid_dynptr, struct sk_buff *skb, struct Qdisc *sch,
+ struct bpf_sk_buff_ptr *to_free)
+{
+ struct bpf_dynptr ptr;
+ struct ethhdr *hdr;
+
+ bpf_dynptr_from_skb((struct __sk_buff *)skb, 0, &ptr);
+
+ bpf_qdisc_skb_drop(skb, to_free);
+
+ hdr = bpf_dynptr_slice(&ptr, 0, NULL, sizeof(*hdr));
+ if (!hdr)
+ return NET_XMIT_DROP;
+
+ proto = hdr->h_proto;
+
+ return NET_XMIT_DROP;
+}
+
+SEC("struct_ops")
+__auxiliary
+struct sk_buff *BPF_PROG(bpf_qdisc_test_dequeue, struct Qdisc *sch)
+{
+ return NULL;
+}
+
+SEC("struct_ops")
+__auxiliary
+int BPF_PROG(bpf_qdisc_test_init, struct Qdisc *sch, struct nlattr *opt,
+ struct netlink_ext_ack *extack)
+{
+ return 0;
+}
+
+SEC("struct_ops")
+__auxiliary
+void BPF_PROG(bpf_qdisc_test_reset, struct Qdisc *sch)
+{
+}
+
+SEC("struct_ops")
+__auxiliary
+void BPF_PROG(bpf_qdisc_test_destroy, struct Qdisc *sch)
+{
+}
+
+SEC(".struct_ops")
+struct Qdisc_ops test = {
+ .enqueue = (void *)invalid_dynptr,
+ .dequeue = (void *)bpf_qdisc_test_dequeue,
+ .init = (void *)bpf_qdisc_test_init,
+ .reset = (void *)bpf_qdisc_test_reset,
+ .destroy = (void *)bpf_qdisc_test_destroy,
+ .id = "bpf_qdisc_test",
+};
diff --git a/tools/testing/selftests/bpf/progs/bpf_qdisc_fail__invalid_dynptr_cross_frame.c b/tools/testing/selftests/bpf/progs/bpf_qdisc_fail__invalid_dynptr_cross_frame.c
new file mode 100644
index 000000000000..2e23b8593af9
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/bpf_qdisc_fail__invalid_dynptr_cross_frame.c
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include "bpf_experimental.h"
+#include "bpf_qdisc_common.h"
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+int proto;
+
+static __noinline int free_skb(struct sk_buff *skb)
+{
+ bpf_kfree_skb(skb);
+ return 0;
+}
+
+SEC("struct_ops")
+__failure __msg("invalid mem access 'scalar'")
+int BPF_PROG(invalid_dynptr_cross_frame, struct sk_buff *skb, struct Qdisc *sch,
+ struct bpf_sk_buff_ptr *to_free)
+{
+ struct bpf_dynptr ptr;
+ struct ethhdr *hdr;
+
+ bpf_dynptr_from_skb((struct __sk_buff *)skb, 0, &ptr);
+
+ hdr = bpf_dynptr_slice(&ptr, 0, NULL, sizeof(*hdr));
+ if (!hdr)
+ return NET_XMIT_DROP;
+
+ free_skb(skb);
+
+ proto = hdr->h_proto;
+
+ return NET_XMIT_DROP;
+}
+
+SEC("struct_ops")
+__auxiliary
+struct sk_buff *BPF_PROG(bpf_qdisc_test_dequeue, struct Qdisc *sch)
+{
+ return NULL;
+}
+
+SEC("struct_ops")
+__auxiliary
+int BPF_PROG(bpf_qdisc_test_init, struct Qdisc *sch, struct nlattr *opt,
+ struct netlink_ext_ack *extack)
+{
+ return 0;
+}
+
+SEC("struct_ops")
+__auxiliary
+void BPF_PROG(bpf_qdisc_test_reset, struct Qdisc *sch)
+{
+}
+
+SEC("struct_ops")
+__auxiliary
+void BPF_PROG(bpf_qdisc_test_destroy, struct Qdisc *sch)
+{
+}
+
+SEC(".struct_ops")
+struct Qdisc_ops test = {
+ .enqueue = (void *)invalid_dynptr_cross_frame,
+ .dequeue = (void *)bpf_qdisc_test_dequeue,
+ .init = (void *)bpf_qdisc_test_init,
+ .reset = (void *)bpf_qdisc_test_reset,
+ .destroy = (void *)bpf_qdisc_test_destroy,
+ .id = "bpf_qdisc_test",
+};
diff --git a/tools/testing/selftests/bpf/progs/bpf_qdisc_fail__invalid_dynptr_slice.c b/tools/testing/selftests/bpf/progs/bpf_qdisc_fail__invalid_dynptr_slice.c
new file mode 100644
index 000000000000..731216c4e45a
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/bpf_qdisc_fail__invalid_dynptr_slice.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include "bpf_experimental.h"
+#include "bpf_qdisc_common.h"
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+int proto;
+
+SEC("struct_ops")
+__failure __msg("invalid mem access 'scalar'")
+int BPF_PROG(invalid_dynptr_slice, struct sk_buff *skb, struct Qdisc *sch,
+ struct bpf_sk_buff_ptr *to_free)
+{
+ struct bpf_dynptr ptr;
+ struct ethhdr *hdr;
+
+ bpf_dynptr_from_skb((struct __sk_buff *)skb, 0, &ptr);
+
+ hdr = bpf_dynptr_slice(&ptr, 0, NULL, sizeof(*hdr));
+ if (!hdr) {
+ bpf_qdisc_skb_drop(skb, to_free);
+ return NET_XMIT_DROP;
+ }
+
+ bpf_qdisc_skb_drop(skb, to_free);
+
+ proto = hdr->h_proto;
+
+ return NET_XMIT_DROP;
+}
+
+SEC("struct_ops")
+__auxiliary
+struct sk_buff *BPF_PROG(bpf_qdisc_test_dequeue, struct Qdisc *sch)
+{
+ return NULL;
+}
+
+SEC("struct_ops")
+__auxiliary
+int BPF_PROG(bpf_qdisc_test_init, struct Qdisc *sch, struct nlattr *opt,
+ struct netlink_ext_ack *extack)
+{
+ return 0;
+}
+
+SEC("struct_ops")
+__auxiliary
+void BPF_PROG(bpf_qdisc_test_reset, struct Qdisc *sch)
+{
+}
+
+SEC("struct_ops")
+__auxiliary
+void BPF_PROG(bpf_qdisc_test_destroy, struct Qdisc *sch)
+{
+}
+
+SEC(".struct_ops")
+struct Qdisc_ops test = {
+ .enqueue = (void *)invalid_dynptr_slice,
+ .dequeue = (void *)bpf_qdisc_test_dequeue,
+ .init = (void *)bpf_qdisc_test_init,
+ .reset = (void *)bpf_qdisc_test_reset,
+ .destroy = (void *)bpf_qdisc_test_destroy,
+ .id = "bpf_qdisc_test",
+};
diff --git a/tools/testing/selftests/bpf/progs/bpf_qdisc_fail__untrusted_write.c b/tools/testing/selftests/bpf/progs/bpf_qdisc_fail__untrusted_write.c
new file mode 100644
index 000000000000..688c2a049ae3
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/bpf_qdisc_fail__untrusted_write.c
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include "bpf_experimental.h"
+#include "bpf_qdisc_common.h"
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+SEC("struct_ops")
+__failure __msg("only read is supported")
+int BPF_PROG(untrusted_write, struct sk_buff *skb, struct Qdisc *sch,
+ struct bpf_sk_buff_ptr *to_free)
+{
+ struct Qdisc *next = sch->next_sched;
+
+ /*
+ * sch is trusted, but the walk of next_sched yields a plain
+ * PTR_TO_BTF_ID which may fault on a dereference. A store through
+ * it does not get an exception table entry, there is no probed
+ * store to rewrite it into, hence it has to be rejected before
+ * bpf_qdisc_btf_struct_access() gets to allow the write to limit.
+ */
+ next->limit = 1000;
+
+ bpf_qdisc_skb_drop(skb, to_free);
+ return NET_XMIT_DROP;
+}
+
+SEC("struct_ops")
+__auxiliary
+struct sk_buff *BPF_PROG(bpf_qdisc_test_dequeue, struct Qdisc *sch)
+{
+ return NULL;
+}
+
+SEC("struct_ops")
+__auxiliary
+int BPF_PROG(bpf_qdisc_test_init, struct Qdisc *sch, struct nlattr *opt,
+ struct netlink_ext_ack *extack)
+{
+ return 0;
+}
+
+SEC("struct_ops")
+__auxiliary
+void BPF_PROG(bpf_qdisc_test_reset, struct Qdisc *sch)
+{
+}
+
+SEC("struct_ops")
+__auxiliary
+void BPF_PROG(bpf_qdisc_test_destroy, struct Qdisc *sch)
+{
+}
+
+SEC(".struct_ops")
+struct Qdisc_ops test = {
+ .enqueue = (void *)untrusted_write,
+ .dequeue = (void *)bpf_qdisc_test_dequeue,
+ .init = (void *)bpf_qdisc_test_init,
+ .reset = (void *)bpf_qdisc_test_reset,
+ .destroy = (void *)bpf_qdisc_test_destroy,
+ .id = "bpf_qdisc_test",
+};
diff --git a/tools/testing/selftests/bpf/progs/bpf_qdisc_fq.c b/tools/testing/selftests/bpf/progs/bpf_qdisc_fq.c
index 1a3233a275c7..8107f5934d2d 100644
--- a/tools/testing/selftests/bpf/progs/bpf_qdisc_fq.c
+++ b/tools/testing/selftests/bpf/progs/bpf_qdisc_fq.c
@@ -196,18 +196,13 @@ fq_flows_remove_front(struct bpf_list_head *head, struct bpf_spin_lock *lock,
static bool
fq_flows_is_empty(struct bpf_list_head *head, struct bpf_spin_lock *lock)
{
- struct bpf_list_node *node;
+ bool empty;
bpf_spin_lock(lock);
- node = bpf_list_pop_front(head);
- if (node) {
- bpf_list_push_front(head, node);
- bpf_spin_unlock(lock);
- return false;
- }
+ empty = bpf_list_empty(head);
bpf_spin_unlock(lock);
- return true;
+ return empty;
}
/* flow->age is used to denote the state of the flow (not-detached, detached, throttled)
diff --git a/tools/testing/selftests/bpf/progs/bpf_smc.c b/tools/testing/selftests/bpf/progs/bpf_smc.c
index 70d8b08f5914..6263a45bf006 100644
--- a/tools/testing/selftests/bpf/progs/bpf_smc.c
+++ b/tools/testing/selftests/bpf/progs/bpf_smc.c
@@ -8,6 +8,10 @@
char _license[] SEC("license") = "GPL";
+#ifndef SMC_HS_CTRL_NAME_MAX
+#define SMC_HS_CTRL_NAME_MAX 16
+#endif
+
enum {
BPF_SMC_LISTEN = 10,
};
@@ -18,6 +22,20 @@ struct smc_sock___local {
bool use_fallback;
} __attribute__((preserve_access_index));
+struct smc_hs_ctrl___local {
+ char name[SMC_HS_CTRL_NAME_MAX];
+ int (*syn_option)(struct tcp_sock *);
+ int (*synack_option)(const struct tcp_sock *, struct inet_request_sock *);
+} __attribute__((preserve_access_index));
+
+struct netns_smc___local {
+ struct smc_hs_ctrl___local *hs_ctrl;
+} __attribute__((preserve_access_index));
+
+struct net___local {
+ struct netns_smc___local smc;
+} __attribute__((preserve_access_index));
+
int smc_cnt = 0;
int fallback_cnt = 0;
@@ -88,8 +106,14 @@ int BPF_PROG(smc_run, int family, int type, int protocol)
task = bpf_get_current_task_btf();
/* Prevent from affecting other tests */
- if (!task || !task->nsproxy->net_ns->smc.hs_ctrl)
+ if (!task) {
return protocol;
+ } else {
+ struct net___local *net = (struct net___local *)task->nsproxy->net_ns;
+
+ if (!bpf_core_field_exists(struct net___local, smc) || !net->smc.hs_ctrl)
+ return protocol;
+ }
return IPPROTO_SMC;
}
@@ -110,7 +134,7 @@ int BPF_PROG(bpf_smc_set_tcp_option, struct tcp_sock *tp)
}
SEC(".struct_ops")
-struct smc_hs_ctrl linkcheck = {
+struct smc_hs_ctrl___local linkcheck = {
.name = "linkcheck",
.syn_option = (void *)bpf_smc_set_tcp_option,
.synack_option = (void *)bpf_smc_set_tcp_option_cond,
diff --git a/tools/testing/selftests/bpf/progs/bpf_tracing_net.h b/tools/testing/selftests/bpf/progs/bpf_tracing_net.h
index d8dacef37c16..593b38f90417 100644
--- a/tools/testing/selftests/bpf/progs/bpf_tracing_net.h
+++ b/tools/testing/selftests/bpf/progs/bpf_tracing_net.h
@@ -8,6 +8,9 @@
#define AF_INET 2
#define AF_INET6 10
+/* include/linux/net.h */
+#define SOCK_TYPE_MASK 0xf
+
#define SOL_SOCKET 1
#define SO_REUSEADDR 2
#define SO_SNDBUF 7
@@ -28,10 +31,12 @@
#define __SO_ACCEPTCON (1 << 16)
#define IP_TOS 1
+#define IP_TRANSPARENT 19
#define SOL_IPV6 41
#define IPV6_TCLASS 67
#define IPV6_AUTOFLOWLABEL 70
+#define IPV6_TRANSPARENT 75
#define TC_ACT_UNSPEC (-1)
#define TC_ACT_OK 0
diff --git a/tools/testing/selftests/bpf/progs/btf__stack_arg_precision.c b/tools/testing/selftests/bpf/progs/btf__stack_arg_precision.c
new file mode 100644
index 000000000000..8d38aafe66a2
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/btf__stack_arg_precision.c
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+#if (defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) && \
+ defined(__BPF_FEATURE_STACK_ARGUMENT)
+
+long subprog_call_mem_kfunc(long a, long b, long c, long d, long e, long size)
+{
+ char buf[8] = {};
+
+ return bpf_kfunc_call_stack_arg_mem(a, b, c, d, e, buf, size);
+}
+
+#else
+
+long subprog_call_mem_kfunc(void)
+{
+ return 0;
+}
+
+#endif
diff --git a/tools/testing/selftests/bpf/progs/btf__verifier_stack_arg_order.c b/tools/testing/selftests/bpf/progs/btf__verifier_stack_arg_order.c
new file mode 100644
index 000000000000..99bc115f8380
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/btf__verifier_stack_arg_order.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+#if (defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) && \
+ defined(__BPF_FEATURE_STACK_ARGUMENT)
+
+int subprog_bad_order_6args(int a, int b, int c, int d, int e, int f)
+{
+ return a + b + c + d + e + f;
+}
+
+int subprog_call_before_load_6args(int a, int b, int c, int d, int e, int f)
+{
+ return a + b + c + d + e + f;
+}
+
+int subprog_pruning_call_before_load_6args(int a, int b, int c, int d, int e, int f)
+{
+ return a + b + c + d + e + f;
+}
+
+void subprog_bad_ptr_7args(long *a, int b, int c, int d, int e, int f, int g)
+{
+}
+
+#else
+
+int subprog_bad_order_6args(void)
+{
+ return 0;
+}
+
+int subprog_call_before_load_6args(void)
+{
+ return 0;
+}
+
+int subprog_pruning_call_before_load_6args(void)
+{
+ return 0;
+}
+
+void subprog_bad_ptr_7args(void)
+{
+}
+
+#endif
diff --git a/tools/testing/selftests/bpf/progs/btf_data.c b/tools/testing/selftests/bpf/progs/btf_data.c
index baa525275bde..8082c13490ab 100644
--- a/tools/testing/selftests/bpf/progs/btf_data.c
+++ b/tools/testing/selftests/bpf/progs/btf_data.c
@@ -48,3 +48,43 @@ int func(struct root_struct *root)
{
return 0;
}
+
+int kfunc_a(struct root_struct *root)
+{
+ return 0;
+}
+
+int kfunc_b(struct root_struct *root)
+{
+ return 0;
+}
+
+struct root_struct *kfunc_c(struct root_struct *a, struct root_struct *b)
+{
+ return a;
+}
+
+int kfunc_d(struct root_struct *a, struct root_struct *b)
+{
+ return 0;
+}
+
+int kfunc_e(struct root_struct *a__arena,
+ struct root_struct *b__arena__nullable,
+ struct root_struct *c__arena,
+ struct root_struct *d__arena__nullable,
+ struct root_struct *e__arena)
+{
+ return 0;
+}
+
+int kfunc_f(struct root_struct *a, struct root_struct *b__arena, int flags)
+{
+ return 0;
+}
+
+struct root_struct *kfunc_g(struct root_struct *a__arena,
+ struct root_struct *b__arena__nullable)
+{
+ return a__arena;
+}
diff --git a/tools/testing/selftests/bpf/progs/cgroup_hierarchical_stats.c b/tools/testing/selftests/bpf/progs/cgroup_hierarchical_stats.c
index ff189a736ad8..8fc38592a87b 100644
--- a/tools/testing/selftests/bpf/progs/cgroup_hierarchical_stats.c
+++ b/tools/testing/selftests/bpf/progs/cgroup_hierarchical_stats.c
@@ -62,9 +62,9 @@ static int create_attach_counter(__u64 cg_id, __u64 state, __u64 pending)
&init, BPF_NOEXIST);
}
-SEC("fentry/cgroup_attach_task")
-int BPF_PROG(counter, struct cgroup *dst_cgrp, struct task_struct *leader,
- bool threadgroup)
+SEC("tp_btf/cgroup_attach_task")
+int BPF_PROG(counter, struct cgroup *dst_cgrp, const char *path,
+ struct task_struct *task, bool threadgroup)
{
__u64 cg_id = cgroup_id(dst_cgrp);
struct percpu_attach_counter *pcpu_counter = bpf_map_lookup_elem(
diff --git a/tools/testing/selftests/bpf/progs/cgroup_iter_memcg.c b/tools/testing/selftests/bpf/progs/cgroup_iter_memcg.c
new file mode 100644
index 000000000000..06a385c9d85b
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/cgroup_iter_memcg.c
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
+#include <vmlinux.h>
+#include <bpf/bpf_core_read.h>
+#include "cgroup_iter_memcg.h"
+
+char _license[] SEC("license") = "GPL";
+
+/* The latest values read are stored here. */
+struct memcg_query memcg_query SEC(".data.query");
+
+SEC("iter.s/cgroup")
+int cgroup_memcg_query(struct bpf_iter__cgroup *ctx)
+{
+ struct cgroup *cgrp = ctx->cgroup;
+ struct cgroup_subsys_state *css;
+ struct mem_cgroup *memcg;
+
+ if (!cgrp)
+ return 1;
+
+ css = &cgrp->self;
+ memcg = bpf_get_mem_cgroup(css);
+ if (!memcg)
+ return 1;
+
+ bpf_mem_cgroup_flush_stats(memcg);
+
+ memcg_query.nr_anon_mapped = bpf_mem_cgroup_page_state(
+ memcg,
+ bpf_core_enum_value(enum node_stat_item, NR_ANON_MAPPED));
+ memcg_query.nr_shmem = bpf_mem_cgroup_page_state(
+ memcg, bpf_core_enum_value(enum node_stat_item, NR_SHMEM));
+ memcg_query.nr_file_pages = bpf_mem_cgroup_page_state(
+ memcg, bpf_core_enum_value(enum node_stat_item, NR_FILE_PAGES));
+ memcg_query.nr_file_mapped = bpf_mem_cgroup_page_state(
+ memcg,
+ bpf_core_enum_value(enum node_stat_item, NR_FILE_MAPPED));
+ memcg_query.pgfault = bpf_mem_cgroup_vm_events(
+ memcg, bpf_core_enum_value(enum vm_event_item, PGFAULT));
+
+ bpf_put_mem_cgroup(memcg);
+
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/cgroup_storage.c b/tools/testing/selftests/bpf/progs/cgroup_storage.c
index db1e4d2d3281..59da1d95e5b9 100644
--- a/tools/testing/selftests/bpf/progs/cgroup_storage.c
+++ b/tools/testing/selftests/bpf/progs/cgroup_storage.c
@@ -21,4 +21,47 @@ int bpf_prog(struct __sk_buff *skb)
return (*counter & 1);
}
+/* Maps for OOB test */
+struct {
+ __uint(type, BPF_MAP_TYPE_CGROUP_STORAGE);
+ __type(key, struct bpf_cgroup_storage_key);
+ __type(value, __u32); /* 4-byte value - not 8-byte aligned */
+} cgroup_storage_oob SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_LRU_PERCPU_HASH);
+ __uint(max_entries, 1);
+ __type(key, __u32);
+ __type(value, __u32); /* 4-byte value - same as cgroup storage */
+} lru_map SEC(".maps");
+
+SEC("cgroup/sock_create")
+int trigger_oob(struct bpf_sock *sk)
+{
+ __u32 key = 0;
+ __u32 *cgroup_val;
+ __u32 value = 0x12345678;
+
+ /* Get cgroup storage value */
+ cgroup_val = bpf_get_local_storage(&cgroup_storage_oob, 0);
+ if (!cgroup_val)
+ return 0;
+
+ /* Initialize cgroup storage */
+ *cgroup_val = value;
+
+ /* This triggers the OOB read:
+ * bpf_map_update_elem() -> htab_map_update_elem() ->
+ * pcpu_init_value() -> copy_map_value_long() ->
+ * bpf_obj_memcpy(..., long_memcpy=true) ->
+ * bpf_long_memcpy(dst, src, round_up(4, 8))
+ *
+ * The copy size is rounded up to 8 bytes, but cgroup_val
+ * points to a 4-byte buffer, causing a 4-byte OOB read.
+ */
+ bpf_map_update_elem(&lru_map, &key, cgroup_val, BPF_ANY);
+
+ return 1;
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/cgrp_kfunc_failure.c b/tools/testing/selftests/bpf/progs/cgrp_kfunc_failure.c
index 9fe9c4a4e8f6..efe7bcae70f8 100644
--- a/tools/testing/selftests/bpf/progs/cgrp_kfunc_failure.c
+++ b/tools/testing/selftests/bpf/progs/cgrp_kfunc_failure.c
@@ -29,7 +29,7 @@ static struct __cgrps_kfunc_map_value *insert_lookup_cgrp(struct cgroup *cgrp)
}
SEC("tp_btf/cgroup_mkdir")
-__failure __msg("Possibly NULL pointer passed to trusted arg0")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
int BPF_PROG(cgrp_kfunc_acquire_untrusted, struct cgroup *cgrp, const char *path)
{
struct cgroup *acquired;
@@ -48,7 +48,7 @@ int BPF_PROG(cgrp_kfunc_acquire_untrusted, struct cgroup *cgrp, const char *path
}
SEC("tp_btf/cgroup_mkdir")
-__failure __msg("Possibly NULL pointer passed to trusted arg0")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
int BPF_PROG(cgrp_kfunc_acquire_no_null_check, struct cgroup *cgrp, const char *path)
{
struct cgroup *acquired;
@@ -64,7 +64,7 @@ int BPF_PROG(cgrp_kfunc_acquire_no_null_check, struct cgroup *cgrp, const char *
}
SEC("tp_btf/cgroup_mkdir")
-__failure __msg("arg#0 pointer type STRUCT cgroup must point")
+__failure __msg("R1 is fp expected STRUCT cgroup")
int BPF_PROG(cgrp_kfunc_acquire_fp, struct cgroup *cgrp, const char *path)
{
struct cgroup *acquired, *stack_cgrp = (struct cgroup *)&path;
@@ -106,7 +106,7 @@ int BPF_PROG(cgrp_kfunc_acquire_trusted_walked, struct cgroup *cgrp, const char
}
SEC("tp_btf/cgroup_mkdir")
-__failure __msg("Possibly NULL pointer passed to trusted arg0")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
int BPF_PROG(cgrp_kfunc_acquire_null, struct cgroup *cgrp, const char *path)
{
struct cgroup *acquired;
@@ -154,7 +154,7 @@ int BPF_PROG(cgrp_kfunc_xchg_unreleased, struct cgroup *cgrp, const char *path)
}
SEC("tp_btf/cgroup_mkdir")
-__failure __msg("must be referenced or trusted")
+__failure __msg("release kfunc bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1")
int BPF_PROG(cgrp_kfunc_rcu_get_release, struct cgroup *cgrp, const char *path)
{
struct cgroup *kptr;
@@ -175,7 +175,7 @@ int BPF_PROG(cgrp_kfunc_rcu_get_release, struct cgroup *cgrp, const char *path)
}
SEC("tp_btf/cgroup_mkdir")
-__failure __msg("Possibly NULL pointer passed to trusted arg0")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
int BPF_PROG(cgrp_kfunc_release_untrusted, struct cgroup *cgrp, const char *path)
{
struct __cgrps_kfunc_map_value *v;
@@ -191,7 +191,7 @@ int BPF_PROG(cgrp_kfunc_release_untrusted, struct cgroup *cgrp, const char *path
}
SEC("tp_btf/cgroup_mkdir")
-__failure __msg("arg#0 pointer type STRUCT cgroup must point")
+__failure __msg("release kfunc bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1")
int BPF_PROG(cgrp_kfunc_release_fp, struct cgroup *cgrp, const char *path)
{
struct cgroup *acquired = (struct cgroup *)&path;
@@ -203,7 +203,7 @@ int BPF_PROG(cgrp_kfunc_release_fp, struct cgroup *cgrp, const char *path)
}
SEC("tp_btf/cgroup_mkdir")
-__failure __msg("Possibly NULL pointer passed to trusted arg0")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
int BPF_PROG(cgrp_kfunc_release_null, struct cgroup *cgrp, const char *path)
{
struct __cgrps_kfunc_map_value local, *v;
@@ -237,7 +237,7 @@ int BPF_PROG(cgrp_kfunc_release_null, struct cgroup *cgrp, const char *path)
}
SEC("tp_btf/cgroup_mkdir")
-__failure __msg("release kernel function bpf_cgroup_release expects")
+__failure __msg("release kfunc bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1")
int BPF_PROG(cgrp_kfunc_release_unacquired, struct cgroup *cgrp, const char *path)
{
/* Cannot release trusted cgroup pointer which was not acquired. */
diff --git a/tools/testing/selftests/bpf/progs/cgrp_ls_sleepable.c b/tools/testing/selftests/bpf/progs/cgrp_ls_sleepable.c
index a2de95f85648..37bd6b03ba01 100644
--- a/tools/testing/selftests/bpf/progs/cgrp_ls_sleepable.c
+++ b/tools/testing/selftests/bpf/progs/cgrp_ls_sleepable.c
@@ -4,6 +4,7 @@
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include "bpf_misc.h"
+#include "err.h"
char _license[] SEC("license") = "GPL";
@@ -16,6 +17,7 @@ struct {
__s32 target_pid;
__u64 cgroup_id;
+long update_err;
int target_hid;
bool is_cgroup1;
@@ -123,3 +125,19 @@ int yes_rcu_lock(void *ctx)
bpf_rcu_read_unlock();
return 0;
}
+
+SEC("fexit/bpf_local_storage_update")
+int BPF_PROG(fexit_update, void *owner, struct bpf_local_storage_map *smap,
+ void *value, u64 map_flags, bool swap_uptrs,
+ struct bpf_local_storage_data *ret)
+{
+ struct task_struct *task = bpf_get_current_task_btf();
+
+ if (task->pid != target_pid)
+ return 0;
+
+ if (IS_ERR_VALUE(ret))
+ update_err = PTR_ERR(ret);
+
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/clone_attach_btf_id.c b/tools/testing/selftests/bpf/progs/clone_attach_btf_id.c
new file mode 100644
index 000000000000..0ffa3ec3e1a0
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/clone_attach_btf_id.c
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Meta */
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+SEC("fentry/bpf_fentry_test1")
+int BPF_PROG(fentry_handler, int a)
+{
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/compute_live_registers.c b/tools/testing/selftests/bpf/progs/compute_live_registers.c
index 6884ab99a421..d055fc7b3b95 100644
--- a/tools/testing/selftests/bpf/progs/compute_live_registers.c
+++ b/tools/testing/selftests/bpf/progs/compute_live_registers.c
@@ -3,7 +3,7 @@
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include "../../../include/linux/filter.h"
-#include "bpf_arena_common.h"
+#include <bpf_arena_common.h>
#include "bpf_misc.h"
struct {
@@ -431,6 +431,47 @@ __naked void subprog1(void)
::: __clobber_all);
}
+#if defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)
+
+SEC("socket")
+__log_level(2)
+__msg("2: .1........ (07) r1 += 8")
+__msg("3: .1........ (79) r2 = *(u64 *)(r1 +0)")
+__msg("4: ..2....... (b7) r3 = 1")
+__msg("5: ..23...... (b7) r4 = 2")
+__msg("6: ..234..... (0d) gotox r2")
+__msg("7: ...3...... (bf) r0 = r3")
+__msg("8: 0......... (95) exit")
+__msg("9: ....4..... (bf) r0 = r4")
+__msg("10: 0......... (95) exit")
+__naked
+void gotox(void)
+{
+ asm volatile (
+ ".pushsection .jumptables,\"\",@progbits;"
+"jt0_%=: .quad l0_%= - socket;"
+ ".quad l1_%= - socket;"
+ ".size jt0_%=, 16;"
+ ".global jt0_%=;"
+ ".popsection;"
+
+ "r1 = jt0_%= ll;"
+ "r1 += 8;"
+ "r2 = *(u64 *)(r1 + 0);"
+ "r3 = 1;"
+ "r4 = 2;"
+ ".8byte %[gotox_r2];"
+"l0_%=: r0 = r3;"
+ "exit;"
+"l1_%=: r0 = r4;"
+ "exit;"
+ :
+ : __imm_insn(gotox_r2, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_2, BPF_REG_0, 0, 0))
+ : __clobber_all);
+}
+
+#endif /* __TARGET_ARCH_x86 || __TARGET_ARCH_arm64 */
+
/* to retain debug info for BTF generation */
void kfunc_root(void)
{
diff --git a/tools/testing/selftests/bpf/progs/connect_force_port4.c b/tools/testing/selftests/bpf/progs/connect_force_port4.c
index 27a632dd382e..d5be6a559d6a 100644
--- a/tools/testing/selftests/bpf/progs/connect_force_port4.c
+++ b/tools/testing/selftests/bpf/progs/connect_force_port4.c
@@ -14,6 +14,8 @@
char _license[] SEC("license") = "GPL";
+__u16 port = 0;
+
struct svc_addr {
__be32 addr;
__be16 port;
@@ -40,7 +42,7 @@ int connect4(struct bpf_sock_addr *ctx)
if (bpf_bind(ctx, (struct sockaddr *)&sa, sizeof(sa)) != 0)
return 0;
- /* Rewire service 1.2.3.4:60000 to backend 127.0.0.1:60123. */
+ /* Rewire service 1.2.3.4:60000 to backend 127.0.0.1:port. */
if (ctx->user_port == bpf_htons(60000)) {
orig = bpf_sk_storage_get(&service_mapping, ctx->sk, 0,
BPF_SK_STORAGE_GET_F_CREATE);
@@ -51,7 +53,7 @@ int connect4(struct bpf_sock_addr *ctx)
orig->port = ctx->user_port;
ctx->user_ip4 = bpf_htonl(0x7f000001);
- ctx->user_port = bpf_htons(60123);
+ ctx->user_port = bpf_htons(port);
}
return 1;
}
@@ -63,7 +65,7 @@ int getsockname4(struct bpf_sock_addr *ctx)
return 1;
/* Expose local server as 1.2.3.4:60000 to client. */
- if (ctx->user_port == bpf_htons(60123)) {
+ if (ctx->user_port == bpf_htons(port)) {
ctx->user_ip4 = bpf_htonl(0x01020304);
ctx->user_port = bpf_htons(60000);
}
@@ -79,7 +81,7 @@ int getpeername4(struct bpf_sock_addr *ctx)
return 1;
/* Expose service 1.2.3.4:60000 as peer instead of backend. */
- if (ctx->user_port == bpf_htons(60123)) {
+ if (ctx->user_port == bpf_htons(port)) {
orig = bpf_sk_storage_get(&service_mapping, ctx->sk, 0, 0);
if (orig) {
ctx->user_ip4 = orig->addr;
diff --git a/tools/testing/selftests/bpf/progs/connect_force_port6.c b/tools/testing/selftests/bpf/progs/connect_force_port6.c
index 19cad93e612f..a1a671b39083 100644
--- a/tools/testing/selftests/bpf/progs/connect_force_port6.c
+++ b/tools/testing/selftests/bpf/progs/connect_force_port6.c
@@ -13,6 +13,8 @@
char _license[] SEC("license") = "GPL";
+__u16 port = 0;
+
struct svc_addr {
__be32 addr[4];
__be16 port;
@@ -39,7 +41,7 @@ int connect6(struct bpf_sock_addr *ctx)
if (bpf_bind(ctx, (struct sockaddr *)&sa, sizeof(sa)) != 0)
return 0;
- /* Rewire service [fc00::1]:60000 to backend [::1]:60124. */
+ /* Rewire service [fc00::1]:60000 to backend [::1]:port. */
if (ctx->user_port == bpf_htons(60000)) {
orig = bpf_sk_storage_get(&service_mapping, ctx->sk, 0,
BPF_SK_STORAGE_GET_F_CREATE);
@@ -56,7 +58,7 @@ int connect6(struct bpf_sock_addr *ctx)
ctx->user_ip6[1] = 0;
ctx->user_ip6[2] = 0;
ctx->user_ip6[3] = bpf_htonl(1);
- ctx->user_port = bpf_htons(60124);
+ ctx->user_port = bpf_htons(port);
}
return 1;
}
@@ -68,7 +70,7 @@ int getsockname6(struct bpf_sock_addr *ctx)
return 1;
/* Expose local server as [fc00::1]:60000 to client. */
- if (ctx->user_port == bpf_htons(60124)) {
+ if (ctx->user_port == bpf_htons(port)) {
ctx->user_ip6[0] = bpf_htonl(0xfc000000);
ctx->user_ip6[1] = 0;
ctx->user_ip6[2] = 0;
@@ -87,7 +89,7 @@ int getpeername6(struct bpf_sock_addr *ctx)
return 1;
/* Expose service [fc00::1]:60000 as peer instead of backend. */
- if (ctx->user_port == bpf_htons(60124)) {
+ if (ctx->user_port == bpf_htons(port)) {
orig = bpf_sk_storage_get(&service_mapping, ctx->sk, 0, 0);
if (orig) {
ctx->user_ip6[0] = orig->addr[0];
diff --git a/tools/testing/selftests/bpf/progs/cpumask_common.h b/tools/testing/selftests/bpf/progs/cpumask_common.h
index 86085b79f5ca..8fe01308d210 100644
--- a/tools/testing/selftests/bpf/progs/cpumask_common.h
+++ b/tools/testing/selftests/bpf/progs/cpumask_common.h
@@ -61,7 +61,7 @@ u32 bpf_cpumask_any_distribute(const struct cpumask *src) __ksym __weak;
u32 bpf_cpumask_any_and_distribute(const struct cpumask *src1,
const struct cpumask *src2) __ksym __weak;
u32 bpf_cpumask_weight(const struct cpumask *cpumask) __ksym __weak;
-int bpf_cpumask_populate(struct cpumask *cpumask, void *src, size_t src__sz) __ksym __weak;
+int bpf_cpumask_populate(struct bpf_cpumask *cpumask, void *src, size_t src__sz) __ksym __weak;
void bpf_rcu_read_lock(void) __ksym __weak;
void bpf_rcu_read_unlock(void) __ksym __weak;
diff --git a/tools/testing/selftests/bpf/progs/cpumask_failure.c b/tools/testing/selftests/bpf/progs/cpumask_failure.c
index 8a2fd596c8a3..4628feb53d86 100644
--- a/tools/testing/selftests/bpf/progs/cpumask_failure.c
+++ b/tools/testing/selftests/bpf/progs/cpumask_failure.c
@@ -45,7 +45,7 @@ int BPF_PROG(test_alloc_no_release, struct task_struct *task, u64 clone_flags)
}
SEC("tp_btf/task_newtask")
-__failure __msg("NULL pointer passed to trusted arg0")
+__failure __msg("NULL pointer passed to trusted R1")
int BPF_PROG(test_alloc_double_release, struct task_struct *task, u64 clone_flags)
{
struct bpf_cpumask *cpumask;
@@ -73,7 +73,7 @@ int BPF_PROG(test_acquire_wrong_cpumask, struct task_struct *task, u64 clone_fla
}
SEC("tp_btf/task_newtask")
-__failure __msg("bpf_cpumask_set_cpu args#1 expected pointer to STRUCT bpf_cpumask")
+__failure __msg("bpf_cpumask_set_cpu R2 expected pointer to STRUCT bpf_cpumask")
int BPF_PROG(test_mutate_cpumask, struct task_struct *task, u64 clone_flags)
{
/* Can't set the CPU of a non-struct bpf_cpumask. */
@@ -107,18 +107,18 @@ int BPF_PROG(test_insert_remove_no_release, struct task_struct *task, u64 clone_
}
SEC("tp_btf/task_newtask")
-__failure __msg("NULL pointer passed to trusted arg0")
+__failure __msg("NULL pointer passed to trusted R1")
int BPF_PROG(test_cpumask_null, struct task_struct *task, u64 clone_flags)
{
- /* NULL passed to KF_TRUSTED_ARGS kfunc. */
+ /* NULL passed to kfunc. */
bpf_cpumask_empty(NULL);
return 0;
}
-SEC("tp_btf/task_newtask")
+SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
__failure __msg("R2 must be a rcu pointer")
-int BPF_PROG(test_global_mask_out_of_rcu, struct task_struct *task, u64 clone_flags)
+int BPF_PROG(test_global_mask_out_of_rcu)
{
struct bpf_cpumask *local, *prev;
@@ -133,6 +133,10 @@ int BPF_PROG(test_global_mask_out_of_rcu, struct task_struct *task, u64 clone_fl
return 0;
}
+ /*
+ * Use a sleepable program so explicit RCU is the only source of RCU
+ * protection.
+ */
bpf_rcu_read_lock();
local = global_mask;
if (!local) {
@@ -151,7 +155,7 @@ int BPF_PROG(test_global_mask_out_of_rcu, struct task_struct *task, u64 clone_fl
}
SEC("tp_btf/task_newtask")
-__failure __msg("NULL pointer passed to trusted arg1")
+__failure __msg("NULL pointer passed to trusted R2")
int BPF_PROG(test_global_mask_no_null_check, struct task_struct *task, u64 clone_flags)
{
struct bpf_cpumask *local, *prev;
@@ -179,7 +183,7 @@ int BPF_PROG(test_global_mask_no_null_check, struct task_struct *task, u64 clone
}
SEC("tp_btf/task_newtask")
-__failure __msg("Possibly NULL pointer passed to helper arg2")
+__failure __msg("Possibly NULL pointer passed to helper R2")
int BPF_PROG(test_global_mask_rcu_no_null_check, struct task_struct *task, u64 clone_flags)
{
struct bpf_cpumask *prev, *curr;
@@ -231,7 +235,7 @@ int BPF_PROG(test_populate_invalid_destination, struct task_struct *task, u64 cl
u64 bits;
int ret;
- ret = bpf_cpumask_populate((struct cpumask *)invalid, &bits, sizeof(bits));
+ ret = bpf_cpumask_populate(invalid, &bits, sizeof(bits));
if (!ret)
err = 2;
@@ -252,7 +256,7 @@ int BPF_PROG(test_populate_invalid_source, struct task_struct *task, u64 clone_f
return 0;
}
- ret = bpf_cpumask_populate((struct cpumask *)local, garbage, 8);
+ ret = bpf_cpumask_populate(local, garbage, 8);
if (!ret)
err = 2;
@@ -260,3 +264,22 @@ int BPF_PROG(test_populate_invalid_source, struct task_struct *task, u64 clone_f
return 0;
}
+
+SEC("tp_btf/task_newtask")
+__failure __msg("expected pointer to STRUCT bpf_cpumask but R1 has a pointer to STRUCT cpumask")
+int BPF_PROG(test_populate_borrowed_destination, struct task_struct *task, u64 clone_flags)
+{
+ u64 bits;
+ int ret;
+
+ /*
+ * task->cpus_ptr is a borrowed, read-only struct cpumask *, not an
+ * owned struct bpf_cpumask *. The verifier must reject it as a
+ * writable destination for bpf_cpumask_populate().
+ */
+ ret = bpf_cpumask_populate((struct bpf_cpumask *)task->cpus_ptr, &bits, sizeof(bits));
+ if (!ret)
+ err = 2;
+
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/cpumask_success.c b/tools/testing/selftests/bpf/progs/cpumask_success.c
index 0e04c31b91c0..36f77b9732d4 100644
--- a/tools/testing/selftests/bpf/progs/cpumask_success.c
+++ b/tools/testing/selftests/bpf/progs/cpumask_success.c
@@ -785,7 +785,7 @@ int BPF_PROG(test_populate_reject_small_mask, struct task_struct *task, u64 clon
return 0;
/* The kfunc should prevent this operation */
- ret = bpf_cpumask_populate((struct cpumask *)local, &toofewbits, sizeof(toofewbits));
+ ret = bpf_cpumask_populate(local, &toofewbits, sizeof(toofewbits));
if (ret != -EACCES)
err = 2;
@@ -824,7 +824,7 @@ int BPF_PROG(test_populate_reject_unaligned, struct task_struct *task, u64 clone
/* Misalign the source array by a byte. */
src = &((char *)bits)[1];
- ret = bpf_cpumask_populate((struct cpumask *)mask, src, CPUMASK_TEST_MASKLEN);
+ ret = bpf_cpumask_populate(mask, src, CPUMASK_TEST_MASKLEN);
if (ret != -EINVAL)
err = 2;
@@ -855,7 +855,7 @@ int BPF_PROG(test_populate, struct task_struct *task, u64 clone_flags)
}
/* Pass the entire bits array, the kfunc will only copy the valid bits. */
- ret = bpf_cpumask_populate((struct cpumask *)mask, bits, CPUMASK_TEST_MASKLEN);
+ ret = bpf_cpumask_populate(mask, bits, CPUMASK_TEST_MASKLEN);
if (ret) {
err = 2;
goto out;
@@ -866,7 +866,7 @@ int BPF_PROG(test_populate, struct task_struct *task, u64 clone_flags)
* access NR_CPUS, the upper bound for nr_cpus, so we infer
* it from the size of cpumask_t.
*/
- if (nr_cpus < 0 || nr_cpus >= CPUMASK_TEST_MASKLEN * 8) {
+ if (nr_cpus < 0 || nr_cpus > CPUMASK_TEST_MASKLEN * 8) {
err = 3;
goto out;
}
diff --git a/tools/testing/selftests/bpf/progs/crypto_bench.c b/tools/testing/selftests/bpf/progs/crypto_bench.c
index 4ac956b26240..4c0a09aa1e6c 100644
--- a/tools/testing/selftests/bpf/progs/crypto_bench.c
+++ b/tools/testing/selftests/bpf/progs/crypto_bench.c
@@ -11,10 +11,19 @@
#include "crypto_common.h"
const volatile unsigned int len = 16;
-char cipher[128] = {};
+/*
+ * cipher[] and key[] are 8-byte aligned and 'params' is kept off the stack to
+ * work around an LLVM code generation bug. clang lowers the memcpy() of these
+ * byte-aligned globals into a per-byte load/store sequence staged on the stack,
+ * and additionally materializes the on-stack 'struct bpf_crypto_params' twice.
+ * Both blow the 512-byte BPF stack limit. Aligning the sources lets clang copy
+ * word-wise, and a global 'params' removes the large object from the stack.
+ */
+char cipher[128] __attribute__((aligned(8))) = {};
u32 key_len, authsize;
char dst[256] = {};
-u8 key[256] = {};
+u8 key[256] __attribute__((aligned(8))) = {};
+static struct bpf_crypto_params params;
long hits = 0;
int status;
@@ -22,11 +31,6 @@ SEC("syscall")
int crypto_setup(void *args)
{
struct bpf_crypto_ctx *cctx;
- struct bpf_crypto_params params = {
- .type = "skcipher",
- .key_len = key_len,
- .authsize = authsize,
- };
int err = 0;
status = 0;
@@ -36,6 +40,9 @@ int crypto_setup(void *args)
return 0;
}
+ __builtin_memcpy(&params.type, "skcipher", sizeof("skcipher"));
+ params.key_len = key_len;
+ params.authsize = authsize;
__builtin_memcpy(&params.algo, cipher, sizeof(cipher));
__builtin_memcpy(&params.key, key, sizeof(key));
cctx = bpf_crypto_ctx_create(&params, sizeof(params), &err);
diff --git a/tools/testing/selftests/bpf/progs/crypto_sanity.c b/tools/testing/selftests/bpf/progs/crypto_sanity.c
index dfd8a258f14a..e81f5ac3b1ae 100644
--- a/tools/testing/selftests/bpf/progs/crypto_sanity.c
+++ b/tools/testing/selftests/bpf/progs/crypto_sanity.c
@@ -10,11 +10,20 @@
#include "bpf_kfuncs.h"
#include "crypto_common.h"
-unsigned char key[256] = {};
+/*
+ * key[] and algo[] are 8-byte aligned and 'params' is kept off the stack to
+ * work around an LLVM code generation bug. clang lowers the memcpy() of these
+ * byte-aligned globals into a per-byte load/store sequence staged on the stack,
+ * and additionally materializes the on-stack 'struct bpf_crypto_params' twice.
+ * Both blow the 512-byte BPF stack limit. Aligning the sources lets clang copy
+ * word-wise, and a global 'params' removes the large object from the stack.
+ */
+unsigned char key[256] __attribute__((aligned(8))) = {};
u16 udp_test_port = 7777;
u32 authsize, key_len;
-char algo[128] = {};
+char algo[128] __attribute__((aligned(8))) = {};
char dst[16] = {}, dst_bad[8] = {};
+static struct bpf_crypto_params params;
int status;
static int skb_dynptr_validate(struct __sk_buff *skb, struct bpf_dynptr *psrc)
@@ -53,11 +62,6 @@ static int skb_dynptr_validate(struct __sk_buff *skb, struct bpf_dynptr *psrc)
SEC("syscall")
int skb_crypto_setup(void *ctx)
{
- struct bpf_crypto_params params = {
- .type = "skcipher",
- .key_len = key_len,
- .authsize = authsize,
- };
struct bpf_crypto_ctx *cctx;
int err;
@@ -67,6 +71,9 @@ int skb_crypto_setup(void *ctx)
return 0;
}
+ __builtin_memcpy(&params.type, "skcipher", sizeof("skcipher"));
+ params.key_len = key_len;
+ params.authsize = authsize;
__builtin_memcpy(&params.algo, algo, sizeof(algo));
__builtin_memcpy(&params.key, key, sizeof(key));
diff --git a/tools/testing/selftests/bpf/progs/dmabuf_iter.c b/tools/testing/selftests/bpf/progs/dmabuf_iter.c
index 13cdb11fdeb2..9cbb7442646e 100644
--- a/tools/testing/selftests/bpf/progs/dmabuf_iter.c
+++ b/tools/testing/selftests/bpf/progs/dmabuf_iter.c
@@ -48,7 +48,7 @@ int dmabuf_collector(struct bpf_iter__dmabuf *ctx)
/* Buffers are not required to be named */
if (pname) {
- if (bpf_probe_read_kernel(name, sizeof(name), pname))
+ if (bpf_probe_read_kernel_str(name, sizeof(name), pname) < 0)
return 1;
/* Name strings can be provided by userspace */
diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c
index dda6a8dada82..1cd61d72c166 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c
@@ -78,7 +78,7 @@ static int get_map_val_dynptr(struct bpf_dynptr *ptr)
* bpf_ringbuf_submit/discard_dynptr call
*/
SEC("?raw_tp")
-__failure __msg("Unreleased reference id=2")
+__failure __msg("Unreleased reference id=1")
int ringbuf_missing_release1(void *ctx)
{
struct bpf_dynptr ptr = {};
@@ -91,7 +91,7 @@ int ringbuf_missing_release1(void *ctx)
}
SEC("?raw_tp")
-__failure __msg("Unreleased reference id=4")
+__failure __msg("Unreleased reference id=3")
int ringbuf_missing_release2(void *ctx)
{
struct bpf_dynptr ptr1, ptr2;
@@ -136,7 +136,7 @@ int ringbuf_missing_release_callback(void *ctx)
/* Can't call bpf_ringbuf_submit/discard_dynptr on a non-initialized dynptr */
SEC("?raw_tp")
-__failure __msg("arg 1 is an unacquired reference")
+__failure __msg("Expected an initialized dynptr as R1")
int ringbuf_release_uninit_dynptr(void *ctx)
{
struct bpf_dynptr ptr;
@@ -149,7 +149,7 @@ int ringbuf_release_uninit_dynptr(void *ctx)
/* A dynptr can't be used after it has been invalidated */
SEC("?raw_tp")
-__failure __msg("Expected an initialized dynptr as arg #2")
+__failure __msg("Expected an initialized dynptr as R3")
int use_after_invalid(void *ctx)
{
struct bpf_dynptr ptr;
@@ -448,7 +448,7 @@ int invalid_helper2(void *ctx)
/* A bpf_dynptr is invalidated if it's been written into */
SEC("?raw_tp")
-__failure __msg("Expected an initialized dynptr as arg #0")
+__failure __msg("Expected an initialized dynptr as R1")
int invalid_write1(void *ctx)
{
struct bpf_dynptr ptr;
@@ -650,7 +650,7 @@ int invalid_offset(void *ctx)
/* Can't release a dynptr twice */
SEC("?raw_tp")
-__failure __msg("arg 1 is an unacquired reference")
+__failure __msg("Expected an initialized dynptr as R1")
int release_twice(void *ctx)
{
struct bpf_dynptr ptr;
@@ -677,7 +677,7 @@ static int release_twice_callback_fn(__u32 index, void *data)
* within a callback function, fails
*/
SEC("?raw_tp")
-__failure __msg("arg 1 is an unacquired reference")
+__failure __msg("Expected an initialized dynptr as R1")
int release_twice_callback(void *ctx)
{
struct bpf_dynptr ptr;
@@ -705,6 +705,48 @@ int dynptr_from_mem_invalid_api(void *ctx)
return 0;
}
+/* Cannot create dynptr from dynptr data */
+SEC("?raw_tp")
+__failure __msg("Unsupported reg type mem for bpf_dynptr_from_mem data")
+int dynptr_from_dynptr_data(void *ctx)
+{
+ struct bpf_dynptr ptr, ptr2;
+ __u8 *data;
+
+ if (get_map_val_dynptr(&ptr))
+ return 0;
+
+ data = bpf_dynptr_data(&ptr, 0, sizeof(__u32));
+ if (!data)
+ return 0;
+
+ /* this should fail */
+ bpf_dynptr_from_mem(data, sizeof(__u32), 0, &ptr2);
+
+ return 0;
+}
+
+/* Cannot create dynptr from dynptr slice */
+SEC("?tc")
+__failure __msg("Unsupported reg type mem for bpf_dynptr_from_mem data")
+int dynptr_from_dynptr_slice(struct __sk_buff *skb)
+{
+ struct bpf_dynptr ptr, ptr2;
+ struct ethhdr *hdr;
+ char buffer[sizeof(*hdr)] = {};
+
+ bpf_dynptr_from_skb(skb, 0, &ptr);
+
+ hdr = bpf_dynptr_slice_rdwr(&ptr, 0, buffer, sizeof(buffer));
+ if (!hdr)
+ return SK_DROP;
+
+ /* this should fail */
+ bpf_dynptr_from_mem(hdr, sizeof(*hdr), 0, &ptr2);
+
+ return SK_PASS;
+}
+
SEC("?tc")
__failure __msg("cannot overwrite referenced dynptr") __log_level(2)
int dynptr_pruning_overwrite(struct __sk_buff *ctx)
@@ -1070,7 +1112,7 @@ int dynptr_overwrite_ref(void *ctx)
/* Reject writes to dynptr slot from bpf_dynptr_read */
SEC("?raw_tp")
-__failure __msg("potential write to dynptr at off=-16")
+__failure __msg("cannot overwrite referenced dynptr")
int dynptr_read_into_slot(void *ctx)
{
union {
@@ -1465,7 +1507,7 @@ int xdp_invalid_data_slice2(struct xdp_md *xdp)
}
/* Only supported prog type can create skb-type dynptrs */
-SEC("?raw_tp")
+SEC("?xdp")
__failure __msg("calling kernel function bpf_dynptr_from_skb is not allowed")
int skb_invalid_ctx(void *ctx)
{
@@ -1516,7 +1558,7 @@ int BPF_PROG(skb_invalid_ctx_fexit, void *skb)
/* Reject writes to dynptr slot for uninit arg */
SEC("?raw_tp")
-__failure __msg("potential write to dynptr at off=-16")
+__failure __msg("cannot overwrite referenced dynptr")
int uninit_write_into_slot(void *ctx)
{
struct {
@@ -1547,7 +1589,8 @@ int xdp_invalid_ctx(void *ctx)
__u32 hdr_size = sizeof(struct ethhdr);
/* Can't pass in variable-sized len to bpf_dynptr_slice */
SEC("?tc")
-__failure __msg("unbounded memory access")
+__failure __msg("must be a known constant")
+__msg("requires this memory size to be a verifier-known constant")
int dynptr_slice_var_len1(struct __sk_buff *skb)
{
struct bpf_dynptr ptr;
@@ -1567,6 +1610,7 @@ int dynptr_slice_var_len1(struct __sk_buff *skb)
/* Can't pass in variable-sized len to bpf_dynptr_slice */
SEC("?tc")
__failure __msg("must be a known constant")
+__msg("requires this memory size to be a verifier-known constant")
int dynptr_slice_var_len2(struct __sk_buff *skb)
{
char buffer[sizeof(struct ethhdr)] = {};
@@ -1593,6 +1637,36 @@ static int callback(__u32 index, void *data)
return 0;
}
+/* A commuted add should preserve the parent id of a dynptr data slice. */
+SEC("?raw_tp")
+__failure __msg("invalid mem access 'scalar'")
+int dynptr_slice_commuted_invalidate(void *ctx)
+{
+ struct bpf_dynptr ptr;
+ __u32 *slice, *derived;
+
+ bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(__u32), 0, &ptr);
+
+ slice = bpf_dynptr_data(&ptr, 0, sizeof(__u32));
+ if (!slice)
+ goto done;
+
+ asm volatile ("%[dst] = 0;"
+ "%[dst] += %[src];"
+ "%[src] = 0;"
+ : [dst]"=&r"(derived), [src]"+r"(slice)
+ :
+ : "memory");
+
+ bpf_ringbuf_discard_dynptr(&ptr, 0);
+ val = *derived;
+ return 0;
+
+done:
+ bpf_ringbuf_discard_dynptr(&ptr, 0);
+ return 0;
+}
+
/* If the dynptr is written into in a callback function, its data
* slices should be invalidated as well.
*/
@@ -1642,7 +1716,7 @@ int invalid_slice_rdwr_rdonly(struct __sk_buff *skb)
/* bpf_dynptr_adjust can only be called on initialized dynptrs */
SEC("?raw_tp")
-__failure __msg("Expected an initialized dynptr as arg #0")
+__failure __msg("Expected an initialized dynptr as R1")
int dynptr_adjust_invalid(void *ctx)
{
struct bpf_dynptr ptr = {};
@@ -1655,7 +1729,7 @@ int dynptr_adjust_invalid(void *ctx)
/* bpf_dynptr_is_null can only be called on initialized dynptrs */
SEC("?raw_tp")
-__failure __msg("Expected an initialized dynptr as arg #0")
+__failure __msg("Expected an initialized dynptr as R1")
int dynptr_is_null_invalid(void *ctx)
{
struct bpf_dynptr ptr = {};
@@ -1668,7 +1742,7 @@ int dynptr_is_null_invalid(void *ctx)
/* bpf_dynptr_is_rdonly can only be called on initialized dynptrs */
SEC("?raw_tp")
-__failure __msg("Expected an initialized dynptr as arg #0")
+__failure __msg("Expected an initialized dynptr as R1")
int dynptr_is_rdonly_invalid(void *ctx)
{
struct bpf_dynptr ptr = {};
@@ -1681,7 +1755,7 @@ int dynptr_is_rdonly_invalid(void *ctx)
/* bpf_dynptr_size can only be called on initialized dynptrs */
SEC("?raw_tp")
-__failure __msg("Expected an initialized dynptr as arg #0")
+__failure __msg("Expected an initialized dynptr as R1")
int dynptr_size_invalid(void *ctx)
{
struct bpf_dynptr ptr = {};
@@ -1694,7 +1768,7 @@ int dynptr_size_invalid(void *ctx)
/* Only initialized dynptrs can be cloned */
SEC("?raw_tp")
-__failure __msg("Expected an initialized dynptr as arg #0")
+__failure __msg("Expected an initialized dynptr as R1")
int clone_invalid1(void *ctx)
{
struct bpf_dynptr ptr1 = {};
@@ -1728,7 +1802,7 @@ int clone_invalid2(struct xdp_md *xdp)
/* Invalidating a dynptr should invalidate its clones */
SEC("?raw_tp")
-__failure __msg("Expected an initialized dynptr as arg #2")
+__failure __msg("Expected an initialized dynptr as R3")
int clone_invalidate1(void *ctx)
{
struct bpf_dynptr clone;
@@ -1749,7 +1823,7 @@ int clone_invalidate1(void *ctx)
/* Invalidating a dynptr should invalidate its parent */
SEC("?raw_tp")
-__failure __msg("Expected an initialized dynptr as arg #2")
+__failure __msg("Expected an initialized dynptr as R3")
int clone_invalidate2(void *ctx)
{
struct bpf_dynptr ptr;
@@ -1770,7 +1844,7 @@ int clone_invalidate2(void *ctx)
/* Invalidating a dynptr should invalidate its siblings */
SEC("?raw_tp")
-__failure __msg("Expected an initialized dynptr as arg #2")
+__failure __msg("Expected an initialized dynptr as R3")
int clone_invalidate3(void *ctx)
{
struct bpf_dynptr ptr;
@@ -1981,7 +2055,7 @@ __noinline long global_call_bpf_dynptr(const struct bpf_dynptr *dynptr)
}
SEC("?raw_tp")
-__failure __msg("arg#0 expected pointer to stack or const struct bpf_dynptr")
+__failure __msg("R1 expected pointer to stack or const struct bpf_dynptr")
int test_dynptr_reg_type(void *ctx)
{
struct task_struct *current = NULL;
@@ -1993,3 +2067,118 @@ int test_dynptr_reg_type(void *ctx)
global_call_bpf_dynptr((const struct bpf_dynptr *)current);
return 0;
}
+
+/* Overwriting a referenced dynptr is allowed if a clone still holds the ref */
+SEC("?raw_tp")
+__success
+int dynptr_overwrite_ref_with_clone(void *ctx)
+{
+ struct bpf_dynptr ptr, clone;
+
+ bpf_ringbuf_reserve_dynptr(&ringbuf, 64, 0, &ptr);
+
+ bpf_dynptr_clone(&ptr, &clone);
+
+ /* Overwrite the original - clone still holds the ref */
+ *(volatile __u8 *)&ptr = 0;
+
+ bpf_ringbuf_discard_dynptr(&clone, 0);
+
+ return 0;
+}
+
+/* Overwriting the last referenced dynptr should still be rejected */
+SEC("?raw_tp")
+__failure __msg("cannot overwrite referenced dynptr")
+int dynptr_overwrite_ref_last_clone(void *ctx)
+{
+ struct bpf_dynptr ptr, clone;
+
+ bpf_ringbuf_reserve_dynptr(&ringbuf, 64, 0, &ptr);
+
+ bpf_dynptr_clone(&ptr, &clone);
+
+ /* Overwrite the original - clone still holds the ref, OK */
+ *(volatile __u8 *)&ptr = 0;
+
+ /* Overwrite the last holder - this should fail */
+ *(volatile __u8 *)&clone = 0;
+
+ return 0;
+}
+
+/* Overwriting a clone should be allowed if the original still holds the ref */
+SEC("?raw_tp")
+__success
+int dynptr_overwrite_clone_with_original(void *ctx)
+{
+ struct bpf_dynptr ptr, clone;
+
+ bpf_ringbuf_reserve_dynptr(&ringbuf, 64, 0, &ptr);
+
+ bpf_dynptr_clone(&ptr, &clone);
+
+ /* Overwrite the clone - original still holds the ref */
+ *(volatile __u8 *)&clone = 0;
+
+ bpf_ringbuf_discard_dynptr(&ptr, 0);
+
+ return 0;
+}
+
+/* Data slices from the destroyed dynptr should be invalidated */
+SEC("?raw_tp")
+__failure __msg("invalid mem access 'scalar'")
+int dynptr_overwrite_ref_invalidate_slice(void *ctx)
+{
+ struct bpf_dynptr ptr, clone;
+ int *data;
+
+ bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr);
+
+ data = bpf_dynptr_data(&ptr, 0, sizeof(val));
+ if (!data)
+ return 0;
+
+ bpf_dynptr_clone(&ptr, &clone);
+
+ /* Overwrite the original - clone holds the ref */
+ *(volatile __u8 *)&ptr = 0;
+
+ /* data was from the original dynptr, should be invalid now */
+ *data = 123;
+
+ return 0;
+}
+
+/*
+ * Data slices from a dynptr clone should remain valid after
+ * overwriting the original dynptr
+ */
+SEC("?raw_tp")
+__success
+int dynptr_overwrite_ref_clone_slice_valid(void *ctx)
+{
+ struct bpf_dynptr ptr, clone;
+ int *data;
+
+ bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr);
+
+ bpf_dynptr_clone(&ptr, &clone);
+
+ data = bpf_dynptr_data(&clone, 0, sizeof(val));
+ if (!data) {
+ bpf_ringbuf_discard_dynptr(&clone, 0);
+ return 0;
+ }
+
+ /* Overwrite the original - clone holds the ref */
+ *(volatile __u8 *)&ptr = 0;
+
+ /* data is from the clone, should still be valid */
+ *data = 123;
+
+ bpf_ringbuf_discard_dynptr(&clone, 0);
+
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/dynptr_success.c b/tools/testing/selftests/bpf/progs/dynptr_success.c
index e0d672d93adf..b668ebd61fc7 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_success.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_success.c
@@ -10,6 +10,7 @@
#include "errno.h"
#define PAGE_SIZE_64K 65536
+#define TEST_SKB_LINEAR_SIZE (sizeof(struct ethhdr) + sizeof(struct iphdr))
char _license[] SEC("license") = "GPL";
@@ -212,6 +213,25 @@ int test_dynptr_skb_data(struct __sk_buff *skb)
}
SEC("?tc")
+int test_dynptr_skb_slice_non_linear(struct __sk_buff *skb)
+{
+ struct bpf_dynptr ptr;
+ void *data;
+
+ if (bpf_dynptr_from_skb(skb, 0, &ptr)) {
+ err = 1;
+ return 1;
+ }
+
+ /* Ensure we cannot read past the end of the buffer. */
+ data = bpf_dynptr_slice(&ptr, TEST_SKB_LINEAR_SIZE + 1, NULL, 1);
+ if (data)
+ err = 2;
+
+ return 1;
+}
+
+SEC("?tc")
int test_dynptr_skb_meta_data(struct __sk_buff *skb)
{
struct bpf_dynptr meta;
@@ -914,7 +934,7 @@ void *user_ptr;
char expected_str[384];
__u32 test_len[7] = {0/* placeholder */, 0, 1, 2, 255, 256, 257};
-typedef int (*bpf_read_dynptr_fn_t)(struct bpf_dynptr *dptr, u64 off,
+typedef int (*bpf_read_dynptr_fn_t)(const struct bpf_dynptr *dptr, u64 off,
u64 size, const void *unsafe_ptr);
/* Returns the offset just before the end of the maximum sized xdp fragment.
@@ -1106,7 +1126,7 @@ int test_copy_from_user_str_dynptr(void *ctx)
return 0;
}
-static int bpf_copy_data_from_user_task(struct bpf_dynptr *dptr, u64 off,
+static int bpf_copy_data_from_user_task(const struct bpf_dynptr *dptr, u64 off,
u64 size, const void *unsafe_ptr)
{
struct task_struct *task = bpf_get_current_task_btf();
@@ -1114,7 +1134,7 @@ static int bpf_copy_data_from_user_task(struct bpf_dynptr *dptr, u64 off,
return bpf_copy_from_user_task_dynptr(dptr, off, size, unsafe_ptr, task);
}
-static int bpf_copy_data_from_user_task_str(struct bpf_dynptr *dptr, u64 off,
+static int bpf_copy_data_from_user_task_str(const struct bpf_dynptr *dptr, u64 off,
u64 size, const void *unsafe_ptr)
{
struct task_struct *task = bpf_get_current_task_btf();
diff --git a/tools/testing/selftests/bpf/progs/empty_skb.c b/tools/testing/selftests/bpf/progs/empty_skb.c
index 4b0cd6753251..44326f5cc8bb 100644
--- a/tools/testing/selftests/bpf/progs/empty_skb.c
+++ b/tools/testing/selftests/bpf/progs/empty_skb.c
@@ -35,3 +35,10 @@ int tc_redirect_egress(struct __sk_buff *skb)
ret = bpf_clone_redirect(skb, ifindex, 0);
return 0;
}
+
+SEC("tc")
+int tc_adjust_room(struct __sk_buff *skb)
+{
+ ret = bpf_skb_adjust_room(skb, 4, BPF_ADJ_ROOM_NET, 0);
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/exceptions.c b/tools/testing/selftests/bpf/progs/exceptions.c
index f09cd14d8e04..91c81971e58c 100644
--- a/tools/testing/selftests/bpf/progs/exceptions.c
+++ b/tools/testing/selftests/bpf/progs/exceptions.c
@@ -109,6 +109,20 @@ int exception_tail_call(struct __sk_buff *ctx) {
return ret + 8;
}
+__weak
+void throw_11(void)
+{
+ bpf_throw(11);
+}
+
+SEC("tc")
+int exception_throw_from_void_global(struct __sk_buff *ctx)
+{
+ throw_11();
+
+ return 0;
+}
+
__noinline int exception_ext_global(struct __sk_buff *ctx)
{
volatile int ret = 0;
@@ -198,6 +212,36 @@ int exception_throw_subprog(struct __sk_buff *ctx)
return 0;
}
+u64 exception_cb_stack_src = 0x1234;
+
+/*
+ * The address handed to the helper has to be this callback's own stack
+ * slot, not one from a frame that is already gone.
+ */
+__noinline int exception_cb_stack(u64 cookie)
+{
+ volatile u64 val = 0xdead;
+
+ bpf_probe_read_kernel((void *)&val, sizeof(val), &exception_cb_stack_src);
+ return val;
+}
+
+/* Throws from a subprogram that has a stack of its own. */
+__noinline static int throwing_subprog_stack(struct __sk_buff *ctx)
+{
+ volatile u64 pad[4] = {};
+
+ bpf_throw(pad[0]);
+ return 0;
+}
+
+SEC("tc")
+__exception_cb(exception_cb_stack)
+int exception_throw_subprog_stack_cb(struct __sk_buff *ctx)
+{
+ return throwing_subprog_stack(ctx);
+}
+
__noinline int assert_nz_gfunc(u64 c)
{
volatile u64 cookie = c;
@@ -365,4 +409,118 @@ int exception_bad_assert_range_with(struct __sk_buff *ctx)
return 1;
}
+#if (defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) \
+ && defined(__BPF_FEATURE_STACK_ARGUMENT)
+
+const volatile bool has_stack_arg = true;
+
+long arg1 = 1, arg2 = 2, arg3 = 3, arg4 = 4, arg5 = 5;
+long arg6 = 6, arg7 = 7, arg8 = 8, arg9 = 9, arg10 = 10;
+
+__noinline static long throwing_many_args(long a, long b, long c, long d,
+ long e, long f, long g, long h,
+ long i, long j)
+{
+ bpf_throw(a + b + c + d + e + f + g + h + i + j);
+ return 0;
+}
+
+__noinline int exception_cb_sa(u64 cookie)
+{
+ return cookie + 1;
+}
+
+SEC("tc")
+__exception_cb(exception_cb_sa)
+int exception_throw_stack_arg(struct __sk_buff *ctx)
+{
+ throwing_many_args(arg1, arg2, arg3, arg4, arg5,
+ arg6, arg7, arg8, arg9, arg10);
+ return 0;
+}
+
+__noinline static long no_throw_many_args(long a, long b, long c, long d,
+ long e, long f, long g, long h,
+ long i, long j)
+{
+ return a + b + c + d + e + f + g + h + i + j;
+}
+
+SEC("tc")
+__exception_cb(exception_cb_sa)
+int exception_throw_after_stack_arg(struct __sk_buff *ctx)
+{
+ long ret;
+
+ ret = no_throw_many_args(arg1, arg2, arg3, arg4, arg5,
+ arg6, arg7, arg8, arg9, arg10);
+ if (ret > 0)
+ bpf_throw(ret);
+ return 0;
+}
+
+__noinline static long subprog_throw_sa(long val)
+{
+ throwing_many_args(val, val + 1, val + 2, val + 3, val + 4,
+ val + 5, val + 6, val + 7, val + 8, val + 9);
+ return 0;
+}
+
+SEC("tc")
+__exception_cb(exception_cb_sa)
+int exception_throw_subprog_stack_arg(struct __sk_buff *ctx)
+{
+ subprog_throw_sa(arg1);
+ return 0;
+}
+
+__noinline static long subprog_throw_after_sa(long val)
+{
+ long ret;
+
+ ret = no_throw_many_args(val, val + 1, val + 2, val + 3, val + 4,
+ val + 5, val + 6, val + 7, val + 8, val + 9);
+ if (ret > 0)
+ bpf_throw(ret);
+ return 0;
+}
+
+SEC("tc")
+__exception_cb(exception_cb_sa)
+int exception_throw_subprog_after_stack_arg(struct __sk_buff *ctx)
+{
+ subprog_throw_after_sa(arg1);
+ return 0;
+}
+
+#else
+
+const volatile bool has_stack_arg = false;
+
+SEC("tc")
+int exception_throw_stack_arg(struct __sk_buff *ctx)
+{
+ return 0;
+}
+
+SEC("tc")
+int exception_throw_after_stack_arg(struct __sk_buff *ctx)
+{
+ return 0;
+}
+
+SEC("tc")
+int exception_throw_subprog_stack_arg(struct __sk_buff *ctx)
+{
+ return 0;
+}
+
+SEC("tc")
+int exception_throw_subprog_after_stack_arg(struct __sk_buff *ctx)
+{
+ return 0;
+}
+
+#endif
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/exceptions_assert.c b/tools/testing/selftests/bpf/progs/exceptions_assert.c
index a01c2736890f..e4abf4172fca 100644
--- a/tools/testing/selftests/bpf/progs/exceptions_assert.c
+++ b/tools/testing/selftests/bpf/progs/exceptions_assert.c
@@ -18,48 +18,48 @@
return *(u64 *)num; \
}
-__msg(": R0=0xffffffff80000000")
+__msg("R{{.}}=0xffffffff80000000")
check_assert(s64, ==, eq_int_min, INT_MIN);
-__msg(": R0=0x7fffffff")
+__msg("R{{.}}=0x7fffffff")
check_assert(s64, ==, eq_int_max, INT_MAX);
-__msg(": R0=0")
+__msg("R{{.}}=0")
check_assert(s64, ==, eq_zero, 0);
-__msg(": R0=0x8000000000000000 R1=0x8000000000000000")
+__msg("R{{.}}=0x8000000000000000")
check_assert(s64, ==, eq_llong_min, LLONG_MIN);
-__msg(": R0=0x7fffffffffffffff R1=0x7fffffffffffffff")
+__msg("R{{.}}=0x7fffffffffffffff")
check_assert(s64, ==, eq_llong_max, LLONG_MAX);
-__msg(": R0=scalar(id=1,smax=0x7ffffffe)")
+__msg("R{{.}}=scalar(id=1,smax=0x7ffffffe)")
check_assert(s64, <, lt_pos, INT_MAX);
-__msg(": R0=scalar(id=1,smax=-1,umin=0x8000000000000000,var_off=(0x8000000000000000; 0x7fffffffffffffff))")
+__msg("R{{.}}=scalar(id=1,smax=-1,umin=0x8000000000000000,var_off=(0x8000000000000000; 0x7fffffffffffffff))")
check_assert(s64, <, lt_zero, 0);
-__msg(": R0=scalar(id=1,smax=0xffffffff7fffffff")
+__msg("R{{.}}=scalar(id=1,smax=0xffffffff7fffffff")
check_assert(s64, <, lt_neg, INT_MIN);
-__msg(": R0=scalar(id=1,smax=0x7fffffff)")
+__msg("R{{.}}=scalar(id=1,smax=0x7fffffff)")
check_assert(s64, <=, le_pos, INT_MAX);
-__msg(": R0=scalar(id=1,smax=0)")
+__msg("R{{.}}=scalar(id=1,smax=0)")
check_assert(s64, <=, le_zero, 0);
-__msg(": R0=scalar(id=1,smax=0xffffffff80000000")
+__msg("R{{.}}=scalar(id=1,smax=0xffffffff80000000")
check_assert(s64, <=, le_neg, INT_MIN);
-__msg(": R0=scalar(id=1,smin=umin=0x80000000,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))")
+__msg("R{{.}}=scalar(id=1,smin=umin=0x80000000,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))")
check_assert(s64, >, gt_pos, INT_MAX);
-__msg(": R0=scalar(id=1,smin=umin=1,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))")
+__msg("R{{.}}=scalar(id=1,smin=umin=1,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))")
check_assert(s64, >, gt_zero, 0);
-__msg(": R0=scalar(id=1,smin=0xffffffff80000001")
+__msg("R{{.}}=scalar(id=1,smin=0xffffffff80000001")
check_assert(s64, >, gt_neg, INT_MIN);
-__msg(": R0=scalar(id=1,smin=umin=0x7fffffff,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))")
+__msg("R{{.}}=scalar(id=1,smin=umin=0x7fffffff,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))")
check_assert(s64, >=, ge_pos, INT_MAX);
-__msg(": R0=scalar(id=1,smin=0,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))")
+__msg("R{{.}}=scalar(id=1,smin=0,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))")
check_assert(s64, >=, ge_zero, 0);
-__msg(": R0=scalar(id=1,smin=0xffffffff80000000")
+__msg("R{{.}}=scalar(id=1,smin=0xffffffff80000000")
check_assert(s64, >=, ge_neg, INT_MIN);
SEC("?tc")
__log_level(2) __failure
-__msg(": R0=0 R1=ctx() R2=scalar(smin=0xffffffff80000002,smax=smax32=0x7ffffffd,smin32=0x80000002) R10=fp0")
+__msg(": R1=ctx() R2=scalar(smin=0xffffffff80000002,smax=smax32=0x7ffffffd,smin32=0x80000002) R10=fp0")
int check_assert_range_s64(struct __sk_buff *ctx)
{
struct bpf_sock *sk = ctx->sk;
@@ -86,7 +86,7 @@ int check_assert_range_u64(struct __sk_buff *ctx)
SEC("?tc")
__log_level(2) __failure
-__msg(": R0=0 R1=ctx() R2=4096 R10=fp0")
+__msg(": R1=ctx() R2=4096 R10=fp0")
int check_assert_single_range_s64(struct __sk_buff *ctx)
{
struct bpf_sock *sk = ctx->sk;
@@ -114,7 +114,7 @@ int check_assert_single_range_u64(struct __sk_buff *ctx)
SEC("?tc")
__log_level(2) __failure
-__msg(": R1=pkt(off=64,r=64) R2=pkt_end() R6=pkt(r=64) R10=fp0")
+__msg(": R6=pkt(r=64) R10=fp0")
int check_assert_generic(struct __sk_buff *ctx)
{
u8 *data_end = (void *)(long)ctx->data_end;
diff --git a/tools/testing/selftests/bpf/progs/exceptions_fail.c b/tools/testing/selftests/bpf/progs/exceptions_fail.c
index 8a0fdff89927..ac44d60e5066 100644
--- a/tools/testing/selftests/bpf/progs/exceptions_fail.c
+++ b/tools/testing/selftests/bpf/progs/exceptions_fail.c
@@ -8,6 +8,11 @@
#include "bpf_experimental.h"
extern void bpf_rcu_read_lock(void) __ksym;
+extern void bpf_rcu_read_unlock(void) __ksym;
+extern void bpf_preempt_disable(void) __ksym;
+extern void bpf_preempt_enable(void) __ksym;
+extern void bpf_local_irq_save(unsigned long *) __ksym;
+extern void bpf_local_irq_restore(unsigned long *) __ksym;
#define private(name) SEC(".bss." #name) __hidden __attribute__((aligned(8)))
@@ -29,11 +34,15 @@ struct {
private(A) struct bpf_spin_lock lock;
private(A) struct bpf_rb_root rbtree __contains(foo, node);
-__noinline void *exception_cb_bad_ret_type(u64 cookie)
+__noinline void *exception_cb_bad_ret_type1(u64 cookie)
{
return NULL;
}
+__noinline void exception_cb_bad_ret_type2(u64 cookie)
+{
+}
+
__noinline int exception_cb_bad_arg_0(void)
{
return 0;
@@ -50,8 +59,8 @@ __noinline int exception_cb_ok_arg_small(int a)
}
SEC("?tc")
-__exception_cb(exception_cb_bad_ret_type)
-__failure __msg("Global function exception_cb_bad_ret_type() doesn't return scalar.")
+__exception_cb(exception_cb_bad_ret_type1)
+__failure __msg("Global function exception_cb_bad_ret_type1() return value not void or scalar.")
int reject_exception_cb_type_1(struct __sk_buff *ctx)
{
bpf_throw(0);
@@ -85,6 +94,15 @@ int reject_exception_cb_type_4(struct __sk_buff *ctx)
return 0;
}
+SEC("?tc")
+__exception_cb(exception_cb_bad_ret_type2)
+__failure __msg("exception cb cannot return void")
+int reject_exception_cb_type_5(struct __sk_buff *ctx)
+{
+ bpf_throw(0);
+ return 0;
+}
+
__noinline
static int timer_cb(void *map, int *key, struct bpf_timer *timer)
{
@@ -131,7 +149,7 @@ int reject_subprog_with_lock(void *ctx)
}
SEC("?tc")
-__failure __msg("BPF_EXIT instruction in main prog cannot be used inside bpf_rcu_read_lock-ed region")
+__failure __msg("bpf_throw cannot be used inside bpf_rcu_read_lock-ed region")
int reject_with_rcu_read_lock(void *ctx)
{
bpf_rcu_read_lock();
@@ -147,11 +165,13 @@ __noinline static int throwing_subprog(struct __sk_buff *ctx)
}
SEC("?tc")
-__failure __msg("BPF_EXIT instruction in main prog cannot be used inside bpf_rcu_read_lock-ed region")
+__failure __msg("bpf_throw cannot be used inside bpf_rcu_read_lock-ed region")
int reject_subprog_with_rcu_read_lock(void *ctx)
{
bpf_rcu_read_lock();
- return throwing_subprog(ctx);
+ throwing_subprog(ctx);
+ bpf_rcu_read_unlock();
+ return 0;
}
static bool rbless(struct bpf_rb_node *n1, const struct bpf_rb_node *n2)
@@ -188,6 +208,28 @@ int reject_with_reference(void *ctx)
return 0;
}
+__noinline int global_subprog_may_throw(struct __sk_buff *ctx)
+{
+ if (ctx->len)
+ bpf_throw(0);
+ return 0;
+}
+
+SEC("?tc")
+__failure __msg("Unreleased reference")
+int reject_global_subprog_throw_with_reference(struct __sk_buff *ctx)
+{
+ struct foo *f;
+
+ f = bpf_obj_new(typeof(*f));
+ if (!f)
+ return 0;
+ if (ctx->protocol)
+ global_subprog_may_throw(ctx);
+ bpf_obj_drop(f);
+ return 0;
+}
+
__noinline static int subprog_ref(struct __sk_buff *ctx)
{
struct foo *f;
@@ -346,4 +388,62 @@ int reject_exception_throw_cb_diff(struct __sk_buff *ctx)
return 0;
}
+__weak
+void foo(void)
+{
+ bpf_throw(1);
+}
+
+SEC("?fentry/bpf_check")
+__failure __msg("At program exit the register R1 has smin=1 smax=1 should")
+int reject_out_of_range_global_throw(struct __sk_buff *skb)
+{
+ foo();
+
+ return 0;
+}
+
+__noinline static int always_throws(void)
+{
+ bpf_throw(0);
+ return 0;
+}
+
+__noinline static int rcu_lock_then_throw(void)
+{
+ bpf_rcu_read_lock();
+ bpf_throw(0);
+ return 0;
+}
+
+SEC("?tc")
+__failure __msg("bpf_throw cannot be used inside bpf_rcu_read_lock-ed region")
+int reject_subprog_rcu_lock_throw(void *ctx)
+{
+ rcu_lock_then_throw();
+ return 0;
+}
+
+SEC("?tc")
+__failure __msg("bpf_throw cannot be used inside bpf_preempt_disable-ed region")
+int reject_subprog_throw_preempt_lock(void *ctx)
+{
+ bpf_preempt_disable();
+ always_throws();
+ bpf_preempt_enable();
+ return 0;
+}
+
+SEC("?tc")
+__failure __msg("bpf_throw cannot be used inside bpf_local_irq_save-ed region")
+int reject_subprog_throw_irq_lock(void *ctx)
+{
+ unsigned long flags;
+
+ bpf_local_irq_save(&flags);
+ always_throws();
+ bpf_local_irq_restore(&flags);
+ return 0;
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/fentry_sleepable.c b/tools/testing/selftests/bpf/progs/fentry_sleepable.c
new file mode 100644
index 000000000000..8c0fc691d329
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/fentry_sleepable.c
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char LICENSE[] SEC("license") = "GPL";
+
+void *user_ptr;
+int retval;
+
+SEC("fentry.s")
+int BPF_PROG(fentry_xdp)
+{
+ char buff[64];
+
+ retval = bpf_copy_from_user(buff, sizeof(buff), user_ptr);
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/fexit_bpf2bpf.c b/tools/testing/selftests/bpf/progs/fexit_bpf2bpf.c
index 983b7c233382..f4bbf87b82dd 100644
--- a/tools/testing/selftests/bpf/progs/fexit_bpf2bpf.c
+++ b/tools/testing/selftests/bpf/progs/fexit_bpf2bpf.c
@@ -53,14 +53,23 @@ int BPF_PROG(test_subprog1, struct sk_buff *skb, int ret)
* r0 = *(u32 *)(r1 + 0)
* w0 <<= 1
* exit
- * In such case the verifier falls back to conservative and
+ * Before llvm23, in such case the verifier falls back to conservative and
* tracing program can access arguments and return value as u64
- * instead of accurate types.
+ * instead of accurate types. With llvm23, the true signature
+ * int test_pkt_access_subprog2(volatile struct __sk_buff *skb)
+ * is available in btf.
*/
+#if __clang_major__ >= 23
+struct args_subprog2 {
+ __u64 args[1];
+ __u64 ret;
+};
+#else
struct args_subprog2 {
__u64 args[5];
__u64 ret;
};
+#endif
__u64 test_result_subprog2 = 0;
SEC("fexit/test_pkt_access_subprog2")
int test_subprog2(struct args_subprog2 *ctx)
diff --git a/tools/testing/selftests/bpf/progs/fib_lookup.c b/tools/testing/selftests/bpf/progs/fib_lookup.c
index 7b5dd2214ff4..36b7218d9ae2 100644
--- a/tools/testing/selftests/bpf/progs/fib_lookup.c
+++ b/tools/testing/selftests/bpf/progs/fib_lookup.c
@@ -4,7 +4,11 @@
#include <linux/types.h>
#include <linux/bpf.h>
#include <linux/pkt_cls.h>
+#include <linux/if_ether.h>
+#include <linux/ip.h>
+#include <linux/in.h>
#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
struct bpf_fib_lookup fib_params = {};
int fib_lookup_ret = 0;
@@ -19,4 +23,57 @@ int fib_lookup(struct __sk_buff *skb)
return TC_ACT_SHOT;
}
+SEC("xdp")
+int fib_lookup_xdp(struct xdp_md *ctx)
+{
+ fib_lookup_ret = bpf_fib_lookup(ctx, &fib_params, sizeof(fib_params),
+ lookup_flags);
+
+ return XDP_DROP;
+}
+
+int redirected = 0;
+int passed = 0;
+int delivered = 0;
+
+SEC("xdp")
+int fib_lookup_redirect(struct xdp_md *ctx)
+{
+ struct bpf_fib_lookup params = fib_params;
+ long ret;
+
+ ret = bpf_fib_lookup(ctx, &params, sizeof(params), lookup_flags);
+ if (ret == BPF_FIB_LKUP_RET_SUCCESS) {
+ redirected++;
+ return bpf_redirect(params.ifindex, 0);
+ }
+
+ passed++;
+ return XDP_PASS;
+}
+
+SEC("xdp")
+int xdp_count(struct xdp_md *ctx)
+{
+ void *data = (void *)(long)ctx->data;
+ void *data_end = (void *)(long)ctx->data_end;
+ struct ethhdr *eth = data;
+ struct iphdr *iph;
+
+ /*
+ * count only the test's TCP frames: the netns has live
+ * link-local traffic (DAD, MLD) that would satisfy a bare
+ * counter
+ */
+ if ((void *)(eth + 1) > data_end ||
+ eth->h_proto != bpf_htons(ETH_P_IP))
+ return XDP_DROP;
+ iph = (void *)(eth + 1);
+ if ((void *)(iph + 1) > data_end || iph->protocol != IPPROTO_TCP)
+ return XDP_DROP;
+
+ delivered++;
+ return XDP_DROP;
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/file_reader.c b/tools/testing/selftests/bpf/progs/file_reader.c
index 4d756b623557..aa2c05cce2b3 100644
--- a/tools/testing/selftests/bpf/progs/file_reader.c
+++ b/tools/testing/selftests/bpf/progs/file_reader.c
@@ -50,7 +50,7 @@ int on_open_expect_fault(void *c)
goto out;
local_err = bpf_dynptr_read(tmp_buf, user_buf_sz, &dynptr, user_buf_sz, 0);
- if (local_err == -EFAULT) { /* Expect page fault */
+ if (local_err == -EFAULT || local_err == 0) { /* Expect page fault or success */
local_err = 0;
run_success = 1;
}
@@ -77,7 +77,7 @@ int on_open_validate_file_read(void *c)
err = 1;
return 0;
}
- bpf_task_work_schedule_signal_impl(task, &work->tw, &arrmap, task_work_callback, NULL);
+ bpf_task_work_schedule_signal(task, &work->tw, &arrmap, task_work_callback);
return 0;
}
diff --git a/tools/testing/selftests/bpf/progs/file_reader_fail.c b/tools/testing/selftests/bpf/progs/file_reader_fail.c
index 32fe28ed2439..3bb9e2612f8f 100644
--- a/tools/testing/selftests/bpf/progs/file_reader_fail.c
+++ b/tools/testing/selftests/bpf/progs/file_reader_fail.c
@@ -30,7 +30,7 @@ int on_nanosleep_unreleased_ref(void *ctx)
SEC("xdp")
__failure
-__msg("Expected a dynptr of type file as arg #0")
+__msg("Expected a dynptr of type file as R1")
int xdp_wrong_dynptr_type(struct xdp_md *xdp)
{
struct bpf_dynptr dynptr;
@@ -42,7 +42,7 @@ int xdp_wrong_dynptr_type(struct xdp_md *xdp)
SEC("xdp")
__failure
-__msg("Expected an initialized dynptr as arg #0")
+__msg("Expected an initialized dynptr as R1")
int xdp_no_dynptr_type(struct xdp_md *xdp)
{
struct bpf_dynptr dynptr;
@@ -50,3 +50,63 @@ int xdp_no_dynptr_type(struct xdp_md *xdp)
bpf_dynptr_file_discard(&dynptr);
return 0;
}
+
+SEC("lsm/file_open")
+__failure
+__msg("Leaking reference id={{[0-9]+}} alloc_insn={{[0-9]+}}. Release it first.")
+int use_file_dynptr_after_put_file(void *ctx)
+{
+ struct task_struct *task = bpf_get_current_task_btf();
+ struct file *file = bpf_get_task_exe_file(task);
+ struct bpf_dynptr dynptr;
+ char buf[64];
+
+ if (!file)
+ return 0;
+
+ if (bpf_dynptr_from_file(file, 0, &dynptr))
+ goto out;
+
+ /* this should fail - file dynptr should be discarded first to prevent resource leak */
+ bpf_put_file(file);
+
+ bpf_dynptr_read(buf, sizeof(buf), &dynptr, 0, 0);
+ return 0;
+
+out:
+ bpf_dynptr_file_discard(&dynptr);
+ bpf_put_file(file);
+ return 0;
+}
+
+SEC("lsm/file_open")
+__failure
+__msg("Leaking reference id={{[0-9]+}} alloc_insn={{[0-9]+}}. Release it first.")
+int use_file_dynptr_slice_after_put_file(void *ctx)
+{
+ struct task_struct *task = bpf_get_current_task_btf();
+ struct file *file = bpf_get_task_exe_file(task);
+ struct bpf_dynptr dynptr;
+ char buf[1];
+ const char *data;
+
+ if (!file)
+ return 0;
+
+ if (bpf_dynptr_from_file(file, 0, &dynptr))
+ goto out;
+
+ data = bpf_dynptr_slice(&dynptr, 0, buf, sizeof(buf));
+ if (!data)
+ goto out;
+
+ /* this should fail - file dynptr should be discarded first to prevent resource leak */
+ bpf_put_file(file);
+
+ return data[0];
+
+out:
+ bpf_dynptr_file_discard(&dynptr);
+ bpf_put_file(file);
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/free_timer.c b/tools/testing/selftests/bpf/progs/free_timer.c
index 4501ae8fc414..eccb2d47db43 100644
--- a/tools/testing/selftests/bpf/progs/free_timer.c
+++ b/tools/testing/selftests/bpf/progs/free_timer.c
@@ -7,6 +7,16 @@
#define MAX_ENTRIES 8
+/* clang considers 'sum += 1' as usage but 'sum++' as non-usage. GCC
+ * is more consistent and considers both 'sum += 1' and 'sum++' as
+ * non-usage. This triggers warnings in the functions below.
+ *
+ * Starting with GCC 16 -Wunused-but-set-variable=2 can be used to
+ * mimic clang's behavior. */
+#if !defined(__clang__) && __GNUC__ > 15
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
+#endif
+
struct map_value {
struct bpf_timer timer;
};
diff --git a/tools/testing/selftests/bpf/progs/freplace_cls_redirect.c b/tools/testing/selftests/bpf/progs/freplace_cls_redirect.c
deleted file mode 100644
index 7e94412d47a5..000000000000
--- a/tools/testing/selftests/bpf/progs/freplace_cls_redirect.c
+++ /dev/null
@@ -1,34 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-// Copyright (c) 2020 Facebook
-
-#include <linux/stddef.h>
-#include <linux/bpf.h>
-#include <linux/pkt_cls.h>
-#include <bpf/bpf_endian.h>
-#include <bpf/bpf_helpers.h>
-
-struct {
- __uint(type, BPF_MAP_TYPE_SOCKMAP);
- __type(key, int);
- __type(value, int);
- __uint(max_entries, 2);
-} sock_map SEC(".maps");
-
-SEC("freplace/cls_redirect")
-int freplace_cls_redirect_test(struct __sk_buff *skb)
-{
- int ret = 0;
- const int zero = 0;
- struct bpf_sock *sk;
-
- sk = bpf_map_lookup_elem(&sock_map, &zero);
- if (!sk)
- return TC_ACT_SHOT;
-
- ret = bpf_map_update_elem(&sock_map, &zero, sk, 0);
- bpf_sk_release(sk);
-
- return ret == 0 ? TC_ACT_OK : TC_ACT_SHOT;
-}
-
-char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/freplace_int_with_void.c b/tools/testing/selftests/bpf/progs/freplace_int_with_void.c
new file mode 100644
index 000000000000..cbb8f8ff2581
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/freplace_int_with_void.c
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <linux/pkt_cls.h>
+#include <bpf/bpf_helpers.h>
+
+SEC("freplace/global_func2")
+void test_freplace_int_with_void(struct __sk_buff *skb)
+{
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/freplace_void.c b/tools/testing/selftests/bpf/progs/freplace_void.c
new file mode 100644
index 000000000000..68b114f477fe
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/freplace_void.c
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+SEC("freplace/foo")
+void test_freplace_void(struct __sk_buff *skb)
+{
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/fsession_test.c b/tools/testing/selftests/bpf/progs/fsession_test.c
new file mode 100644
index 000000000000..86e8a2fe467e
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/fsession_test.c
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 ChinaTelecom */
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+__u64 test1_entry_result = 0;
+__u64 test1_exit_result = 0;
+
+SEC("fsession/bpf_fentry_test1")
+int BPF_PROG(test1, int a, int ret)
+{
+ bool is_exit = bpf_session_is_return(ctx);
+
+ if (!is_exit) {
+ test1_entry_result = a == 1 && ret == 0;
+ return 0;
+ }
+
+ test1_exit_result = a == 1 && ret == 2;
+ return 0;
+}
+
+__u64 test2_entry_result = 0;
+__u64 test2_exit_result = 0;
+
+SEC("fsession/bpf_fentry_test3")
+int BPF_PROG(test2, char a, int b, __u64 c, int ret)
+{
+ bool is_exit = bpf_session_is_return(ctx);
+
+ if (!is_exit) {
+ test2_entry_result = a == 4 && b == 5 && c == 6 && ret == 0;
+ return 0;
+ }
+
+ test2_exit_result = a == 4 && b == 5 && c == 6 && ret == 15;
+ return 0;
+}
+
+__u64 test3_entry_result = 0;
+__u64 test3_exit_result = 0;
+
+SEC("fsession/bpf_fentry_test4")
+int BPF_PROG(test3, void *a, char b, int c, __u64 d, int ret)
+{
+ bool is_exit = bpf_session_is_return(ctx);
+
+ if (!is_exit) {
+ test3_entry_result = a == (void *)7 && b == 8 && c == 9 && d == 10 && ret == 0;
+ return 0;
+ }
+
+ test3_exit_result = a == (void *)7 && b == 8 && c == 9 && d == 10 && ret == 34;
+ return 0;
+}
+
+__u64 test4_entry_result = 0;
+__u64 test4_exit_result = 0;
+
+SEC("fsession/bpf_fentry_test5")
+int BPF_PROG(test4, __u64 a, void *b, short c, int d, __u64 e, int ret)
+{
+ bool is_exit = bpf_session_is_return(ctx);
+
+ if (!is_exit) {
+ test4_entry_result = a == 11 && b == (void *)12 && c == 13 && d == 14 &&
+ e == 15 && ret == 0;
+ return 0;
+ }
+
+ test4_exit_result = a == 11 && b == (void *)12 && c == 13 && d == 14 &&
+ e == 15 && ret == 65;
+ return 0;
+}
+
+__u64 test5_entry_result = 0;
+__u64 test5_exit_result = 0;
+
+SEC("fsession/bpf_fentry_test7")
+int BPF_PROG(test5, struct bpf_fentry_test_t *arg, int ret)
+{
+ bool is_exit = bpf_session_is_return(ctx);
+
+ if (!is_exit) {
+ if (!arg)
+ test5_entry_result = ret == 0;
+ return 0;
+ }
+
+ if (!arg)
+ test5_exit_result = 1;
+ return 0;
+}
+
+__u64 test6_entry_result = 0;
+__u64 test6_exit_result = 0;
+SEC("fsession/bpf_fentry_test1")
+int BPF_PROG(test6, int a)
+{
+ __u64 addr = bpf_get_func_ip(ctx);
+
+ if (bpf_session_is_return(ctx))
+ test6_exit_result = (const void *) addr == &bpf_fentry_test1;
+ else
+ test6_entry_result = (const void *) addr == &bpf_fentry_test1;
+ return 0;
+}
+
+__u64 test7_entry_ok = 0;
+__u64 test7_exit_ok = 0;
+SEC("fsession/bpf_fentry_test1")
+int BPF_PROG(test7, int a)
+{
+ volatile __u64 *cookie = bpf_session_cookie(ctx);
+
+ if (!bpf_session_is_return(ctx)) {
+ *cookie = 0xAAAABBBBCCCCDDDDull;
+ test7_entry_ok = *cookie == 0xAAAABBBBCCCCDDDDull;
+ return 0;
+ }
+
+ test7_exit_ok = *cookie == 0xAAAABBBBCCCCDDDDull;
+ return 0;
+}
+
+__u64 test8_entry_ok = 0;
+__u64 test8_exit_ok = 0;
+
+SEC("fsession/bpf_fentry_test1")
+int BPF_PROG(test8, int a)
+{
+ volatile __u64 *cookie = bpf_session_cookie(ctx);
+
+ if (!bpf_session_is_return(ctx)) {
+ *cookie = 0x1111222233334444ull;
+ test8_entry_ok = *cookie == 0x1111222233334444ull;
+ return 0;
+ }
+
+ test8_exit_ok = *cookie == 0x1111222233334444ull;
+ return 0;
+}
+
+__u64 test9_entry_result = 0;
+__u64 test9_exit_result = 0;
+
+SEC("fsession/bpf_fentry_test1")
+int BPF_PROG(test9, int a, int ret)
+{
+ __u64 *cookie = bpf_session_cookie(ctx);
+
+ if (!bpf_session_is_return(ctx)) {
+ test9_entry_result = a == 1 && ret == 0;
+ *cookie = 0x123456ULL;
+ return 0;
+ }
+
+ test9_exit_result = a == 1 && ret == 2 && *cookie == 0x123456ULL;
+ return 0;
+}
+
+__u64 test10_result = 0;
+SEC("fexit/bpf_fentry_test1")
+int BPF_PROG(test10, int a, int ret)
+{
+ test10_result = a == 1 && ret == 2;
+ return 0;
+}
+
+__u64 test11_result = 0;
+SEC("fentry/bpf_fentry_test1")
+int BPF_PROG(test11, int a)
+{
+ test11_result = a == 1;
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/get_func_args_fsession_test.c b/tools/testing/selftests/bpf/progs/get_func_args_fsession_test.c
new file mode 100644
index 000000000000..bb597f24b659
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/get_func_args_fsession_test.c
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <errno.h>
+
+char _license[] SEC("license") = "GPL";
+
+__u64 test1_result = 0;
+
+SEC("fsession/bpf_fentry_test1")
+int BPF_PROG(test1)
+{
+ __u64 cnt = bpf_get_func_arg_cnt(ctx);
+ __u64 a = 0, z = 0, ret = 0;
+ __s64 err;
+
+ test1_result = cnt == 1;
+
+ /* valid arguments */
+ err = bpf_get_func_arg(ctx, 0, &a);
+ test1_result &= err == 0 && ((int) a == 1);
+
+ /* not valid argument */
+ err = bpf_get_func_arg(ctx, 1, &z);
+ test1_result &= err == -EINVAL;
+
+ if (bpf_session_is_return(ctx)) {
+ err = bpf_get_func_ret(ctx, &ret);
+ test1_result &= err == 0 && ret == 2;
+ } else {
+ err = bpf_get_func_ret(ctx, &ret);
+ test1_result &= err == 0 && ret == 0;
+ }
+
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/get_func_args_test.c b/tools/testing/selftests/bpf/progs/get_func_args_test.c
index e0f34a55e697..1bf47f64d096 100644
--- a/tools/testing/selftests/bpf/progs/get_func_args_test.c
+++ b/tools/testing/selftests/bpf/progs/get_func_args_test.c
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0
-#include <linux/bpf.h>
+#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <errno.h>
@@ -121,3 +121,47 @@ int BPF_PROG(fexit_test, int _a, int *_b, int _ret)
test4_result &= err == 0 && ret == 1234;
return 0;
}
+
+__u64 test5_result = 0;
+SEC("tp_btf/bpf_testmod_fentry_test1_tp")
+int BPF_PROG(tp_test1)
+{
+ __u64 cnt = bpf_get_func_arg_cnt(ctx);
+ __u64 a = 0, z = 0;
+ __s64 err;
+
+ test5_result = cnt == 1;
+
+ err = bpf_get_func_arg(ctx, 0, &a);
+ test5_result &= err == 0 && ((int) a == 1);
+
+ /* not valid argument */
+ err = bpf_get_func_arg(ctx, 1, &z);
+ test5_result &= err == -EINVAL;
+
+ return 0;
+}
+
+__u64 test6_result = 0;
+SEC("tp_btf/bpf_testmod_fentry_test2_tp")
+int BPF_PROG(tp_test2)
+{
+ __u64 cnt = bpf_get_func_arg_cnt(ctx);
+ __u64 a = 0, b = 0, z = 0;
+ __s64 err;
+
+ test6_result = cnt == 2;
+
+ /* valid arguments */
+ err = bpf_get_func_arg(ctx, 0, &a);
+ test6_result &= err == 0 && (int) a == 2;
+
+ err = bpf_get_func_arg(ctx, 1, &b);
+ test6_result &= err == 0 && b == 3;
+
+ /* not valid argument */
+ err = bpf_get_func_arg(ctx, 2, &z);
+ test6_result &= err == -EINVAL;
+
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/get_func_ip_fsession_test.c b/tools/testing/selftests/bpf/progs/get_func_ip_fsession_test.c
new file mode 100644
index 000000000000..bbeea0d512e3
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/get_func_ip_fsession_test.c
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+__u64 test1_entry_result = 0;
+__u64 test1_exit_result = 0;
+
+SEC("fsession/bpf_fentry_test1")
+int BPF_PROG(test1, int a)
+{
+ __u64 addr = bpf_get_func_ip(ctx);
+
+ if (bpf_session_is_return(ctx))
+ test1_exit_result = (const void *) addr == &bpf_fentry_test1;
+ else
+ test1_entry_result = (const void *) addr == &bpf_fentry_test1;
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/get_smp_processor_id.c b/tools/testing/selftests/bpf/progs/get_smp_processor_id.c
new file mode 100644
index 000000000000..cf4791a5cf07
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/get_smp_processor_id.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+__u64 cpu_nr_result;
+
+SEC("raw_tp")
+void call_bpf_get_smp_processor_id(void)
+{
+ register __u64 r0 asm("r0") = -1;
+ asm volatile ("call %[bpf_get_smp_processor_id];"
+ : "+r"(r0)
+ : __imm(bpf_get_smp_processor_id)
+ : "r1", "r2", "r3", "r4", "r5", "memory");
+ cpu_nr_result = r0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/htab_reuse.c b/tools/testing/selftests/bpf/progs/htab_reuse.c
index 7f7368cb3095..1c7fa7ee45ee 100644
--- a/tools/testing/selftests/bpf/progs/htab_reuse.c
+++ b/tools/testing/selftests/bpf/progs/htab_reuse.c
@@ -17,3 +17,19 @@ struct {
__type(value, struct htab_val);
__uint(map_flags, BPF_F_NO_PREALLOC);
} htab SEC(".maps");
+
+#define HTAB_NDATA 256
+
+struct htab_val_large {
+ struct bpf_spin_lock lock;
+ __u32 seq;
+ __u64 data[HTAB_NDATA];
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(max_entries, 8);
+ __type(key, unsigned int);
+ __type(value, struct htab_val_large);
+ __uint(map_flags, BPF_F_NO_PREALLOC);
+} htab_lock_consistency SEC(".maps");
diff --git a/tools/testing/selftests/bpf/progs/htab_update.c b/tools/testing/selftests/bpf/progs/htab_update.c
index 195d3b2fba00..62c1b1325ec2 100644
--- a/tools/testing/selftests/bpf/progs/htab_update.c
+++ b/tools/testing/selftests/bpf/progs/htab_update.c
@@ -22,8 +22,8 @@ struct {
int pid = 0;
int update_err = 0;
-SEC("?fentry/bpf_obj_free_fields")
-int bpf_obj_free_fields(void *ctx)
+SEC("?fentry/bpf_obj_cancel_fields")
+int bpf_obj_cancel_fields(void *ctx)
{
__u32 key = 0;
struct val value = { .payload = 1 };
diff --git a/tools/testing/selftests/bpf/progs/icmp_send.c b/tools/testing/selftests/bpf/progs/icmp_send.c
new file mode 100644
index 000000000000..c642ccdf9fd5
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/icmp_send.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+/* 127.0.0.1 in host byte order */
+#define SERVER_IP 0x7F000001
+/* ::1 in host byte order (last 32-bit word) */
+#define SERVER_IP6_LO 0x00000001
+
+__u16 server_port = 0;
+int unreach_type = 0;
+int unreach_code = 0;
+int kfunc_ret = -1;
+int target_pid = -1;
+
+unsigned int rec_count = 0;
+int rec_kfunc_rets[] = { -1, -1 };
+
+SEC("cgroup_skb/egress")
+int egress(struct __sk_buff *skb)
+{
+ void *data = (void *)(long)skb->data;
+ void *data_end = (void *)(long)skb->data_end;
+ struct iphdr *iph;
+ struct ipv6hdr *ip6h;
+ struct tcphdr *tcph;
+ __u8 version;
+
+ if (data + 1 > data_end)
+ return SK_PASS;
+
+ version = (*((__u8 *)data)) >> 4;
+
+ if (version == 4) {
+ iph = data;
+ if ((void *)(iph + 1) > data_end ||
+ iph->protocol != IPPROTO_TCP ||
+ iph->daddr != bpf_htonl(SERVER_IP))
+ return SK_PASS;
+
+ tcph = (void *)iph + iph->ihl * 4;
+ if ((void *)(tcph + 1) > data_end ||
+ tcph->dest != bpf_htons(server_port))
+ return SK_PASS;
+
+ } else if (version == 6) {
+ ip6h = data;
+ if ((void *)(ip6h + 1) > data_end ||
+ ip6h->nexthdr != IPPROTO_TCP)
+ return SK_PASS;
+
+ if (ip6h->daddr.in6_u.u6_addr32[0] != 0 ||
+ ip6h->daddr.in6_u.u6_addr32[1] != 0 ||
+ ip6h->daddr.in6_u.u6_addr32[2] != 0 ||
+ ip6h->daddr.in6_u.u6_addr32[3] != bpf_htonl(SERVER_IP6_LO))
+ return SK_PASS;
+
+ tcph = (void *)(ip6h + 1);
+ if ((void *)(tcph + 1) > data_end ||
+ tcph->dest != bpf_htons(server_port))
+ return SK_PASS;
+ } else {
+ return SK_PASS;
+ }
+
+ kfunc_ret = bpf_icmp_send(skb, unreach_type, unreach_code);
+
+ return SK_DROP;
+}
+
+SEC("cgroup_skb/egress")
+int recursion(struct __sk_buff *skb)
+{
+ void *data = (void *)(long)skb->data;
+ void *data_end = (void *)(long)skb->data_end;
+ struct icmphdr *icmph;
+ struct tcphdr *tcph;
+ struct iphdr *iph;
+ int ret;
+
+ if ((bpf_get_current_pid_tgid() >> 32) != target_pid)
+ return SK_PASS;
+
+ iph = data;
+ if ((void *)(iph + 1) > data_end || iph->version != 4)
+ return SK_PASS;
+
+ if (iph->daddr != bpf_htonl(SERVER_IP))
+ return SK_PASS;
+
+ if (iph->protocol == IPPROTO_TCP) {
+ tcph = (void *)iph + iph->ihl * 4;
+ if ((void *)(tcph + 1) > data_end ||
+ tcph->dest != bpf_htons(server_port))
+ return SK_PASS;
+ } else if (iph->protocol == IPPROTO_ICMP) {
+ icmph = (void *)iph + iph->ihl * 4;
+ if ((void *)(icmph + 1) > data_end ||
+ icmph->type != unreach_type || icmph->code != unreach_code)
+ return SK_PASS;
+ } else {
+ return SK_PASS;
+ }
+
+ /*
+ * This call will provoke a recursion: the ICMP packet generated by the
+ * kfunc will re-trigger this program since we are in the root cgroup in
+ * which the kernel ICMP socket belongs. However when re-entering the
+ * kfunc, it should return EBUSY.
+ */
+ ret = bpf_icmp_send(skb, unreach_type, unreach_code);
+ rec_kfunc_rets[rec_count & 1] = ret;
+ __sync_fetch_and_add(&rec_count, 1);
+
+ /* Let the first ICMP error message pass */
+ if (iph->protocol == IPPROTO_ICMP)
+ return SK_PASS;
+
+ return SK_DROP;
+}
+
+char LICENSE[] SEC("license") = "Dual BSD/GPL";
diff --git a/tools/testing/selftests/bpf/progs/irq.c b/tools/testing/selftests/bpf/progs/irq.c
index 74d912b22de9..a4a007866a33 100644
--- a/tools/testing/selftests/bpf/progs/irq.c
+++ b/tools/testing/selftests/bpf/progs/irq.c
@@ -15,7 +15,7 @@ struct bpf_res_spin_lock lockA __hidden SEC(".data.A");
struct bpf_res_spin_lock lockB __hidden SEC(".data.B");
SEC("?tc")
-__failure __msg("arg#0 doesn't point to an irq flag on stack")
+__failure __msg("R1 doesn't point to an irq flag on stack")
int irq_save_bad_arg(struct __sk_buff *ctx)
{
bpf_local_irq_save(&global_flags);
@@ -23,7 +23,7 @@ int irq_save_bad_arg(struct __sk_buff *ctx)
}
SEC("?tc")
-__failure __msg("arg#0 doesn't point to an irq flag on stack")
+__failure __msg("R1 doesn't point to an irq flag on stack")
int irq_restore_bad_arg(struct __sk_buff *ctx)
{
bpf_local_irq_restore(&global_flags);
@@ -490,7 +490,7 @@ int irq_non_sleepable_global_subprog(void *ctx)
}
SEC("?syscall")
-__failure __msg("global functions that may sleep are not allowed in non-sleepable context")
+__failure __msg("sleepable global function")
int irq_sleepable_helper_global_subprog(void *ctx)
{
unsigned long flags;
@@ -502,7 +502,7 @@ int irq_sleepable_helper_global_subprog(void *ctx)
}
SEC("?syscall")
-__failure __msg("global functions that may sleep are not allowed in non-sleepable context")
+__failure __msg("sleepable global function")
int irq_sleepable_global_subprog_indirect(void *ctx)
{
unsigned long flags;
diff --git a/tools/testing/selftests/bpf/progs/iter_buf_null_fail.c b/tools/testing/selftests/bpf/progs/iter_buf_null_fail.c
new file mode 100644
index 000000000000..3daad40515e6
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/iter_buf_null_fail.c
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Qi Tang */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+/* Verify that the verifier rejects direct access to nullable PTR_TO_BUF. */
+SEC("iter/bpf_map_elem")
+__failure __msg("invalid mem access")
+int iter_buf_null_deref(struct bpf_iter__bpf_map_elem *ctx)
+{
+ /*
+ * ctx->key is PTR_TO_BUF | PTR_MAYBE_NULL | MEM_RDONLY.
+ * Direct access without null check must be rejected.
+ */
+ volatile __u32 v = *(__u32 *)ctx->key;
+
+ (void)v;
+ return 0;
+}
+
+/* Verify that access after a null check is still accepted. */
+SEC("iter/bpf_map_elem")
+__success
+int iter_buf_null_check_ok(struct bpf_iter__bpf_map_elem *ctx)
+{
+ __u32 *key = ctx->key;
+
+ if (!key)
+ return 0;
+
+ volatile __u32 v = *key;
+
+ (void)v;
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/iters.c b/tools/testing/selftests/bpf/progs/iters.c
index 7dd92a303bf6..c6699159dacd 100644
--- a/tools/testing/selftests/bpf/progs/iters.c
+++ b/tools/testing/selftests/bpf/progs/iters.c
@@ -88,6 +88,89 @@ int iter_err_unsafe_asm_loop(const void *ctx)
return 0;
}
+/*
+ * Naked function, so there is no compiler-generated glue and the whole inlined program can be
+ * matched. Pinned to arches whose JITs zero-extend 32-bit writes implicitly
+ * (bpf_jit_needs_zext() == false); on arches that need explicit zero-extension the verifier
+ * interleaves "wN = wN" insns and the fixed shape below would not match. The inlining itself is
+ * arch independent, so checking it on these arches is sufficient.
+ *
+ * bpf_iter_num_new() emits the full range check (distance computation and both the -EINVAL and
+ * -E2BIG error paths); bpf_iter_num_next() and bpf_iter_num_destroy() are inlined too.
+ */
+SEC("raw_tp")
+__arch_x86_64
+__arch_arm64
+__success
+__xlated("r6 = r10")
+__xlated("r6 += -8")
+__xlated("call unknown")
+__xlated("r3 = r0")
+__xlated("r3 &= 65535")
+__xlated("r1 = r6")
+__xlated("r2 = 0")
+/* bpf_iter_num_new(&it, 0, <non-const>) with the range check kept */
+__xlated("if w2 s> w3 goto pc+8")
+__xlated("w0 = w3")
+__xlated("w0 -= w2")
+__xlated("if r0 > 0x800000 goto pc+8")
+__xlated("w2 += -1")
+__xlated("*(u32 *)(r1 +0) = r2")
+__xlated("*(u32 *)(r1 +4) = r3")
+__xlated("r0 = 0")
+__xlated("goto pc+5")
+__xlated("*(u64 *)(r1 +0) = 0")
+__xlated("r0 = -22")
+__xlated("goto pc+2")
+__xlated("*(u64 *)(r1 +0) = 0")
+__xlated("r0 = -7")
+__xlated("r1 = r6")
+/* bpf_iter_num_next(&it) */
+__xlated("r0 = *(u32 *)(r1 +0)")
+__xlated("w0 += 1")
+__xlated("r2 = *(u32 *)(r1 +4)")
+__xlated("if w0 s>= w2 goto pc+3")
+__xlated("*(u32 *)(r1 +0) = r0")
+__xlated("r0 = r1")
+__xlated("goto pc+2")
+__xlated("*(u64 *)(r1 +0) = 0")
+__xlated("r0 = 0")
+__xlated("if r0 != 0x0 goto pc-11")
+__xlated("r1 = r6")
+/* bpf_iter_num_destroy(&it) is inlined to a nop */
+__xlated("goto pc+0")
+__xlated("r0 = 0")
+__xlated("exit")
+int __naked iter_num_new_inlined(void)
+{
+ asm volatile (
+ /* r6 points to struct bpf_iter_num on the stack */
+ "r6 = r10;"
+ "r6 += -8;"
+ /* non-constant end so the range checks are kept */
+ "call %[bpf_get_prandom_u32];"
+ "r3 = r0;"
+ "r3 &= 0xffff;"
+ "r1 = r6;"
+ "r2 = 0;"
+ "call %[bpf_iter_num_new];"
+ "1:"
+ "r1 = r6;"
+ "call %[bpf_iter_num_next];"
+ "if r0 != 0 goto 1b;"
+ "r1 = r6;"
+ "call %[bpf_iter_num_destroy];"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_get_prandom_u32),
+ __imm(bpf_iter_num_new),
+ __imm(bpf_iter_num_next),
+ __imm(bpf_iter_num_destroy)
+ : __clobber_common, "r6"
+ );
+}
+
SEC("raw_tp")
__success
int iter_while_loop(const void *ctx)
@@ -1605,7 +1688,7 @@ int iter_subprog_check_stacksafe(const void *ctx)
struct bpf_iter_num global_it;
SEC("raw_tp")
-__failure __msg("arg#0 expected pointer to an iterator on stack")
+__failure __msg("R1 expected pointer to an iterator on stack")
int iter_new_bad_arg(const void *ctx)
{
bpf_iter_num_new(&global_it, 0, 1);
@@ -1613,7 +1696,7 @@ int iter_new_bad_arg(const void *ctx)
}
SEC("raw_tp")
-__failure __msg("arg#0 expected pointer to an iterator on stack")
+__failure __msg("R1 expected pointer to an iterator on stack")
int iter_next_bad_arg(const void *ctx)
{
bpf_iter_num_next(&global_it);
@@ -1621,7 +1704,7 @@ int iter_next_bad_arg(const void *ctx)
}
SEC("raw_tp")
-__failure __msg("arg#0 expected pointer to an iterator on stack")
+__failure __msg("R1 expected pointer to an iterator on stack")
int iter_destroy_bad_arg(const void *ctx)
{
bpf_iter_num_destroy(&global_it);
@@ -1651,7 +1734,7 @@ int clean_live_states(const void *ctx)
SEC("?raw_tp")
__flag(BPF_F_TEST_STATE_FREQ)
-__failure __msg("misaligned stack access off 0+-31+0 size 8")
+__failure __msg("misaligned stack access off -31+0 size 8")
__naked int absent_mark_in_the_middle_state(void)
{
/* This is equivalent to C program below.
@@ -1726,7 +1809,7 @@ static int noop(void)
SEC("?raw_tp")
__flag(BPF_F_TEST_STATE_FREQ)
-__failure __msg("misaligned stack access off 0+-31+0 size 8")
+__failure __msg("misaligned stack access off -31+0 size 8")
__naked int absent_mark_in_the_middle_state2(void)
{
/* This is equivalent to C program below.
@@ -1802,7 +1885,7 @@ __naked int absent_mark_in_the_middle_state2(void)
SEC("?raw_tp")
__flag(BPF_F_TEST_STATE_FREQ)
-__failure __msg("misaligned stack access off 0+-31+0 size 8")
+__failure __msg("misaligned stack access off -31+0 size 8")
__naked int absent_mark_in_the_middle_state3(void)
{
/*
@@ -1926,4 +2009,183 @@ static int loop1_wrapper(void)
);
}
+/*
+ * This is similar to a test case absent_mark_in_the_middle_state(),
+ * but adapted for use with bpf_loop().
+ */
+SEC("raw_tp")
+__flag(BPF_F_TEST_STATE_FREQ)
+__failure __msg("math between fp pointer and register with unbounded min value is not allowed")
+__naked void absent_mark_in_the_middle_state4(void)
+{
+ /*
+ * Equivalent to a C program below:
+ *
+ * int main(void) {
+ * fp[-8] = bpf_get_prandom_u32();
+ * fp[-16] = -32; // used in a memory access below
+ * bpf_loop(7, loop_cb4, fp, 0);
+ * return 0;
+ * }
+ *
+ * int loop_cb4(int i, void *ctx) {
+ * if (unlikely(ctx[-8] > bpf_get_prandom_u32()))
+ * *(u64 *)(fp + ctx[-16]) = 42; // aligned access expected
+ * if (unlikely(fp[-8] > bpf_get_prandom_u32()))
+ * ctx[-16] = -31; // makes said access unaligned
+ * return 0;
+ * }
+ */
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "r8 = r0;"
+ "*(u64 *)(r10 - 8) = r0;"
+ "*(u64 *)(r10 - 16) = -32;"
+ "r1 = 7;"
+ "r2 = loop_cb4 ll;"
+ "r3 = r10;"
+ "r4 = 0;"
+ "call %[bpf_loop];"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_loop),
+ __imm(bpf_get_prandom_u32)
+ : __clobber_all
+ );
+}
+
+__used __naked
+static void loop_cb4(void)
+{
+ asm volatile (
+ "r9 = r2;"
+ "r8 = *(u64 *)(r9 - 8);"
+ "r6 = *(u64 *)(r9 - 16);"
+ "call %[bpf_get_prandom_u32];"
+ "if r0 > r8 goto use_fp16_%=;"
+ "1:"
+ "call %[bpf_get_prandom_u32];"
+ "if r0 > r8 goto update_fp16_%=;"
+ "2:"
+ "r0 = 0;"
+ "exit;"
+ "use_fp16_%=:"
+ "r1 = r10;"
+ "r1 += r6;"
+ "*(u64 *)(r1 + 0) = 42;"
+ "goto 1b;"
+ "update_fp16_%=:"
+ "*(u64 *)(r9 - 16) = -31;"
+ "goto 2b;"
+ :
+ : __imm(bpf_get_prandom_u32)
+ );
+}
+
+SEC("raw_tp")
+__success
+__naked int stack_misc_vs_scalar_in_a_loop(void)
+{
+ asm volatile(
+ "*(u8 *)(r10 - 15) = 1;" /* This marks stack slot fp[-16] as STACK_MISC. */
+ "*(u8 *)(r10 - 23) = 1;"
+ "*(u8 *)(r10 - 31) = 1;"
+ "*(u8 *)(r10 - 39) = 1;"
+ "*(u8 *)(r10 - 47) = 1;"
+ "*(u8 *)(r10 - 55) = 1;"
+ "*(u8 *)(r10 - 63) = 1;"
+ "*(u8 *)(r10 - 71) = 1;"
+ "*(u8 *)(r10 - 79) = 1;"
+ "r1 = r10;"
+ "r1 += -8;"
+ "r2 = 0;"
+ "r3 = 10;"
+ "call %[bpf_iter_num_new];"
+ "loop_%=:"
+ "r1 = r10;"
+ "r1 += -8;"
+ "call %[bpf_iter_num_next];"
+ "if r0 == 0 goto loop_end_%=;"
+
+#define maybe_change_stack_slot(off) \
+ "call %[bpf_get_prandom_u32];" \
+ "if r0 == 42 goto +1;" \
+ "goto +1;" \
+ "*(u64 *)(r10 " #off ") = r0;"
+
+ /*
+ * When comparing verifier states fp[-16] will be
+ * either STACK_MISC or SCALAR. Pruning logic should
+ * consider old STACK_MISC equivalent to current SCALAR
+ * to avoid states explosion.
+ */
+ maybe_change_stack_slot(-16)
+ maybe_change_stack_slot(-24)
+ maybe_change_stack_slot(-32)
+ maybe_change_stack_slot(-40)
+ maybe_change_stack_slot(-48)
+ maybe_change_stack_slot(-56)
+ maybe_change_stack_slot(-64)
+ maybe_change_stack_slot(-72)
+ maybe_change_stack_slot(-80)
+
+#undef maybe_change_stack_slot
+
+ "goto loop_%=;"
+ "loop_end_%=:"
+ "r1 = r10;"
+ "r1 += -8;"
+ "call %[bpf_iter_num_destroy];"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_get_prandom_u32),
+ __imm(bpf_iter_num_new),
+ __imm(bpf_iter_num_next),
+ __imm(bpf_iter_num_destroy),
+ __imm_addr(amap)
+ : __clobber_all
+ );
+}
+
+__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/iters_css.c b/tools/testing/selftests/bpf/progs/iters_css.c
index ec1f6c2f590b..5a1d87d186a9 100644
--- a/tools/testing/selftests/bpf/progs/iters_css.c
+++ b/tools/testing/selftests/bpf/progs/iters_css.c
@@ -12,8 +12,7 @@ char _license[] SEC("license") = "GPL";
pid_t target_pid;
u64 root_cg_id, leaf_cg_id;
u64 first_cg_id, last_cg_id;
-
-int pre_order_cnt, post_order_cnt, tree_high;
+int pre_order_cnt, post_order_cnt, children_cnt, tree_high;
struct cgroup *bpf_cgroup_from_id(u64 cgid) __ksym;
void bpf_cgroup_release(struct cgroup *p) __ksym;
@@ -43,7 +42,7 @@ int iter_css_for_each(const void *ctx)
}
root_css = &root_cgrp->self;
leaf_css = &leaf_cgrp->self;
- pre_order_cnt = post_order_cnt = tree_high = 0;
+ pre_order_cnt = post_order_cnt = children_cnt = tree_high = 0;
first_cg_id = last_cg_id = 0;
bpf_rcu_read_lock();
@@ -60,6 +59,10 @@ int iter_css_for_each(const void *ctx)
first_cg_id = cur_cgrp->kn->id;
}
+ bpf_for_each(css, pos, root_css, BPF_CGROUP_ITER_CHILDREN) {
+ children_cnt++;
+ }
+
bpf_for_each(css, pos, leaf_css, BPF_CGROUP_ITER_ANCESTORS_UP)
tree_high++;
diff --git a/tools/testing/selftests/bpf/progs/iters_state_safety.c b/tools/testing/selftests/bpf/progs/iters_state_safety.c
index d273b46dfc7c..e5bb9fe6d5e5 100644
--- a/tools/testing/selftests/bpf/progs/iters_state_safety.c
+++ b/tools/testing/selftests/bpf/progs/iters_state_safety.c
@@ -30,7 +30,7 @@ int force_clang_to_emit_btf_for_externs(void *ctx)
SEC("?raw_tp")
__success __log_level(2)
-__msg("fp-8=iter_num(ref_id=1,state=active,depth=0)")
+__msg("fp-8=iter_num(id=1,state=active,depth=0)")
int create_and_destroy(void *ctx)
{
struct bpf_iter_num iter;
@@ -52,6 +52,28 @@ int create_and_destroy(void *ctx)
return 0;
}
+/* fp+0 is not a stack slot. bpf_get_spi(0) used to alias spi 0 (fp-8). */
+SEC("?raw_tp")
+__failure __msg("cannot pass in iter at an offset=0")
+int destroy_fp0_fail(void *ctx)
+{
+ struct bpf_iter_num iter;
+
+ asm volatile ("r1 = %[iter];"
+ "r2 = 0;"
+ "r3 = 1000;"
+ "call %[bpf_iter_num_new];"
+ /* r10 is fp+0, one byte above the top of the BPF stack */
+ "r1 = r10;"
+ "call %[bpf_iter_num_destroy];"
+ :
+ : __imm_ptr(iter), ITER_HELPERS
+ : __clobber_common
+ );
+
+ return 0;
+}
+
SEC("?raw_tp")
__failure __msg("Unreleased reference id=1")
int create_and_forget_to_destroy_fail(void *ctx)
@@ -73,7 +95,7 @@ int create_and_forget_to_destroy_fail(void *ctx)
}
SEC("?raw_tp")
-__failure __msg("expected an initialized iter_num as arg #0")
+__failure __msg("expected an initialized iter_num as R1")
int destroy_without_creating_fail(void *ctx)
{
/* init with zeros to stop verifier complaining about uninit stack */
@@ -91,7 +113,7 @@ int destroy_without_creating_fail(void *ctx)
}
SEC("?raw_tp")
-__failure __msg("expected an initialized iter_num as arg #0")
+__failure __msg("expected an initialized iter_num as R1")
int compromise_iter_w_direct_write_fail(void *ctx)
{
struct bpf_iter_num iter;
@@ -143,7 +165,7 @@ int compromise_iter_w_direct_write_and_skip_destroy_fail(void *ctx)
}
SEC("?raw_tp")
-__failure __msg("expected an initialized iter_num as arg #0")
+__failure __msg("expected an initialized iter_num as R1")
int compromise_iter_w_helper_write_fail(void *ctx)
{
struct bpf_iter_num iter;
@@ -196,7 +218,7 @@ int leak_iter_from_subprog_fail(void *ctx)
SEC("?raw_tp")
__success __log_level(2)
-__msg("fp-8=iter_num(ref_id=1,state=active,depth=0)")
+__msg("fp-8=iter_num(id=1,state=active,depth=0)")
int valid_stack_reuse(void *ctx)
{
struct bpf_iter_num iter;
@@ -230,7 +252,7 @@ int valid_stack_reuse(void *ctx)
}
SEC("?raw_tp")
-__failure __msg("expected uninitialized iter_num as arg #0")
+__failure __msg("expected uninitialized iter_num as R1")
int double_create_fail(void *ctx)
{
struct bpf_iter_num iter;
@@ -258,7 +280,7 @@ int double_create_fail(void *ctx)
}
SEC("?raw_tp")
-__failure __msg("expected an initialized iter_num as arg #0")
+__failure __msg("expected an initialized iter_num as R1")
int double_destroy_fail(void *ctx)
{
struct bpf_iter_num iter;
@@ -284,7 +306,7 @@ int double_destroy_fail(void *ctx)
}
SEC("?raw_tp")
-__failure __msg("expected an initialized iter_num as arg #0")
+__failure __msg("expected an initialized iter_num as R1")
int next_without_new_fail(void *ctx)
{
struct bpf_iter_num iter;
@@ -305,7 +327,7 @@ int next_without_new_fail(void *ctx)
}
SEC("?raw_tp")
-__failure __msg("expected an initialized iter_num as arg #0")
+__failure __msg("expected an initialized iter_num as R1")
int next_after_destroy_fail(void *ctx)
{
struct bpf_iter_num iter;
diff --git a/tools/testing/selftests/bpf/progs/iters_task_failure.c b/tools/testing/selftests/bpf/progs/iters_task_failure.c
index fe3663dedbe1..566a1d3dffea 100644
--- a/tools/testing/selftests/bpf/progs/iters_task_failure.c
+++ b/tools/testing/selftests/bpf/progs/iters_task_failure.c
@@ -62,6 +62,52 @@ int BPF_PROG(iter_tasks_lock_and_unlock)
}
SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
+__failure __msg("expected an RCU CS when using bpf_iter_task_next")
+__flag(BPF_F_TEST_STATE_FREQ)
+int BPF_PROG(iter_tasks_rcu_state_pruning)
+{
+ struct bpf_iter_task it;
+
+ asm volatile (
+ "call %[bpf_rcu_read_lock];"
+ "r1 = %[it];"
+ "r2 = 0;"
+ "r3 = 0;" /* BPF_TASK_ITER_ALL_PROCS */
+ "call %[bpf_iter_task_new];"
+
+ "call %[bpf_get_prandom_u32];"
+ "if w0 == 0 goto unprotected_%=;"
+
+ /* Keep the outer RCU lock active on the straight-line path. */
+ "call %[bpf_rcu_read_lock];"
+ "call %[bpf_rcu_read_unlock];"
+ "goto merge_%=;"
+
+ "unprotected_%=:"
+ /* Create an unprotected gap on the taken path. */
+ "call %[bpf_rcu_read_unlock];"
+ "call %[bpf_rcu_read_lock];"
+
+ "merge_%=: r1 = %[it];"
+ "call %[bpf_iter_task_next];"
+ "r1 = %[it];"
+ "call %[bpf_iter_task_destroy];"
+ "call %[bpf_rcu_read_unlock];"
+ :
+ : __imm_ptr(it),
+ __imm(bpf_get_prandom_u32),
+ __imm(bpf_iter_task_new),
+ __imm(bpf_iter_task_next),
+ __imm(bpf_iter_task_destroy),
+ __imm(bpf_rcu_read_lock),
+ __imm(bpf_rcu_read_unlock)
+ : __clobber_common
+ );
+
+ return 0;
+}
+
+SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
__failure __msg("expected an RCU CS when using bpf_iter_css_next")
int BPF_PROG(iter_css_lock_and_unlock)
{
diff --git a/tools/testing/selftests/bpf/progs/iters_testmod.c b/tools/testing/selftests/bpf/progs/iters_testmod.c
index 5379e9960ffd..76012dbbdb41 100644
--- a/tools/testing/selftests/bpf/progs/iters_testmod.c
+++ b/tools/testing/selftests/bpf/progs/iters_testmod.c
@@ -29,7 +29,7 @@ out:
}
SEC("raw_tp/sys_enter")
-__failure __msg("Possibly NULL pointer passed to trusted arg0")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
int iter_next_trusted_or_null(const void *ctx)
{
struct task_struct *cur_task = bpf_get_current_task_btf();
@@ -67,7 +67,7 @@ out:
}
SEC("raw_tp/sys_enter")
-__failure __msg("Possibly NULL pointer passed to trusted arg0")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
int iter_next_rcu_or_null(const void *ctx)
{
struct task_struct *cur_task = bpf_get_current_task_btf();
diff --git a/tools/testing/selftests/bpf/progs/iters_testmod_seq.c b/tools/testing/selftests/bpf/progs/iters_testmod_seq.c
index 83791348bed5..d00888f6687a 100644
--- a/tools/testing/selftests/bpf/progs/iters_testmod_seq.c
+++ b/tools/testing/selftests/bpf/progs/iters_testmod_seq.c
@@ -20,8 +20,8 @@ __s64 res_empty;
SEC("raw_tp/sys_enter")
__success __log_level(2)
-__msg("fp-16=iter_testmod_seq(ref_id=1,state=active,depth=0)")
-__msg("fp-16=iter_testmod_seq(ref_id=1,state=drained,depth=0)")
+__msg("fp-16=iter_testmod_seq(id=1,state=active,depth=0)")
+__msg("fp-16=iter_testmod_seq(id=1,state=drained,depth=0)")
__msg("call bpf_iter_testmod_seq_destroy")
int testmod_seq_empty(const void *ctx)
{
@@ -38,8 +38,8 @@ __s64 res_full;
SEC("raw_tp/sys_enter")
__success __log_level(2)
-__msg("fp-16=iter_testmod_seq(ref_id=1,state=active,depth=0)")
-__msg("fp-16=iter_testmod_seq(ref_id=1,state=drained,depth=0)")
+__msg("fp-16=iter_testmod_seq(id=1,state=active,depth=0)")
+__msg("fp-16=iter_testmod_seq(id=1,state=drained,depth=0)")
__msg("call bpf_iter_testmod_seq_destroy")
int testmod_seq_full(const void *ctx)
{
@@ -58,8 +58,8 @@ static volatile int zero = 0;
SEC("raw_tp/sys_enter")
__success __log_level(2)
-__msg("fp-16=iter_testmod_seq(ref_id=1,state=active,depth=0)")
-__msg("fp-16=iter_testmod_seq(ref_id=1,state=drained,depth=0)")
+__msg("fp-16=iter_testmod_seq(id=1,state=active,depth=0)")
+__msg("fp-16=iter_testmod_seq(id=1,state=drained,depth=0)")
__msg("call bpf_iter_testmod_seq_destroy")
int testmod_seq_truncated(const void *ctx)
{
@@ -79,7 +79,7 @@ int testmod_seq_truncated(const void *ctx)
SEC("?raw_tp")
__failure
-__msg("expected an initialized iter_testmod_seq as arg #1")
+__msg("expected an initialized iter_testmod_seq as R2")
int testmod_seq_getter_before_bad(const void *ctx)
{
struct bpf_iter_testmod_seq it;
@@ -89,7 +89,7 @@ int testmod_seq_getter_before_bad(const void *ctx)
SEC("?raw_tp")
__failure
-__msg("expected an initialized iter_testmod_seq as arg #1")
+__msg("expected an initialized iter_testmod_seq as R2")
int testmod_seq_getter_after_bad(const void *ctx)
{
struct bpf_iter_testmod_seq it;
diff --git a/tools/testing/selftests/bpf/progs/kfunc_call_fail.c b/tools/testing/selftests/bpf/progs/kfunc_call_fail.c
index a1963497f0bf..7e93f7fb1329 100644
--- a/tools/testing/selftests/bpf/progs/kfunc_call_fail.c
+++ b/tools/testing/selftests/bpf/progs/kfunc_call_fail.c
@@ -4,6 +4,18 @@
#include <bpf/bpf_helpers.h>
#include "../test_kmods/bpf_testmod_kfunc.h"
+static struct bpf_spin_lock kfunc_call_lock SEC(".data.A");
+
+SEC("?tc")
+int kfunc_call_test_spin_lock_unsafe(struct __sk_buff *skb)
+{
+ bpf_spin_lock(&kfunc_call_lock);
+ bpf_kfunc_trigger_ctx_check();
+ bpf_spin_unlock(&kfunc_call_lock);
+
+ return 0;
+}
+
struct syscall_test_args {
__u8 data[16];
size_t size;
@@ -103,6 +115,66 @@ int kfunc_call_test_get_mem_fail_oob(struct __sk_buff *skb)
return ret;
}
+SEC("?tc")
+int kfunc_call_test_get_mem_fail_zero_size(struct __sk_buff *skb)
+{
+ struct prog_test_ref_kfunc *pt;
+ unsigned long s = 0;
+ int *p = NULL;
+ int ret = 0;
+
+ pt = bpf_kfunc_call_test_acquire(&s);
+ if (pt) {
+ /*
+ * An explicit rdwr_buf_size of 0 gives R0 a zero-sized buffer,
+ * so any access is out of bounds, hence -EACCES. Previously the
+ * verifier treated a zero size as "no size argument" and sized
+ * R0 after the pointed-to return type, wrongly allowing the read.
+ */
+ p = bpf_kfunc_call_test_get_rdwr_mem(pt, 0);
+ if (p)
+ ret = p[0];
+ else
+ ret = -1;
+
+ bpf_kfunc_call_test_release(pt);
+ }
+ return ret;
+}
+
+SEC("?tc")
+int kfunc_call_test_get_mem_fail_oversized(struct __sk_buff *skb)
+{
+ struct prog_test_ref_kfunc *pt;
+ unsigned long s = 0;
+ int *p = NULL;
+ int ret = 0;
+
+ pt = bpf_kfunc_call_test_acquire(&s);
+ if (pt) {
+ /*
+ * rdwr_buf_size is a const int, so a C literal is narrowed to
+ * 32 bits before the call. Force the full 64-bit value 2^64 - 192
+ * (0xffffffffffffff40, > U32_MAX) into the argument register with
+ * a 64-bit immediate load. The verifier records r0_size from the
+ * full register value and must reject it before that value is
+ * truncated into R0's u32 mem_size.
+ */
+ asm volatile (
+ "r1 = %[pt];"
+ "r2 = %[oversized] ll;"
+ "call %[get_rdwr_mem];"
+ "%[p] = r0;"
+ : [p] "=r"(p)
+ : [pt] "r"(pt),
+ [oversized] "i"(0xffffffffffffff40LL),
+ [get_rdwr_mem] "i"(bpf_kfunc_call_test_get_rdwr_mem)
+ : "r0", "r1", "r2", "r3", "r4", "r5");
+ bpf_kfunc_call_test_release(pt);
+ }
+ return ret;
+}
+
int not_const_size = 2 * sizeof(int);
SEC("?tc")
diff --git a/tools/testing/selftests/bpf/progs/kfunc_call_test.c b/tools/testing/selftests/bpf/progs/kfunc_call_test.c
index 8b86113a0126..8e6560c31e78 100644
--- a/tools/testing/selftests/bpf/progs/kfunc_call_test.c
+++ b/tools/testing/selftests/bpf/progs/kfunc_call_test.c
@@ -2,8 +2,118 @@
/* Copyright (c) 2021 Facebook */
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
#include "../test_kmods/bpf_testmod_kfunc.h"
+static struct bpf_spin_lock kfunc_call_lock SEC(".data.A");
+
+SEC("tc")
+int kfunc_call_test_spin_lock_safe(struct __sk_buff *skb)
+{
+ bpf_spin_lock(&kfunc_call_lock);
+ bpf_testmod_test_mod_kfunc(42);
+ bpf_spin_unlock(&kfunc_call_lock);
+
+ return 0;
+}
+
+SEC("tc")
+int kfunc_call_test5(struct __sk_buff *skb)
+{
+ struct bpf_sock *sk = skb->sk;
+ int ret;
+ u32 val32;
+ u16 val16;
+ u8 val8;
+
+ if (!sk)
+ return -1;
+
+ sk = bpf_sk_fullsock(sk);
+ if (!sk)
+ return -1;
+
+ /*
+ * Test with constant values to verify zero-extension.
+ * ISA-dependent BPF asm:
+ * With ALU32: w1 = 0xFF; w2 = 0xFFFF; w3 = 0xFFFFffff
+ * Without ALU32: r1 = 0xFF; r2 = 0xFFFF; r3 = 0xFFFFffff
+ * Both zero-extend to 64-bit before the kfunc call.
+ */
+ ret = bpf_kfunc_call_test5(0xFF, 0xFFFF, 0xFFFFffffULL);
+ if (ret)
+ return ret;
+
+ val32 = bpf_get_prandom_u32();
+ val16 = val32 & 0xFFFF;
+ val8 = val32 & 0xFF;
+ ret = bpf_kfunc_call_test5(val8, val16, val32);
+ if (ret)
+ return ret;
+
+ /*
+ * Test multiplication with different operand sizes:
+ *
+ * val8 * 0xFF:
+ * - Both operands promote to int (32-bit signed)
+ * - Result: 32-bit multiplication, truncated to u8, then zero-extended
+ *
+ * val16 * 0xFFFF:
+ * - Both operands promote to int (32-bit signed)
+ * - Result: 32-bit multiplication, truncated to u16, then zero-extended
+ *
+ * val32 * 0xFFFFffffULL:
+ * - val32 (u32) promotes to unsigned long long (due to ULL suffix)
+ * - Result: 64-bit unsigned multiplication, truncated to u32, then zero-extended
+ */
+ ret = bpf_kfunc_call_test5(val8 * 0xFF, val16 * 0xFFFF, val32 * 0xFFFFffffULL);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * Assembly version testing the multiplication edge case explicitly.
+ * This ensures consistent testing across different ISA versions.
+ */
+SEC("tc")
+__naked int kfunc_call_test5_asm(void)
+{
+ asm volatile (
+ /* Get a random u32 value */
+ "call %[bpf_get_prandom_u32];"
+ "r6 = r0;" /* Save val32 in r6 */
+
+ /* Prepare first argument: val8 * 0xFF */
+ "r1 = r6;"
+ "r1 &= 0xFF;" /* val8 = val32 & 0xFF */
+ "r7 = 0xFF;"
+ "r1 *= r7;" /* 64-bit mult: r1 = r1 * r7 */
+
+ /* Prepare second argument: val16 * 0xFFFF */
+ "r2 = r6;"
+ "r2 &= 0xFFFF;" /* val16 = val32 & 0xFFFF */
+ "r7 = 0xFFFF;"
+ "r2 *= r7;" /* 64-bit mult: r2 = r2 * r7 */
+
+ /* Prepare third argument: val32 * 0xFFFFffff */
+ "r3 = r6;" /* val32 */
+ "r7 = 0xFFFFffff;"
+ "r3 *= r7;" /* 64-bit mult: r3 = r3 * r7 */
+
+ /* Call kfunc with multiplication results */
+ "call bpf_kfunc_call_test5;"
+
+ /* Check return value */
+ "if r0 != 0 goto exit_%=;"
+ "r0 = 0;"
+ "exit_%=: exit;"
+ :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
SEC("tc")
int kfunc_call_test4(struct __sk_buff *skb)
{
diff --git a/tools/testing/selftests/bpf/progs/kfunc_implicit_args.c b/tools/testing/selftests/bpf/progs/kfunc_implicit_args.c
new file mode 100644
index 000000000000..89b6a47e22dd
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kfunc_implicit_args.c
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+extern int bpf_kfunc_implicit_arg(int a) __weak __ksym;
+extern int bpf_kfunc_implicit_arg_impl(int a, struct bpf_prog_aux *aux) __weak __ksym; /* illegal */
+extern int bpf_kfunc_implicit_arg_legacy(int a, int b) __weak __ksym;
+extern int bpf_kfunc_implicit_arg_legacy_impl(int a, int b, struct bpf_prog_aux *aux) __weak __ksym;
+
+char _license[] SEC("license") = "GPL";
+
+SEC("syscall")
+__retval(5)
+int test_kfunc_implicit_arg(void *ctx)
+{
+ return bpf_kfunc_implicit_arg(5);
+}
+
+SEC("syscall")
+__failure __msg("cannot find address for kernel function bpf_kfunc_implicit_arg_impl")
+int test_kfunc_implicit_arg_impl_illegal(void *ctx)
+{
+ return bpf_kfunc_implicit_arg_impl(5, NULL);
+}
+
+SEC("syscall")
+__retval(7)
+int test_kfunc_implicit_arg_legacy(void *ctx)
+{
+ return bpf_kfunc_implicit_arg_legacy(3, 4);
+}
+
+SEC("syscall")
+__retval(11)
+int test_kfunc_implicit_arg_legacy_impl(void *ctx)
+{
+ return bpf_kfunc_implicit_arg_legacy_impl(5, 6, NULL);
+}
diff --git a/tools/testing/selftests/bpf/progs/kfunc_implicit_args_tracing.c b/tools/testing/selftests/bpf/progs/kfunc_implicit_args_tracing.c
new file mode 100644
index 000000000000..995f8b8b5b9e
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kfunc_implicit_args_tracing.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <errno.h>
+
+extern int bpf_kfunc_implicit_arg(int a) __weak __ksym;
+
+char _license[] SEC("license") = "GPL";
+
+/* Shared arg checks; reports arg count and aux, returns 1 on success. */
+static __always_inline __u64
+check_implicit_args(void *ctx, __u64 *arg_cnt, __u64 *aux_arg)
+{
+ __u64 a = 0, aux = 0, z = 0;
+ __u64 result;
+ __s64 err;
+
+ *arg_cnt = bpf_get_func_arg_cnt(ctx);
+ result = *arg_cnt == 2;
+
+ err = bpf_get_func_arg(ctx, 0, &a);
+ result &= err == 0 && (int)a == 5;
+
+ err = bpf_get_func_arg(ctx, 1, &aux);
+ *aux_arg = aux;
+ result &= err == 0 && aux != 0;
+
+ err = bpf_get_func_arg(ctx, 2, &z);
+ result &= err == -EINVAL;
+
+ return result;
+}
+
+__u64 fentry_result;
+__u64 fentry_arg_cnt;
+__u64 fentry_aux_arg;
+
+SEC("fentry/bpf_kfunc_implicit_arg")
+int BPF_PROG(trace_implicit_arg_fentry)
+{
+ __u64 ret = 0;
+ __s64 err;
+
+ fentry_result = check_implicit_args(ctx, &fentry_arg_cnt, &fentry_aux_arg);
+
+ err = bpf_get_func_ret(ctx, &ret);
+ fentry_result &= err == -EOPNOTSUPP;
+
+ return 0;
+}
+
+__u64 fexit_result;
+__u64 fexit_arg_cnt;
+__u64 fexit_aux_arg;
+
+SEC("fexit/bpf_kfunc_implicit_arg")
+int BPF_PROG(trace_implicit_arg_fexit)
+{
+ __u64 ret = 0;
+ __s64 err;
+
+ fexit_result = check_implicit_args(ctx, &fexit_arg_cnt, &fexit_aux_arg);
+
+ err = bpf_get_func_ret(ctx, &ret);
+ fexit_result &= err == 0 && ret == 5;
+
+ return 0;
+}
+
+SEC("syscall")
+int trigger_implicit_arg(void *ctx)
+{
+ return bpf_kfunc_implicit_arg(5);
+}
diff --git a/tools/testing/selftests/bpf/progs/kprobe_multi_override.c b/tools/testing/selftests/bpf/progs/kprobe_multi_override.c
index 28f8487c9059..14f39fa6d515 100644
--- a/tools/testing/selftests/bpf/progs/kprobe_multi_override.c
+++ b/tools/testing/selftests/bpf/progs/kprobe_multi_override.c
@@ -5,9 +5,24 @@
char _license[] SEC("license") = "GPL";
+int pid = 0;
+
SEC("kprobe.multi")
int test_override(struct pt_regs *ctx)
{
+ if (bpf_get_current_pid_tgid() >> 32 != pid)
+ return 0;
+
+ bpf_override_return(ctx, 123);
+ return 0;
+}
+
+SEC("kprobe")
+int test_kprobe_override(struct pt_regs *ctx)
+{
+ if (bpf_get_current_pid_tgid() >> 32 != pid)
+ return 0;
+
bpf_override_return(ctx, 123);
return 0;
}
diff --git a/tools/testing/selftests/bpf/progs/kprobe_multi_session.c b/tools/testing/selftests/bpf/progs/kprobe_multi_session.c
index bd8b7fb7061e..d52a65b40bbf 100644
--- a/tools/testing/selftests/bpf/progs/kprobe_multi_session.c
+++ b/tools/testing/selftests/bpf/progs/kprobe_multi_session.c
@@ -76,3 +76,13 @@ int test_kprobe(struct pt_regs *ctx)
{
return session_check(ctx);
}
+
+/*
+ * Exact function name (no wildcards) - exercises the fast syms[] path
+ * in bpf_program__attach_kprobe_multi_opts() which bypasses kallsyms parsing.
+ */
+SEC("kprobe.session/bpf_fentry_test1")
+int test_kprobe_syms(struct pt_regs *ctx)
+{
+ return session_check(ctx);
+}
diff --git a/tools/testing/selftests/bpf/progs/kprobe_multi_session_cookie.c b/tools/testing/selftests/bpf/progs/kprobe_multi_session_cookie.c
index 0835b5edf685..ad627016e3e5 100644
--- a/tools/testing/selftests/bpf/progs/kprobe_multi_session_cookie.c
+++ b/tools/testing/selftests/bpf/progs/kprobe_multi_session_cookie.c
@@ -1,9 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
-#include <linux/bpf.h>
+#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <stdbool.h>
-#include "bpf_kfuncs.h"
char _license[] SEC("license") = "GPL";
@@ -23,16 +22,16 @@ int BPF_PROG(trigger)
return 0;
}
-static int check_cookie(__u64 val, __u64 *result)
+static int check_cookie(struct pt_regs *ctx, __u64 val, __u64 *result)
{
__u64 *cookie;
if (bpf_get_current_pid_tgid() >> 32 != pid)
return 1;
- cookie = bpf_session_cookie();
+ cookie = bpf_session_cookie(ctx);
- if (bpf_session_is_return())
+ if (bpf_session_is_return(ctx))
*result = *cookie == val ? val : 0;
else
*cookie = val;
@@ -42,17 +41,17 @@ static int check_cookie(__u64 val, __u64 *result)
SEC("kprobe.session/bpf_fentry_test1")
int test_kprobe_1(struct pt_regs *ctx)
{
- return check_cookie(1, &test_kprobe_1_result);
+ return check_cookie(ctx, 1, &test_kprobe_1_result);
}
SEC("kprobe.session/bpf_fentry_test1")
int test_kprobe_2(struct pt_regs *ctx)
{
- return check_cookie(2, &test_kprobe_2_result);
+ return check_cookie(ctx, 2, &test_kprobe_2_result);
}
SEC("kprobe.session/bpf_fentry_test1")
int test_kprobe_3(struct pt_regs *ctx)
{
- return check_cookie(3, &test_kprobe_3_result);
+ return check_cookie(ctx, 3, &test_kprobe_3_result);
}
diff --git a/tools/testing/selftests/bpf/progs/kprobe_multi_sleepable.c b/tools/testing/selftests/bpf/progs/kprobe_multi_sleepable.c
new file mode 100644
index 000000000000..932e1d9c72e2
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kprobe_multi_sleepable.c
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+void *user_ptr = 0;
+
+SEC("kprobe.multi")
+int handle_kprobe_multi_sleepable(struct pt_regs *ctx)
+{
+ int a, err;
+
+ err = bpf_copy_from_user(&a, sizeof(a), user_ptr);
+ barrier_var(a);
+ return err;
+}
+
+SEC("fentry/bpf_fentry_test1")
+int BPF_PROG(fentry)
+{
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/kprobe_write_ctx.c b/tools/testing/selftests/bpf/progs/kprobe_write_ctx.c
index f77aef0474d3..adbf52afe490 100644
--- a/tools/testing/selftests/bpf/progs/kprobe_write_ctx.c
+++ b/tools/testing/selftests/bpf/progs/kprobe_write_ctx.c
@@ -19,4 +19,23 @@ int kprobe_multi_write_ctx(struct pt_regs *ctx)
ctx->ax = 0;
return 0;
}
+
+SEC("?kprobe")
+int kprobe_dummy(struct pt_regs *regs)
+{
+ return 0;
+}
+
+SEC("?freplace")
+int freplace_kprobe(struct pt_regs *regs)
+{
+ regs->di = 0;
+ return 0;
+}
+
+SEC("?fentry/bpf_fentry_test1")
+int BPF_PROG(fentry)
+{
+ return 0;
+}
#endif
diff --git a/tools/testing/selftests/bpf/progs/kptr_xchg_inline.c b/tools/testing/selftests/bpf/progs/kptr_xchg_inline.c
index 2414ac20b6d5..ca5943166057 100644
--- a/tools/testing/selftests/bpf/progs/kptr_xchg_inline.c
+++ b/tools/testing/selftests/bpf/progs/kptr_xchg_inline.c
@@ -25,14 +25,14 @@ __naked int kptr_xchg_inline(void)
"if r0 == 0 goto 1f;"
"r1 = r0;"
"r2 = 0;"
- "call %[bpf_obj_drop_impl];"
+ "call %[bpf_obj_drop];"
"1:"
"r0 = 0;"
"exit;"
:
: __imm_addr(ptr),
__imm(bpf_kptr_xchg),
- __imm(bpf_obj_drop_impl)
+ __imm(bpf_obj_drop)
: __clobber_all
);
}
diff --git a/tools/testing/selftests/bpf/progs/ksock_common.h b/tools/testing/selftests/bpf/progs/ksock_common.h
new file mode 100644
index 000000000000..01edaeb9fdd4
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/ksock_common.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2026 Isovalent */
+
+#ifndef _KSOCK_COMMON_H
+#define _KSOCK_COMMON_H
+
+#include "errno.h"
+
+#define SOCK_DGRAM 2
+#define IPPROTO_UDP 17
+
+struct bpf_ksock *bpf_ksock_create(const struct bpf_ksock_create_opts *opts,
+ u32 opts__sz, int *err__uninit) __ksym;
+int bpf_ksock_connect(struct bpf_ksock *ks, const union bpf_ksock_addr *addr,
+ u32 addr__sz) __ksym;
+struct bpf_ksock *bpf_ksock_acquire(struct bpf_ksock *ks) __ksym;
+void bpf_ksock_release(struct bpf_ksock *ks) __ksym;
+int bpf_ksock_send(struct bpf_ksock *ks, const void *data, u32 data__sz) __ksym;
+void bpf_rcu_read_lock(void) __ksym;
+void bpf_rcu_read_unlock(void) __ksym;
+
+struct __ksock_ctx_value {
+ struct bpf_ksock __kptr * ctx;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, int);
+ __type(value, struct __ksock_ctx_value);
+ __uint(max_entries, 1);
+} __ksock_ctx_map SEC(".maps");
+
+static inline struct __ksock_ctx_value *ksock_ctx_value_lookup(void)
+{
+ u32 key = 0;
+
+ return bpf_map_lookup_elem(&__ksock_ctx_map, &key);
+}
+
+static inline struct bpf_ksock *ksock_ctx_get(void)
+{
+ struct __ksock_ctx_value *v;
+ struct bpf_ksock *ks = NULL, *tmp;
+
+ v = ksock_ctx_value_lookup();
+ if (!v)
+ return NULL;
+
+ bpf_rcu_read_lock();
+ tmp = v->ctx;
+ if (tmp)
+ ks = bpf_ksock_acquire(tmp);
+ bpf_rcu_read_unlock();
+
+ return ks;
+}
+
+static inline int ksock_ctx_insert(struct bpf_ksock *ctx)
+{
+ struct __ksock_ctx_value *v;
+ struct bpf_ksock *old;
+
+ v = ksock_ctx_value_lookup();
+ if (!v) {
+ bpf_ksock_release(ctx);
+ return -ENOENT;
+ }
+
+ old = bpf_kptr_xchg(&v->ctx, ctx);
+ if (old) {
+ bpf_ksock_release(old);
+ return -EEXIST;
+ }
+
+ return 0;
+}
+
+#endif /* _KSOCK_COMMON_H */
diff --git a/tools/testing/selftests/bpf/progs/ksock_lsm.c b/tools/testing/selftests/bpf/progs/ksock_lsm.c
new file mode 100644
index 000000000000..9808451098ef
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/ksock_lsm.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Isovalent */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_endian.h>
+#include "bpf_tracing_net.h"
+#include "ksock_common.h"
+
+char send_data[32] = "hello from bpf ksock";
+
+__be32 ipv4_remote;
+__u16 remote_port;
+int target_pid;
+int send_ret = -1;
+
+SEC("syscall")
+int ksock_setup(void *ctx)
+{
+ struct bpf_ksock_create_opts create_opts = {};
+ union bpf_ksock_addr addr = {};
+ struct bpf_ksock *ks;
+ int err = 0;
+
+ create_opts.family = AF_INET;
+ create_opts.type = SOCK_DGRAM;
+ create_opts.protocol = IPPROTO_UDP;
+
+ ks = bpf_ksock_create(&create_opts, sizeof(create_opts), &err);
+ if (!ks)
+ return err;
+
+ addr.sin.sin_family = AF_INET;
+ addr.sin.sin_port = bpf_htons(remote_port);
+ addr.sin.sin_addr.s_addr = ipv4_remote;
+
+ err = bpf_ksock_connect(ks, &addr, sizeof(addr));
+ if (err) {
+ bpf_ksock_release(ks);
+ return err;
+ }
+
+ err = ksock_ctx_insert(ks);
+ if (err && err != -EEXIST)
+ return err;
+ return 0;
+}
+
+SEC("lsm.s/socket_bind")
+int BPF_PROG(ksock_socket_bind, struct socket *sock, struct sockaddr *address,
+ int addrlen, int ret)
+{
+ struct bpf_ksock *ks;
+ u32 pid = bpf_get_current_pid_tgid() >> 32;
+
+ if (ret || pid != target_pid)
+ return ret;
+
+ ks = ksock_ctx_get();
+ if (!ks) {
+ send_ret = -ENOENT;
+ return ret;
+ }
+
+ send_ret = bpf_ksock_send(ks, send_data, sizeof(send_data));
+ bpf_ksock_release(ks);
+
+ return ret;
+}
+
+char __license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/ksock_lsm_verifier.c b/tools/testing/selftests/bpf/progs/ksock_lsm_verifier.c
new file mode 100644
index 000000000000..fd2ccfdb5802
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/ksock_lsm_verifier.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Isovalent */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+#include "ksock_common.h"
+
+char send_data[11] = "dummy data";
+
+SEC("lsm.s/socket_sendmsg")
+__description("bpf_ksock_send is rejected from socket_sendmsg LSM hook")
+__failure __msg("calling kernel function bpf_ksock_send is not allowed")
+int BPF_PROG(ksock_socket_sendmsg, struct socket *sock, struct msghdr *msg,
+ int size, int ret)
+{
+ struct __ksock_ctx_value *v;
+ struct bpf_ksock *ks;
+
+ v = ksock_ctx_value_lookup();
+ if (!v)
+ return ret;
+
+ ks = bpf_kptr_xchg(&v->ctx, NULL);
+ if (!ks)
+ return ret;
+
+ bpf_ksock_send(ks, send_data, sizeof(send_data));
+ bpf_ksock_release(ks);
+
+ return ret;
+}
+
+char __license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/ksock_wq.c b/tools/testing/selftests/bpf/progs/ksock_wq.c
new file mode 100644
index 000000000000..16a1873d132e
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/ksock_wq.c
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Isovalent */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include "bpf_experimental.h"
+#include "bpf_tracing_net.h"
+#include "errno.h"
+#include "ksock_common.h"
+
+struct ksock_wq_value {
+ struct bpf_wq work;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, u32);
+ __type(value, struct ksock_wq_value);
+} work_map SEC(".maps");
+
+int create_err;
+u32 callback_done;
+
+static int ksock_wq_callback(void *map, int *key, void *value)
+{
+ struct bpf_ksock_create_opts opts = {
+ .family = AF_INET,
+ .type = SOCK_DGRAM,
+ .protocol = IPPROTO_UDP,
+ };
+ struct bpf_ksock *ks;
+ int err = 0;
+
+ ks = bpf_ksock_create(&opts, sizeof(opts), &err);
+ if (ks)
+ bpf_ksock_release(ks);
+ create_err = err;
+ __sync_fetch_and_add(&callback_done, 1);
+ return 0;
+}
+
+SEC("syscall")
+int ksock_wq_start(void *ctx)
+{
+ struct ksock_wq_value *value;
+ u32 key = 0;
+ int err;
+
+ value = bpf_map_lookup_elem(&work_map, &key);
+ if (!value)
+ return -ENOENT;
+ err = bpf_wq_init(&value->work, &work_map, 0);
+ if (err)
+ return err;
+ err = bpf_wq_set_callback(&value->work, ksock_wq_callback, 0);
+ if (err)
+ return err;
+ return bpf_wq_start(&value->work, 0);
+}
+
+char __license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/linked_list.c b/tools/testing/selftests/bpf/progs/linked_list.c
index 421f40835acd..fa97faa5358b 100644
--- a/tools/testing/selftests/bpf/progs/linked_list.c
+++ b/tools/testing/selftests/bpf/progs/linked_list.c
@@ -290,6 +290,77 @@ int test_list_in_list(struct bpf_spin_lock *lock, struct bpf_list_head *head)
return list_in_list(lock, head, true);
}
+#define MAX_LIST_CLEAR_NODES 256
+
+static __always_inline
+int clear_list(struct bpf_spin_lock *lock, struct bpf_list_head *head)
+{
+ struct bpf_list_node *n;
+ int i;
+
+ for (i = 0; i < MAX_LIST_CLEAR_NODES; i++) {
+ bpf_spin_lock(lock);
+ n = bpf_list_pop_front(head);
+ bpf_spin_unlock(lock);
+ if (!n)
+ return 0;
+ bpf_obj_drop(container_of(n, struct foo, node2));
+ }
+ return 1;
+}
+
+SEC("syscall")
+int clear_map_list(void *ctx)
+{
+ struct map_value *v;
+
+ v = bpf_map_lookup_elem(&array_map, &(int){0});
+ if (!v)
+ return 1;
+ return clear_list(&v->lock, &v->head);
+}
+
+SEC("syscall")
+int clear_inner_map_list(void *ctx)
+{
+ struct map_value *v;
+ void *map;
+
+ map = bpf_map_lookup_elem(&map_of_maps, &(int){0});
+ if (!map)
+ return 1;
+ v = bpf_map_lookup_elem(map, &(int){0});
+ if (!v)
+ return 1;
+ return clear_list(&v->lock, &v->head);
+}
+
+SEC("syscall")
+int clear_global_list(void *ctx)
+{
+ return clear_list(&glock, &ghead);
+}
+
+SEC("syscall")
+int clear_global_nested_list(void *ctx)
+{
+ return clear_list(&ghead_nested.inner.lock, &ghead_nested.inner.head);
+}
+
+SEC("syscall")
+int clear_global_array_list(void *ctx)
+{
+ int ret;
+
+ ret = clear_list(&glock_c, &ghead_array[0]);
+ if (ret)
+ return ret;
+ ret = clear_list(&glock_c, &ghead_array[1]);
+ if (ret)
+ return ret;
+ return clear_list(&glock_c, &ghead_array_one[0]);
+}
+
SEC("tc")
int map_list_push_pop(void *ctx)
{
diff --git a/tools/testing/selftests/bpf/progs/linked_list_fail.c b/tools/testing/selftests/bpf/progs/linked_list_fail.c
index ddd26d1a083f..031e77a288ee 100644
--- a/tools/testing/selftests/bpf/progs/linked_list_fail.c
+++ b/tools/testing/selftests/bpf/progs/linked_list_fail.c
@@ -167,6 +167,16 @@ CHECK_OP(push_back);
#undef CHECK_OP
#undef INIT
+struct obj_new_flex_elem {
+ int lo;
+ int hi;
+};
+
+struct obj_new_flex {
+ int hdr;
+ struct obj_new_flex_elem cells[];
+};
+
SEC("?kprobe/xyz")
int map_compat_kprobe(void *ctx)
{
@@ -231,6 +241,19 @@ int obj_new_no_struct(void *ctx)
}
SEC("?tc")
+int obj_new_flex_array(void *ctx)
+{
+ struct obj_new_flex *p;
+
+ p = bpf_obj_new_impl(bpf_core_type_id_local(struct obj_new_flex), NULL);
+ if (!p)
+ return 0;
+ p->cells[0].hi = 42;
+ bpf_obj_drop_impl(p, NULL);
+ return 0;
+}
+
+SEC("?tc")
int obj_drop_non_zero_off(void *ctx)
{
void *f;
diff --git a/tools/testing/selftests/bpf/progs/local_storage.c b/tools/testing/selftests/bpf/progs/local_storage.c
index 637e75df2e14..d0be77011a84 100644
--- a/tools/testing/selftests/bpf/progs/local_storage.c
+++ b/tools/testing/selftests/bpf/progs/local_storage.c
@@ -62,7 +62,6 @@ SEC("lsm/inode_unlink")
int BPF_PROG(unlink_hook, struct inode *dir, struct dentry *victim)
{
__u32 pid = bpf_get_current_pid_tgid() >> 32;
- struct bpf_local_storage *local_storage;
struct local_storage *storage;
struct task_struct *task;
bool is_self_unlink;
@@ -88,15 +87,10 @@ int BPF_PROG(unlink_hook, struct inode *dir, struct dentry *victim)
if (!storage || storage->value)
return 0;
- if (bpf_task_storage_delete(&task_storage_map, task))
+ if (bpf_task_storage_delete(&task_storage_map2, task))
return 0;
- /* Ensure that the task_storage_map is disconnected from the storage.
- * The storage memory should not be freed back to the
- * bpf_mem_alloc.
- */
- local_storage = task->bpf_storage;
- if (!local_storage || local_storage->smap)
+ if (bpf_task_storage_delete(&task_storage_map, task))
return 0;
task_storage_result = 0;
@@ -164,18 +158,9 @@ int BPF_PROG(socket_bind, struct socket *sock, struct sockaddr *address,
if (bpf_sk_storage_delete(&sk_storage_map2, sk))
return 0;
- storage = bpf_sk_storage_get(&sk_storage_map2, sk, 0,
- BPF_LOCAL_STORAGE_GET_F_CREATE);
- if (!storage)
- return 0;
-
if (bpf_sk_storage_delete(&sk_storage_map, sk))
return 0;
- /* Ensure that the sk_storage_map is disconnected from the storage. */
- if (!sk->sk_bpf_storage || sk->sk_bpf_storage->smap)
- return 0;
-
sk_storage_result = 0;
return 0;
}
diff --git a/tools/testing/selftests/bpf/progs/lpm_trie_bench.c b/tools/testing/selftests/bpf/progs/lpm_trie_bench.c
index a0e6ebd5507a..2831cf4445e8 100644
--- a/tools/testing/selftests/bpf/progs/lpm_trie_bench.c
+++ b/tools/testing/selftests/bpf/progs/lpm_trie_bench.c
@@ -7,7 +7,7 @@
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
#include "bpf_misc.h"
-#include "bpf_atomic.h"
+#include <bpf_atomic.h>
#include "progs/lpm_trie.h"
#define BPF_OBJ_NAME_LEN 16U
diff --git a/tools/testing/selftests/bpf/progs/lru_lock_nmi.c b/tools/testing/selftests/bpf/progs/lru_lock_nmi.c
new file mode 100644
index 000000000000..c0692cd54237
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/lru_lock_nmi.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+struct {
+ __uint(type, BPF_MAP_TYPE_LRU_HASH);
+ __uint(max_entries, 64);
+ __type(key, __u32);
+ __type(value, __u64);
+} lru_map SEC(".maps");
+
+int hits;
+
+SEC("perf_event")
+int oncpu(void *ctx)
+{
+ /*
+ * Key range deliberately wider than max_entries to force LRU
+ * eviction on every other update.
+ */
+ __u32 key = bpf_get_prandom_u32() % 128;
+ bool do_update = bpf_get_prandom_u32() & 1;
+ __u64 val = 1;
+
+ if (do_update)
+ bpf_map_update_elem(&lru_map, &key, &val, BPF_ANY);
+ else
+ bpf_map_delete_elem(&lru_map, &key);
+ __sync_fetch_and_add(&hits, 1);
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/lsm_bdev.c b/tools/testing/selftests/bpf/progs/lsm_bdev.c
new file mode 100644
index 000000000000..45554e6db605
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/lsm_bdev.c
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Christian Brauner <brauner@kernel.org> */
+
+/*
+ * BPF LSM block device integrity tracker for dm-verity.
+ *
+ * Tracks block devices in a hashmap keyed by bd_dev. When dm-verity
+ * calls security_bdev_setintegrity() during verity_preresume(), the
+ * setintegrity hook records the roothash and signature-validity data.
+ * The free hook cleans up when the device goes away. The alloc hook
+ * counts allocations for test validation.
+ *
+ * The sleepable hooks exercise bpf_copy_from_user() to verify that
+ * the sleepable classification actually permits sleepable helpers.
+ */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+struct verity_info {
+ __u8 has_roothash; /* LSM_INT_DMVERITY_ROOTHASH seen */
+ __u8 sig_valid; /* LSM_INT_DMVERITY_SIG_VALID value (non-NULL = valid) */
+ __u32 setintegrity_cnt; /* total setintegrity calls for this dev */
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(max_entries, 64);
+ __type(key, __u32); /* dev_t from bdev->bd_dev */
+ __type(value, struct verity_info);
+} verity_devices SEC(".maps");
+
+/* Global counters exposed to userspace via skeleton bss. */
+int alloc_count;
+
+char _license[] SEC("license") = "GPL";
+
+SEC("lsm.s/bdev_setintegrity")
+int BPF_PROG(bdev_setintegrity, struct block_device *bdev,
+ enum lsm_integrity_type type, const void *value, size_t size)
+{
+ struct verity_info zero = {};
+ struct verity_info *info;
+ __u32 dev;
+ char buf;
+
+ /*
+ * Exercise a sleepable helper to confirm the verifier
+ * allows it in this sleepable hook.
+ */
+ (void)bpf_copy_from_user(&buf, sizeof(buf), NULL);
+
+ dev = bdev->bd_dev;
+
+ info = bpf_map_lookup_elem(&verity_devices, &dev);
+ if (!info) {
+ bpf_map_update_elem(&verity_devices, &dev, &zero, BPF_NOEXIST);
+ info = bpf_map_lookup_elem(&verity_devices, &dev);
+ if (!info)
+ return 0;
+ }
+
+ if (type == LSM_INT_DMVERITY_ROOTHASH)
+ info->has_roothash = 1;
+ else if (type == LSM_INT_DMVERITY_SIG_VALID)
+ info->sig_valid = (value != NULL);
+
+ __sync_fetch_and_add(&info->setintegrity_cnt, 1);
+
+ return 0;
+}
+
+SEC("lsm/bdev_free_security")
+void BPF_PROG(bdev_free_security, struct block_device *bdev)
+{
+ __u32 dev = bdev->bd_dev;
+
+ bpf_map_delete_elem(&verity_devices, &dev);
+}
+
+SEC("lsm.s/bdev_alloc_security")
+int BPF_PROG(bdev_alloc_security, struct block_device *bdev)
+{
+ char buf;
+
+ /*
+ * Exercise a sleepable helper to confirm the verifier
+ * allows it in this sleepable hook.
+ */
+ (void)bpf_copy_from_user(&buf, sizeof(buf), NULL);
+
+ __sync_fetch_and_add(&alloc_count, 1);
+
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/lsm_cgroup.c b/tools/testing/selftests/bpf/progs/lsm_cgroup.c
index d7598538aa2d..3bfa479104be 100644
--- a/tools/testing/selftests/bpf/progs/lsm_cgroup.c
+++ b/tools/testing/selftests/bpf/progs/lsm_cgroup.c
@@ -35,6 +35,8 @@ int called_socket_bind;
int called_socket_bind2;
int called_socket_alloc;
int called_socket_clone;
+int skipcap_retval = -4095;
+int socket_retval = -4095;
static __always_inline int test_local_storage(void)
{
@@ -190,3 +192,31 @@ int BPF_PROG(socket_clone, struct sock *newsk, const struct request_sock *req)
return 1;
}
+
+SEC("lsm_cgroup/inode_xattr_skipcap")
+int BPF_PROG(skipcap_first, const char *name)
+{
+ return 0;
+}
+
+SEC("lsm_cgroup/inode_xattr_skipcap")
+int BPF_PROG(skipcap_second, const char *name)
+{
+ skipcap_retval = bpf_get_retval();
+ bpf_set_retval(0);
+ return 1;
+}
+
+SEC("lsm_cgroup/socket_create")
+int BPF_PROG(socket_first, int family, int type, int protocol, int kern)
+{
+ return 0;
+}
+
+SEC("lsm_cgroup/socket_create")
+int BPF_PROG(socket_second, int family, int type, int protocol, int kern)
+{
+ socket_retval = bpf_get_retval();
+ bpf_set_retval(0);
+ return 1;
+}
diff --git a/tools/testing/selftests/bpf/progs/lwt_misc.c b/tools/testing/selftests/bpf/progs/lwt_misc.c
new file mode 100644
index 000000000000..b392317088d2
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/lwt_misc.c
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+SEC("lwt_xmit")
+__success __retval(0)
+int test_missing_dst(struct __sk_buff *skb)
+{
+ struct iphdr iph;
+
+ __builtin_memset(&iph, 0, sizeof(struct iphdr));
+ iph.ihl = 5;
+ iph.version = 4;
+
+ bpf_lwt_push_encap(skb, BPF_LWT_ENCAP_IP, &iph, sizeof(struct iphdr));
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/map_kptr.c b/tools/testing/selftests/bpf/progs/map_kptr.c
index edaba481db9d..0d87c97dac99 100644
--- a/tools/testing/selftests/bpf/progs/map_kptr.c
+++ b/tools/testing/selftests/bpf/progs/map_kptr.c
@@ -4,6 +4,18 @@
#include <bpf/bpf_helpers.h>
#include "../test_kmods/bpf_testmod_kfunc.h"
+struct map_uninit_value {
+ struct prog_test_ref_kfunc __kptr_untrusted *unref_ptr;
+ __u32 data;
+} __attribute__((packed));
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
+ __type(key, int);
+ __type(value, struct map_uninit_value);
+ __uint(max_entries, 1);
+} pcpu_array SEC(".maps");
+
struct map_value {
struct prog_test_ref_kfunc __kptr_untrusted *unref_ptr;
struct prog_test_ref_kfunc __kptr *ref_ptr;
@@ -487,6 +499,107 @@ int test_map_kptr_ref3(struct __sk_buff *ctx)
return 0;
}
+int num_of_refs;
+
+static __always_inline int read_ref_count(void)
+{
+ struct prog_test_ref_kfunc *p;
+ unsigned long arg = 0;
+
+ p = bpf_kfunc_call_test_acquire(&arg);
+ if (!p)
+ return 1;
+
+ num_of_refs = p->cnt.refs.counter;
+ bpf_kfunc_call_test_release(p);
+ return 0;
+}
+
+SEC("syscall")
+int count_ref(void *ctx)
+{
+ return read_ref_count();
+}
+
+static __always_inline int stash_ref_ptr(struct map_value *v)
+{
+ struct prog_test_ref_kfunc *p, *old;
+ unsigned long arg = 0;
+
+ p = bpf_kfunc_call_test_acquire(&arg);
+ if (!p)
+ return 1;
+
+ old = bpf_kptr_xchg(&v->ref_ptr, p);
+ if (old) {
+ bpf_kfunc_call_test_release(old);
+ old = bpf_kptr_xchg(&v->ref_ptr, NULL);
+ if (old)
+ bpf_kfunc_call_test_release(old);
+ return 2;
+ }
+ return 0;
+}
+
+static __always_inline int check_refs(int expected)
+{
+ int ret;
+
+ ret = read_ref_count();
+ if (ret)
+ return ret;
+ return num_of_refs == expected ? 0 : 3;
+}
+
+SEC("syscall")
+int test_array_map_update_kptr(void *ctx)
+{
+ struct map_value init = {}, *v;
+ int key = 0, ret;
+
+ v = bpf_map_lookup_elem(&array_map, &key);
+ if (!v)
+ return 1;
+ ret = stash_ref_ptr(v);
+ if (ret)
+ return ret;
+ ret = check_refs(3);
+ if (ret)
+ return ret;
+ ret = bpf_map_update_elem(&array_map, &key, &init, BPF_EXIST);
+ if (ret)
+ return 4;
+ return check_refs(3);
+}
+
+#define DEFINE_HASH_UPDATE_KPTR_TEST(name, map) \
+SEC("syscall") \
+int name(void *ctx) \
+{ \
+ struct map_value init = {}, *v; \
+ int key = 0, ret; \
+ \
+ ret = bpf_map_update_elem(&map, &key, &init, BPF_NOEXIST); \
+ if (ret) \
+ return 1; \
+ v = bpf_map_lookup_elem(&map, &key); \
+ if (!v) \
+ return 2; \
+ ret = stash_ref_ptr(v); \
+ if (ret) \
+ return ret; \
+ ret = check_refs(3); \
+ if (ret) \
+ return ret; \
+ ret = bpf_map_update_elem(&map, &key, &init, BPF_EXIST); \
+ if (ret) \
+ return 4; \
+ return check_refs(3); \
+}
+
+DEFINE_HASH_UPDATE_KPTR_TEST(test_hash_map_update_kptr, hash_map)
+DEFINE_HASH_UPDATE_KPTR_TEST(test_hash_malloc_map_update_kptr, hash_malloc_map)
+
SEC("syscall")
int test_ls_map_kptr_ref1(void *ctx)
{
diff --git a/tools/testing/selftests/bpf/progs/map_kptr_fail.c b/tools/testing/selftests/bpf/progs/map_kptr_fail.c
index 4c0ff01f1a96..eee35d203b66 100644
--- a/tools/testing/selftests/bpf/progs/map_kptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/map_kptr_fail.c
@@ -252,7 +252,7 @@ int reject_untrusted_store_to_ref(struct __sk_buff *ctx)
}
SEC("?tc")
-__failure __msg("R2 must be referenced")
+__failure __msg("release helper bpf_kptr_xchg expects referenced PTR_TO_BTF_ID passed to R2")
int reject_untrusted_xchg(struct __sk_buff *ctx)
{
struct prog_test_ref_kfunc *p;
@@ -272,7 +272,7 @@ int reject_untrusted_xchg(struct __sk_buff *ctx)
SEC("?tc")
__failure
-__msg("invalid kptr access, R2 type=ptr_prog_test_ref_kfunc expected=ptr_prog_test_member")
+__msg("invalid kptr access, R2 type=trusted_ptr_prog_test_ref_kfunc expected=ptr_prog_test_member")
int reject_bad_type_xchg(struct __sk_buff *ctx)
{
struct prog_test_ref_kfunc *ref_ptr;
@@ -291,7 +291,7 @@ int reject_bad_type_xchg(struct __sk_buff *ctx)
}
SEC("?tc")
-__failure __msg("invalid kptr access, R2 type=ptr_prog_test_ref_kfunc")
+__failure __msg("invalid kptr access, R2 type=trusted_ptr_prog_test_ref_kfunc")
int reject_member_of_ref_xchg(struct __sk_buff *ctx)
{
struct prog_test_ref_kfunc *ref_ptr;
@@ -364,7 +364,7 @@ int kptr_xchg_ref_state(struct __sk_buff *ctx)
}
SEC("?tc")
-__failure __msg("Possibly NULL pointer passed to helper arg2")
+__failure __msg("Possibly NULL pointer passed to helper R2")
int kptr_xchg_possibly_null(struct __sk_buff *ctx)
{
struct prog_test_ref_kfunc *p;
@@ -385,4 +385,65 @@ int kptr_xchg_possibly_null(struct __sk_buff *ctx)
return 0;
}
+SEC("?tc")
+/*
+ * A compiler with BPF_ST folds the constant into a store-immediate, which the
+ * verifier rejects on a different path (and with a different message) than the
+ * BPF_STX form.
+ */
+#ifdef __BPF_FEATURE_ST
+__failure __msg("BPF_ST imm must be 0 when storing to kptr at off=8")
+#else
+__failure __msg("invalid kptr access, R")
+#endif
+int reject_scalar_store_to_kptr(struct __sk_buff *ctx)
+{
+ struct map_value *v;
+ int key = 0;
+
+ v = bpf_map_lookup_elem(&array_map, &key);
+ if (!v)
+ return 0;
+
+ *(volatile u64 *)&v->unref_ptr = 0xBADC0DE;
+ 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/map_kptr_race.c b/tools/testing/selftests/bpf/progs/map_kptr_race.c
new file mode 100644
index 000000000000..f6f136cd8f60
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/map_kptr_race.c
@@ -0,0 +1,197 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+struct map_value {
+ struct prog_test_ref_kfunc __kptr *ref_ptr;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(map_flags, BPF_F_NO_PREALLOC);
+ __type(key, int);
+ __type(value, struct map_value);
+ __uint(max_entries, 1);
+} race_hash_map SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_HASH);
+ __uint(map_flags, BPF_F_NO_PREALLOC);
+ __type(key, int);
+ __type(value, struct map_value);
+ __uint(max_entries, 1);
+} race_percpu_hash_map SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_SK_STORAGE);
+ __uint(map_flags, BPF_F_NO_PREALLOC);
+ __type(key, int);
+ __type(value, struct map_value);
+} race_sk_ls_map SEC(".maps");
+
+int num_of_refs;
+int sk_ls_leak_done;
+int target_map_id;
+int map_freed;
+const volatile int nr_cpus;
+
+SEC("tc")
+int test_htab_leak(struct __sk_buff *skb)
+{
+ struct prog_test_ref_kfunc *p, *old;
+ struct map_value val = {};
+ struct map_value *v;
+ int key = 0;
+
+ if (bpf_map_update_elem(&race_hash_map, &key, &val, BPF_ANY))
+ return 1;
+
+ v = bpf_map_lookup_elem(&race_hash_map, &key);
+ if (!v)
+ return 2;
+
+ p = bpf_kfunc_call_test_acquire(&(unsigned long){0});
+ if (!p)
+ return 3;
+ old = bpf_kptr_xchg(&v->ref_ptr, p);
+ if (old)
+ bpf_kfunc_call_test_release(old);
+
+ bpf_map_delete_elem(&race_hash_map, &key);
+
+ p = bpf_kfunc_call_test_acquire(&(unsigned long){0});
+ if (!p)
+ return 4;
+ old = bpf_kptr_xchg(&v->ref_ptr, p);
+ if (old)
+ bpf_kfunc_call_test_release(old);
+
+ return 0;
+}
+
+static int fill_percpu_kptr(struct map_value *v)
+{
+ struct prog_test_ref_kfunc *p, *old;
+
+ p = bpf_kfunc_call_test_acquire(&(unsigned long){0});
+ if (!p)
+ return 1;
+ old = bpf_kptr_xchg(&v->ref_ptr, p);
+ if (old)
+ bpf_kfunc_call_test_release(old);
+ return 0;
+}
+
+SEC("tc")
+int test_percpu_htab_leak(struct __sk_buff *skb)
+{
+ struct map_value *v, *arr[16] = {};
+ struct map_value val = {};
+ int key = 0;
+ int err = 0;
+
+ if (bpf_map_update_elem(&race_percpu_hash_map, &key, &val, BPF_ANY))
+ return 1;
+
+ for (int i = 0; i < nr_cpus; i++) {
+ v = bpf_map_lookup_percpu_elem(&race_percpu_hash_map, &key, i);
+ if (!v)
+ return 2;
+ arr[i] = v;
+ }
+
+ bpf_map_delete_elem(&race_percpu_hash_map, &key);
+
+ for (int i = 0; i < nr_cpus; i++) {
+ v = arr[i];
+ err = fill_percpu_kptr(v);
+ if (err)
+ return 3;
+ }
+
+ return 0;
+}
+
+SEC("tp_btf/inet_sock_set_state")
+int BPF_PROG(test_sk_ls_leak, struct sock *sk, int oldstate, int newstate)
+{
+ struct prog_test_ref_kfunc *p, *old;
+ struct map_value *v;
+
+ if (newstate != BPF_TCP_SYN_SENT)
+ return 0;
+
+ if (sk_ls_leak_done)
+ return 0;
+
+ v = bpf_sk_storage_get(&race_sk_ls_map, sk, NULL,
+ BPF_SK_STORAGE_GET_F_CREATE);
+ if (!v)
+ return 0;
+
+ p = bpf_kfunc_call_test_acquire(&(unsigned long){0});
+ if (!p)
+ return 0;
+ old = bpf_kptr_xchg(&v->ref_ptr, p);
+ if (old)
+ bpf_kfunc_call_test_release(old);
+
+ bpf_sk_storage_delete(&race_sk_ls_map, sk);
+
+ p = bpf_kfunc_call_test_acquire(&(unsigned long){0});
+ if (!p)
+ return 0;
+ old = bpf_kptr_xchg(&v->ref_ptr, p);
+ if (old)
+ bpf_kfunc_call_test_release(old);
+
+ sk_ls_leak_done = 1;
+ return 0;
+}
+
+long target_map_ptr;
+
+SEC("fentry/bpf_map_put")
+int BPF_PROG(map_put, struct bpf_map *map)
+{
+ if (target_map_id && map->id == (u32)target_map_id)
+ target_map_ptr = (long)map;
+ return 0;
+}
+
+SEC("fexit/htab_map_free")
+int BPF_PROG(htab_map_free, struct bpf_map *map)
+{
+ if (target_map_ptr && (long)map == target_map_ptr)
+ map_freed = 1;
+ return 0;
+}
+
+SEC("fexit/bpf_sk_storage_map_free")
+int BPF_PROG(sk_map_free, struct bpf_map *map)
+{
+ if (target_map_ptr && (long)map == target_map_ptr)
+ map_freed = 1;
+ return 0;
+}
+
+SEC("syscall")
+int count_ref(void *ctx)
+{
+ struct prog_test_ref_kfunc *p;
+ unsigned long arg = 0;
+
+ p = bpf_kfunc_call_test_acquire(&arg);
+ if (!p)
+ return 1;
+
+ num_of_refs = p->cnt.refs.counter;
+
+ bpf_kfunc_call_test_release(p);
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/map_ptr_kern.c b/tools/testing/selftests/bpf/progs/map_ptr_kern.c
index efaf622c28dd..373c8d17ea55 100644
--- a/tools/testing/selftests/bpf/progs/map_ptr_kern.c
+++ b/tools/testing/selftests/bpf/progs/map_ptr_kern.c
@@ -647,8 +647,14 @@ static inline int check_devmap_hash(void)
return 1;
}
+struct bpf_ringbuf {
+ unsigned long consumer_pos;
+ unsigned long producer_pos;
+} __attribute__((preserve_access_index));
+
struct bpf_ringbuf_map {
struct bpf_map map;
+ struct bpf_ringbuf *rb;
} __attribute__((preserve_access_index));
struct {
@@ -659,9 +665,20 @@ static inline int check_ringbuf(void)
{
struct bpf_ringbuf_map *ringbuf = (struct bpf_ringbuf_map *)&m_ringbuf;
struct bpf_map *map = (struct bpf_map *)&m_ringbuf;
+ struct bpf_ringbuf *rb;
+ void *ptr;
VERIFY(check(&ringbuf->map, map, 0, 0, page_size));
+ ptr = bpf_ringbuf_reserve(&m_ringbuf, 128, 0);
+ VERIFY(ptr);
+
+ bpf_ringbuf_discard(ptr, 0);
+ rb = ringbuf->rb;
+ VERIFY(rb);
+ VERIFY(rb->consumer_pos == 0);
+ VERIFY(rb->producer_pos == 128 + BPF_RINGBUF_HDR_SZ);
+
return 1;
}
diff --git a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c
index 3b984b6ae7c0..3e0d4f687aaa 100644
--- a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c
+++ b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c
@@ -3,12 +3,13 @@
#include <vmlinux.h>
#include <bpf/bpf_core_read.h>
#include "bpf_misc.h"
+#include "bpf_kfuncs.h"
#include "../test_kmods/bpf_testmod_kfunc.h"
SEC("tp_btf/sys_enter")
__success
__log_level(2)
-__msg("r8 = *(u64 *)(r7 +0) ; R7=ptr_nameidata(off={{[0-9]+}}) R8=rdonly_untrusted_mem(sz=0)")
+__msg("r8 = *(u64 *)(r7 +0) ; R7=ptr_nameidata(imm={{[0-9]+}}) R8=rdonly_untrusted_mem(sz=0)")
__msg("r9 = *(u8 *)(r8 +0) ; R8=rdonly_untrusted_mem(sz=0) R9=scalar")
int btf_id_to_ptr_mem(void *ctx)
{
@@ -137,7 +138,7 @@ int helper_param_not_ok(void *ctx)
p = bpf_rdonly_cast(0, 0);
/*
- * Any helper with ARG_CONST_SIZE_OR_ZERO constraint will do,
+ * Any helper with ARG_MEM_SIZE_OR_ZERO constraint will do,
* the most permissive constraint
*/
bpf_copy_from_user(p, 0, (void *)42);
@@ -164,6 +165,239 @@ int mixed_mem_type(void *ctx)
return *p;
}
+struct {
+ __uint(type, BPF_MAP_TYPE_RINGBUF);
+ __uint(max_entries, 4096);
+} ringbuf SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, u32);
+ __type(value, u64);
+} array SEC(".maps");
+
+char dynptr_data[8];
+
+int zero;
+
+SEC("socket")
+__success
+__log_level(2)
+__msg("r8 = *(u64 *)(r7 +0){{.*}}R7=untrusted_ptr_sock")
+__msg("r8 = *(u64 *)(r7 +0){{.*}}R7=ringbuf_mem")
+__retval(0)
+int mixed_mem_untrusted_btf_id_type(void *ctx)
+{
+ u64 *p, *q, v;
+
+ p = bpf_ringbuf_reserve(&ringbuf, sizeof(*p), 0);
+ if (!p)
+ return 1;
+ *p = 42;
+ q = bpf_rdonly_cast(0, bpf_core_type_id_kernel(struct sock));
+ /*
+ * The load below is reached with PTR_TO_MEM | MEM_RINGBUF on one
+ * path and with PTR_TO_BTF_ID | PTR_UNTRUSTED on the other. The
+ * merged type has to keep the BPF_PROBE_MEM rewrite, otherwise
+ * the NULL deref taken at runtime panics the kernel instead of
+ * returning 0.
+ */
+ asm volatile (
+ "r7 = %[p];"
+ "if %[zero] != 0 goto +1;"
+ "r7 = %[q];"
+ "r8 = *(u64 *)(r7 + 0);"
+ "%[v] = r8;"
+ : [v]"=r"(v)
+ : [p]"r"(p),
+ [q]"r"(q),
+ [zero]"r"(zero)
+ : "r7", "r8");
+ bpf_ringbuf_discard(p, 0);
+ return v;
+}
+
+SEC("socket")
+__success
+__log_level(2)
+__msg("r8 = *(u32 *)(r7 +0){{.*}}R7=ptr_nameidata")
+__msg("r8 = *(u32 *)(r7 +0){{.*}}R7=ringbuf_mem")
+__retval(0)
+int mixed_mem_btf_id_type(void *ctx)
+{
+ struct task_struct *task;
+ u32 *p, *q;
+ u64 v;
+
+ p = bpf_ringbuf_reserve(&ringbuf, sizeof(*p), 0);
+ if (!p)
+ return 1;
+ *p = 42;
+ task = bpf_get_current_task_btf();
+ /*
+ * A plain BTF pointer walk yields a bare PTR_TO_BTF_ID, and
+ * task->nameidata is NULL unless the task currently is in the
+ * middle of a path lookup.
+ */
+ q = (u32 *)&task->nameidata->flags;
+ /*
+ * Same as above, except that the other path yields a bare
+ * PTR_TO_BTF_ID. Merging it with PTR_TO_MEM used to drop the
+ * BPF_PROBE_MEM rewrite the bare PTR_TO_BTF_ID would have
+ * gotten on its own.
+ */
+ asm volatile (
+ "r7 = %[p];"
+ "if %[zero] != 0 goto +1;"
+ "r7 = %[q];"
+ "r8 = *(u32 *)(r7 + 0);"
+ "%[v] = r8;"
+ : [v]"=r"(v)
+ : [p]"r"(p),
+ [q]"r"(q),
+ [zero]"r"(zero)
+ : "r7", "r8");
+ bpf_ringbuf_discard(p, 0);
+ return v;
+}
+
+SEC("socket")
+__success
+__log_level(2)
+__msg("r8 = *(u32 *)(r7 +0){{.*}}R7=ptr_nameidata")
+__msg("r8 = *(u32 *)(r7 +0){{.*}}R7=rdonly_mem")
+__retval(0)
+int mixed_rdonly_mem_btf_id_type(void *ctx)
+{
+ struct task_struct *task;
+ struct bpf_dynptr dptr;
+ char buf[sizeof(u32)];
+ u32 *p, *q;
+ u64 v;
+
+ if (bpf_dynptr_from_mem(dynptr_data, sizeof(dynptr_data), 0, &dptr))
+ return 1;
+ p = bpf_dynptr_slice(&dptr, 0, buf, sizeof(buf));
+ if (!p)
+ return 1;
+ task = bpf_get_current_task_btf();
+ q = (u32 *)&task->nameidata->flags;
+ /*
+ * Same as above, except that the PTR_TO_MEM side already carries
+ * MEM_RDONLY. Merging it with a bare PTR_TO_BTF_ID used to yield
+ * PTR_TO_MEM | MEM_RDONLY, which is not rewritten either since
+ * only its PTR_UNTRUSTED variant is.
+ */
+ asm volatile (
+ "r7 = %[p];"
+ "if %[zero] != 0 goto +1;"
+ "r7 = %[q];"
+ "r8 = *(u32 *)(r7 + 0);"
+ "%[v] = r8;"
+ : [v]"=r"(v)
+ : [p]"r"(p),
+ [q]"r"(q),
+ [zero]"r"(zero)
+ : "r7", "r8");
+ return v;
+}
+
+SEC("socket")
+__success
+__log_level(2)
+__msg("r8 = *(u64 *)(r7 +0){{.*}}R7=ringbuf_mem")
+__msg("r8 = *(u64 *)(r7 +0){{.*}}R7=rdonly_untrusted_mem")
+__retval(0)
+int mixed_mem_mem_type(void *ctx)
+{
+ u64 *p, *q, v;
+
+ p = bpf_ringbuf_reserve(&ringbuf, sizeof(*p), 0);
+ if (!p)
+ return 1;
+ *p = 42;
+ q = bpf_rdonly_cast(0, 0);
+ /*
+ * Both paths are PTR_TO_MEM based, so they used to not trip the
+ * type mismatch check and skipped the merge altogether, leaving
+ * the insn with the PTR_TO_MEM | MEM_RINGBUF recorded first and
+ * hence without the BPF_PROBE_MEM rewrite the other path needs.
+ */
+ asm volatile (
+ "r7 = %[q];"
+ "if %[zero] == 0 goto +1;"
+ "r7 = %[p];"
+ "r8 = *(u64 *)(r7 + 0);"
+ "%[v] = r8;"
+ : [v]"=r"(v)
+ : [p]"r"(p),
+ [q]"r"(q),
+ [zero]"r"(zero)
+ : "r7", "r8");
+ bpf_ringbuf_discard(p, 0);
+ return v;
+}
+
+SEC("socket")
+__failure
+__msg("same insn cannot be used with different pointers")
+int mixed_map_value_mem_type(void *ctx)
+{
+ u64 *p, *q, v;
+ u32 key = 0;
+
+ p = bpf_map_lookup_elem(&array, &key);
+ if (!p)
+ return 1;
+ q = bpf_rdonly_cast(0, 0);
+ /*
+ * PTR_TO_MAP_VALUE is neither PTR_TO_MEM nor PTR_TO_BTF_ID based,
+ * so it cannot be merged into a type which keeps the BPF_PROBE_MEM
+ * rewrite the PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED of the other
+ * path needs. Both bases were mismatch ok, hence the load used to be
+ * accepted with the PTR_TO_MAP_VALUE recorded and the NULL deref on
+ * the second path panicked the kernel.
+ */
+ asm volatile (
+ "r7 = %[q];"
+ "if %[zero] == 0 goto +1;"
+ "r7 = %[p];"
+ "r8 = *(u64 *)(r7 + 0);"
+ "%[v] = r8;"
+ : [v]"=r"(v)
+ : [p]"r"(p),
+ [q]"r"(q),
+ [zero]"r"(zero)
+ : "r7", "r8");
+ return v;
+}
+
+SEC("socket")
+__failure
+__msg("same insn cannot be used with different pointers")
+int mixed_stack_mem_type(void *ctx)
+{
+ u64 *p = bpf_rdonly_cast(0, 0);
+ u64 s = 42, v;
+
+ /*
+ * Same as above, but for a PTR_TO_STACK on the other path.
+ */
+ asm volatile (
+ "r7 = %[p];"
+ "if %[zero] == 0 goto +1;"
+ "r7 = %[s];"
+ "r8 = *(u64 *)(r7 + 0);"
+ "%[v] = r8;"
+ : [v]"=r"(v)
+ : [p]"r"(p),
+ [s]"r"(&s),
+ [zero]"r"(zero)
+ : "r7", "r8");
+ return v;
+}
+
__attribute__((__aligned__(8)))
u8 global[] = {
0x11, 0x22, 0x33, 0x44,
@@ -226,4 +460,21 @@ int null_check(void *ctx)
return 0;
}
+SEC("socket")
+__success
+__retval(1)
+int ldx_is_ok_commuted_addr(void *ctx)
+{
+ int v, *p, *derived;
+
+ v = 1;
+ p = bpf_rdonly_cast(&v, 0);
+ asm volatile ("%[dst] = 0;"
+ "%[dst] += %[src];"
+ : [dst]"=&r"(derived)
+ : [src]"r"(p)
+ : "memory");
+ return *derived;
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/modify_return.c b/tools/testing/selftests/bpf/progs/modify_return.c
index 3376d4849f58..68fabd2efe8d 100644
--- a/tools/testing/selftests/bpf/progs/modify_return.c
+++ b/tools/testing/selftests/bpf/progs/modify_return.c
@@ -12,11 +12,14 @@ char _license[] SEC("license") = "GPL";
static int sequence = 0;
__s32 input_retval = 0;
+__u32 test_pid = 0;
__u64 fentry_result = 0;
SEC("fentry/bpf_modify_return_test")
int BPF_PROG(fentry_test, int a, __u64 b)
{
+ if (bpf_get_current_pid_tgid() >> 32 != test_pid)
+ return 0;
sequence++;
fentry_result = (sequence == 1);
return 0;
@@ -26,6 +29,8 @@ __u64 fmod_ret_result = 0;
SEC("fmod_ret/bpf_modify_return_test")
int BPF_PROG(fmod_ret_test, int a, int *b, int ret)
{
+ if (bpf_get_current_pid_tgid() >> 32 != test_pid)
+ return ret;
sequence++;
/* This is the first fmod_ret program, the ret passed should be 0 */
fmod_ret_result = (sequence == 2 && ret == 0);
@@ -36,6 +41,8 @@ __u64 fexit_result = 0;
SEC("fexit/bpf_modify_return_test")
int BPF_PROG(fexit_test, int a, __u64 b, int ret)
{
+ if (bpf_get_current_pid_tgid() >> 32 != test_pid)
+ return 0;
sequence++;
/* If the input_reval is non-zero a successful modification should have
* occurred.
@@ -55,6 +62,8 @@ SEC("fentry/bpf_modify_return_test2")
int BPF_PROG(fentry_test2, int a, int *b, short c, int d, void *e, char f,
int g)
{
+ if (bpf_get_current_pid_tgid() >> 32 != test_pid)
+ return 0;
sequence2++;
fentry_result2 = (sequence2 == 1);
return 0;
@@ -65,6 +74,8 @@ SEC("fmod_ret/bpf_modify_return_test2")
int BPF_PROG(fmod_ret_test2, int a, int *b, short c, int d, void *e, char f,
int g, int ret)
{
+ if (bpf_get_current_pid_tgid() >> 32 != test_pid)
+ return ret;
sequence2++;
/* This is the first fmod_ret program, the ret passed should be 0 */
fmod_ret_result2 = (sequence2 == 2 && ret == 0);
@@ -76,6 +87,8 @@ SEC("fexit/bpf_modify_return_test2")
int BPF_PROG(fexit_test2, int a, int *b, short c, int d, void *e, char f,
int g, int ret)
{
+ if (bpf_get_current_pid_tgid() >> 32 != test_pid)
+ return 0;
sequence2++;
/* If the input_reval is non-zero a successful modification should have
* occurred.
diff --git a/tools/testing/selftests/bpf/progs/mptcpify.c b/tools/testing/selftests/bpf/progs/mptcpify.c
index cbdc730c3a47..e3f8cb54dbe9 100644
--- a/tools/testing/selftests/bpf/progs/mptcpify.c
+++ b/tools/testing/selftests/bpf/progs/mptcpify.c
@@ -15,7 +15,7 @@ int BPF_PROG(mptcpify, int family, int type, int protocol)
return protocol;
if ((family == AF_INET || family == AF_INET6) &&
- type == SOCK_STREAM &&
+ (type & SOCK_TYPE_MASK) == SOCK_STREAM &&
(!protocol || protocol == IPPROTO_TCP)) {
return IPPROTO_MPTCP;
}
diff --git a/tools/testing/selftests/bpf/progs/percpu_alloc_array.c b/tools/testing/selftests/bpf/progs/percpu_alloc_array.c
index 37c2d2608ec0..ed6a2a93d5a5 100644
--- a/tools/testing/selftests/bpf/progs/percpu_alloc_array.c
+++ b/tools/testing/selftests/bpf/progs/percpu_alloc_array.c
@@ -187,4 +187,36 @@ out:
return 0;
}
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
+ __uint(max_entries, 2);
+ __type(key, int);
+ __type(value, u32);
+} percpu SEC(".maps");
+
+SEC("?fentry/bpf_fentry_test1")
+int BPF_PROG(test_percpu_array, int x)
+{
+ u64 value = 0xDEADC0DE;
+ int key = 0;
+
+ bpf_map_update_elem(&percpu, &key, &value, BPF_ANY);
+ return 0;
+}
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE);
+ __type(key, struct bpf_cgroup_storage_key);
+ __type(value, u32);
+} percpu_cgroup_storage SEC(".maps");
+
+SEC("cgroup_skb/egress")
+int cgroup_egress(struct __sk_buff *skb)
+{
+ u32 *val = bpf_get_local_storage(&percpu_cgroup_storage, 0);
+
+ *val = 1;
+ return 1;
+}
+
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 f2b8eb2ff76f..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")
@@ -110,7 +124,7 @@ int BPF_PROG(test_array_map_3)
}
SEC("?fentry.s/bpf_fentry_test1")
-__failure __msg("arg#0 expected for bpf_percpu_obj_drop_impl()")
+__failure __msg("R1 expected for bpf_percpu_obj_drop()")
int BPF_PROG(test_array_map_4)
{
struct val_t __percpu_kptr *p;
@@ -124,7 +138,7 @@ int BPF_PROG(test_array_map_4)
}
SEC("?fentry.s/bpf_fentry_test1")
-__failure __msg("arg#0 expected for bpf_obj_drop_impl()")
+__failure __msg("R1 expected for bpf_obj_drop()")
int BPF_PROG(test_array_map_5)
{
struct val_t *p;
@@ -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 7d04254e61f1..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)
@@ -177,7 +229,7 @@ global_subprog_calling_sleepable_global(int i)
}
SEC("?syscall")
-__failure __msg("global functions that may sleep are not allowed in non-sleepable context")
+__failure __msg("sleepable global function")
int preempt_global_sleepable_helper_subprog(struct __sk_buff *ctx)
{
preempt_disable();
@@ -188,7 +240,7 @@ int preempt_global_sleepable_helper_subprog(struct __sk_buff *ctx)
}
SEC("?syscall")
-__failure __msg("global functions that may sleep are not allowed in non-sleepable context")
+__failure __msg("sleepable global function")
int preempt_global_sleepable_kfunc_subprog(struct __sk_buff *ctx)
{
preempt_disable();
@@ -199,7 +251,7 @@ int preempt_global_sleepable_kfunc_subprog(struct __sk_buff *ctx)
}
SEC("?syscall")
-__failure __msg("global functions that may sleep are not allowed in non-sleepable context")
+__failure __msg("sleepable global function")
int preempt_global_sleepable_subprog_indirect(struct __sk_buff *ctx)
{
preempt_disable();
diff --git a/tools/testing/selftests/bpf/progs/profiler.h b/tools/testing/selftests/bpf/progs/profiler.h
index 3bac4fdd4bdf..637fbf2c2652 100644
--- a/tools/testing/selftests/bpf/progs/profiler.h
+++ b/tools/testing/selftests/bpf/progs/profiler.h
@@ -169,7 +169,7 @@ enum bpf_function_id {
profiler_bpf_sched_process_exec,
profiler_bpf_sched_process_exit,
profiler_bpf_sys_enter_kill,
- profiler_bpf_do_filp_open_ret,
+ profiler_bpf_do_file_open_ret,
profiler_bpf_sched_process_fork,
profiler_bpf_vfs_link,
profiler_bpf_vfs_symlink,
diff --git a/tools/testing/selftests/bpf/progs/profiler.inc.h b/tools/testing/selftests/bpf/progs/profiler.inc.h
index 813143b4985d..9044dd8aff11 100644
--- a/tools/testing/selftests/bpf/progs/profiler.inc.h
+++ b/tools/testing/selftests/bpf/progs/profiler.inc.h
@@ -751,11 +751,11 @@ out:
return 0;
}
-SEC("kretprobe/do_filp_open")
-int kprobe_ret__do_filp_open(struct pt_regs* ctx)
+SEC("kretprobe/do_file_open")
+int kprobe_ret__do_file_open(struct pt_regs *ctx)
{
struct bpf_func_stats_ctx stats_ctx;
- bpf_stats_enter(&stats_ctx, profiler_bpf_do_filp_open_ret);
+ bpf_stats_enter(&stats_ctx, profiler_bpf_do_file_open_ret);
struct file* filp = (struct file*)PT_REGS_RC_CORE(ctx);
diff --git a/tools/testing/selftests/bpf/progs/pyperf.h b/tools/testing/selftests/bpf/progs/pyperf.h
index 86484f07e1d1..fd93a96e5901 100644
--- a/tools/testing/selftests/bpf/progs/pyperf.h
+++ b/tools/testing/selftests/bpf/progs/pyperf.h
@@ -85,9 +85,11 @@ static void *get_thread_state(void *tls_base, PidData *pidData)
return thread_state;
}
-static __always_inline bool get_frame_data(void *frame_ptr, PidData *pidData,
- FrameData *frame, Symbol *symbol)
+__weak bool __get_frame_data(long frame_ptr_, PidData *pidData __arg_nonnull,
+ FrameData *frame __arg_nonnull, Symbol *symbol __arg_nonnull)
{
+ void *frame_ptr = (void *)frame_ptr_;
+
// read data from PyFrameObject
bpf_probe_read_user(&frame->f_back,
sizeof(frame->f_back),
@@ -119,6 +121,12 @@ static __always_inline bool get_frame_data(void *frame_ptr, PidData *pidData,
return true;
}
+static __always_inline bool get_frame_data(void *frame_ptr, PidData *pidData,
+ FrameData *frame, Symbol *symbol)
+{
+ return __get_frame_data((long)frame_ptr, pidData, frame, symbol);
+}
+
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1);
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 4acb6af2dfe3..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)
{
@@ -134,7 +135,7 @@ unlock_err:
}
SEC("?tc")
-__failure __msg("arg#1 expected pointer to allocated object")
+__failure __msg("R2 expected pointer to allocated object")
long rbtree_api_add_to_multiple_trees(void *ctx)
{
struct node_data *n;
@@ -153,7 +154,7 @@ long rbtree_api_add_to_multiple_trees(void *ctx)
}
SEC("?tc")
-__failure __msg("dereference of modified ptr_or_null_ ptr R2 off=16 disallowed")
+__failure __msg("Possibly NULL pointer passed to trusted R2")
long rbtree_api_use_unchecked_remove_retval(void *ctx)
{
struct bpf_rb_node *res;
@@ -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))
{
@@ -281,7 +329,7 @@ long add_with_cb(bool (cb)(struct bpf_rb_node *a, const struct bpf_rb_node *b))
}
SEC("?tc")
-__failure __msg("arg#1 expected pointer to allocated object")
+__failure __msg("R2 expected pointer to allocated object")
long rbtree_api_add_bad_cb_bad_fn_call_add(void *ctx)
{
return add_with_cb(less__bad_fn_call_add);
@@ -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/rbtree_search_kptr.c b/tools/testing/selftests/bpf/progs/rbtree_search_kptr.c
new file mode 100644
index 000000000000..610aae45e2dc
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/rbtree_search_kptr.c
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 KylinSoft Corporation. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+#include "bpf_experimental.h"
+
+#define NR_NODES 16
+
+struct node_data {
+ int data;
+};
+
+struct tree_node {
+ struct bpf_rb_node node;
+ u64 key;
+ struct node_data __kptr * node_data;
+};
+
+struct tree_node_ref {
+ struct bpf_refcount ref;
+ struct bpf_rb_node node;
+ u64 key;
+ struct node_data __kptr * node_data;
+};
+
+#define private(name) SEC(".data." #name) __hidden __aligned(8)
+
+private(A) struct bpf_rb_root root __contains(tree_node, node);
+private(A) struct bpf_spin_lock lock;
+
+private(B) struct bpf_rb_root root_r __contains(tree_node_ref, node);
+private(B) struct bpf_spin_lock lock_r;
+
+static bool less(struct bpf_rb_node *a, const struct bpf_rb_node *b)
+{
+ struct tree_node *node_a, *node_b;
+
+ node_a = container_of(a, struct tree_node, node);
+ node_b = container_of(b, struct tree_node, node);
+
+ return node_a->key < node_b->key;
+}
+
+SEC("syscall")
+__retval(0)
+long rbtree_search_kptr(void *ctx)
+{
+ struct tree_node *tnode;
+ struct bpf_rb_node *rb_n;
+ struct node_data __kptr * node_data;
+ int lookup_key = NR_NODES / 2;
+ int lookup_data = NR_NODES / 2;
+ int i, data, ret = 0;
+
+ for (i = 0; i < NR_NODES && can_loop; i++) {
+ tnode = bpf_obj_new(typeof(*tnode));
+ if (!tnode)
+ return __LINE__;
+
+ node_data = bpf_obj_new(typeof(*node_data));
+ if (!node_data) {
+ bpf_obj_drop(tnode);
+ return __LINE__;
+ }
+
+ tnode->key = i;
+ node_data->data = i;
+
+ node_data = bpf_kptr_xchg(&tnode->node_data, node_data);
+ if (node_data)
+ bpf_obj_drop(node_data);
+
+ bpf_spin_lock(&lock);
+ bpf_rbtree_add(&root, &tnode->node, less);
+ bpf_spin_unlock(&lock);
+ }
+
+ bpf_spin_lock(&lock);
+ rb_n = bpf_rbtree_root(&root);
+ while (rb_n && can_loop) {
+ tnode = container_of(rb_n, struct tree_node, node);
+ node_data = bpf_kptr_xchg(&tnode->node_data, NULL);
+ if (!node_data) {
+ ret = __LINE__;
+ goto fail;
+ }
+
+ data = node_data->data;
+ node_data = bpf_kptr_xchg(&tnode->node_data, node_data);
+ if (node_data) {
+ bpf_spin_unlock(&lock);
+ bpf_obj_drop(node_data);
+ return __LINE__;
+ }
+
+ if (lookup_key == tnode->key) {
+ if (data == lookup_data)
+ break;
+
+ ret = __LINE__;
+ goto fail;
+ }
+
+ if (lookup_key < tnode->key)
+ rb_n = bpf_rbtree_left(&root, rb_n);
+ else
+ rb_n = bpf_rbtree_right(&root, rb_n);
+ }
+ bpf_spin_unlock(&lock);
+
+ while (can_loop) {
+ bpf_spin_lock(&lock);
+ rb_n = bpf_rbtree_first(&root);
+ if (!rb_n) {
+ bpf_spin_unlock(&lock);
+ return 0;
+ }
+
+ rb_n = bpf_rbtree_remove(&root, rb_n);
+ if (!rb_n) {
+ ret = __LINE__;
+ goto fail;
+ }
+ bpf_spin_unlock(&lock);
+
+ tnode = container_of(rb_n, struct tree_node, node);
+
+ node_data = bpf_kptr_xchg(&tnode->node_data, NULL);
+ if (node_data)
+ bpf_obj_drop(node_data);
+
+ bpf_obj_drop(tnode);
+ }
+
+ return 0;
+fail:
+ bpf_spin_unlock(&lock);
+ return ret;
+}
+
+static bool less_r(struct bpf_rb_node *a, const struct bpf_rb_node *b)
+{
+ struct tree_node_ref *node_a, *node_b;
+
+ node_a = container_of(a, struct tree_node_ref, node);
+ node_b = container_of(b, struct tree_node_ref, node);
+
+ return node_a->key < node_b->key;
+}
+
+SEC("syscall")
+__retval(0)
+long rbtree_search_kptr_ref(void *ctx)
+{
+ struct tree_node_ref *tnode_r, *tnode_m;
+ struct bpf_rb_node *rb_n;
+ struct node_data __kptr * node_data;
+ int lookup_key = NR_NODES / 2;
+ int lookup_data = NR_NODES / 2;
+ int i, data, ret = 0;
+
+ for (i = 0; i < NR_NODES && can_loop; i++) {
+ tnode_r = bpf_obj_new(typeof(*tnode_r));
+ if (!tnode_r)
+ return __LINE__;
+
+ node_data = bpf_obj_new(typeof(*node_data));
+ if (!node_data) {
+ bpf_obj_drop(tnode_r);
+ return __LINE__;
+ }
+
+ tnode_r->key = i;
+ node_data->data = i;
+
+ node_data = bpf_kptr_xchg(&tnode_r->node_data, node_data);
+ if (node_data)
+ bpf_obj_drop(node_data);
+
+ /* Unused reference */
+ tnode_m = bpf_refcount_acquire(tnode_r);
+ if (!tnode_m)
+ return __LINE__;
+
+ bpf_spin_lock(&lock_r);
+ bpf_rbtree_add(&root_r, &tnode_r->node, less_r);
+ bpf_spin_unlock(&lock_r);
+
+ bpf_obj_drop(tnode_m);
+ }
+
+ bpf_spin_lock(&lock_r);
+ rb_n = bpf_rbtree_root(&root_r);
+ while (rb_n && can_loop) {
+ tnode_r = container_of(rb_n, struct tree_node_ref, node);
+ node_data = bpf_kptr_xchg(&tnode_r->node_data, NULL);
+ if (!node_data) {
+ ret = __LINE__;
+ goto fail;
+ }
+
+ data = node_data->data;
+ node_data = bpf_kptr_xchg(&tnode_r->node_data, node_data);
+ if (node_data) {
+ bpf_spin_unlock(&lock_r);
+ bpf_obj_drop(node_data);
+ return __LINE__;
+ }
+
+ if (lookup_key == tnode_r->key) {
+ if (data == lookup_data)
+ break;
+
+ ret = __LINE__;
+ goto fail;
+ }
+
+ if (lookup_key < tnode_r->key)
+ rb_n = bpf_rbtree_left(&root_r, rb_n);
+ else
+ rb_n = bpf_rbtree_right(&root_r, rb_n);
+ }
+ bpf_spin_unlock(&lock_r);
+
+ while (can_loop) {
+ bpf_spin_lock(&lock_r);
+ rb_n = bpf_rbtree_first(&root_r);
+ if (!rb_n) {
+ bpf_spin_unlock(&lock_r);
+ return 0;
+ }
+
+ rb_n = bpf_rbtree_remove(&root_r, rb_n);
+ if (!rb_n) {
+ ret = __LINE__;
+ goto fail;
+ }
+ bpf_spin_unlock(&lock_r);
+
+ tnode_r = container_of(rb_n, struct tree_node_ref, node);
+
+ node_data = bpf_kptr_xchg(&tnode_r->node_data, NULL);
+ if (node_data)
+ bpf_obj_drop(node_data);
+
+ bpf_obj_drop(tnode_r);
+ }
+
+ return 0;
+fail:
+ bpf_spin_unlock(&lock_r);
+ return ret;
+}
+
+SEC("syscall")
+__failure __msg("R1 type=scalar expected=map_value, ptr_, ptr_")
+long non_own_ref_kptr_xchg_no_lock(void *ctx)
+{
+ struct tree_node *tnode;
+ struct bpf_rb_node *rb_n;
+ struct node_data __kptr * node_data;
+ int data;
+
+ bpf_spin_lock(&lock);
+ rb_n = bpf_rbtree_first(&root);
+ if (!rb_n) {
+ bpf_spin_unlock(&lock);
+ return __LINE__;
+ }
+ bpf_spin_unlock(&lock);
+
+ tnode = container_of(rb_n, struct tree_node, node);
+ node_data = bpf_kptr_xchg(&tnode->node_data, NULL);
+ if (!node_data)
+ return __LINE__;
+
+ data = node_data->data;
+ if (data < 0)
+ return __LINE__;
+
+ node_data = bpf_kptr_xchg(&tnode->node_data, node_data);
+ if (node_data)
+ return __LINE__;
+
+ return 0;
+}
+
+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 d70c28824bbe..cdb255addbc3 100644
--- a/tools/testing/selftests/bpf/progs/rcu_read_lock.c
+++ b/tools/testing/selftests/bpf/progs/rcu_read_lock.c
@@ -7,6 +7,16 @@
#include "bpf_tracing_net.h"
#include "bpf_misc.h"
+/* clang considers 'sum += 1' as usage but 'sum++' as non-usage. GCC
+ * is more consistent and considers both 'sum += 1' and 'sum++' as
+ * non-usage. This triggers warnings in the functions below.
+ *
+ * Starting with GCC 16 -Wunused-but-set-variable=2 can be used to
+ * mimic clang's behavior. */
+#if !defined(__clang__) && __GNUC__ > 15
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
+#endif
+
char _license[] SEC("license") = "GPL";
struct {
@@ -539,3 +549,79 @@ int rcu_read_lock_sleepable_global_subprog_indirect(void *ctx)
bpf_rcu_read_unlock();
return 0;
}
+
+struct rcu_node_data {
+ long key;
+ struct bpf_rb_node node;
+};
+
+struct rcu_node_stash {
+ struct rcu_node_data __kptr *node;
+};
+
+/*
+ * Necessary so that LLVM emits BTF for rcu_node_data rather than just a
+ * fwd reference to it, same as in progs/local_kptr_stash.c.
+ */
+struct rcu_node_data *just_here_because_btf_bug;
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, int);
+ __type(value, struct rcu_node_stash);
+} node_stash SEC(".maps");
+
+long non_own_ref_key;
+
+SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
+int non_own_ref_untrusted_ld(void *ctx)
+{
+ struct rcu_node_stash *stash;
+ struct rcu_node_data *node;
+ int key = 0;
+
+ stash = bpf_map_lookup_elem(&node_stash, &key);
+ if (!stash)
+ return 0;
+ bpf_rcu_read_lock();
+ node = stash->node;
+ if (!node) {
+ bpf_rcu_read_unlock();
+ return 0;
+ }
+ bpf_rcu_read_unlock();
+ /*
+ * 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;
+}
+
+long rcu_untrusted_wq_flags;
+
+SEC("?tp_btf/tcp_probe")
+int BPF_PROG(rcu_untrusted_union_ld, struct sock *sk)
+{
+ struct socket_wq *wq;
+
+ /*
+ * sk_wq sits in a two member union, so btf_struct_walk() marks the
+ * pointer PTR_UNTRUSTED, and the __rcu tag on the member adds MEM_RCU
+ * on top of it. struct sock is not on the __safe_rcu_or_null allow
+ * list, hence the two stay combined and the load below has to get the
+ * BPF_PROBE_MEM rewrite for PTR_TO_BTF_ID | PTR_UNTRUSTED | MEM_RCU,
+ * otherwise a bad address panics the kernel.
+ *
+ * The __rcu tag only reaches BTF on a clang built kernel, that is, one
+ * with CONFIG_PAHOLE_HAS_BTF_TAG. On a gcc built kernel the walk yields
+ * a plain untrusted pointer, which is rewritten either way.
+ */
+ wq = sk->sk_wq;
+ if (!wq)
+ return 0;
+ rcu_untrusted_wq_flags = wq->flags;
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/rcu_tasks_trace_gp.c b/tools/testing/selftests/bpf/progs/rcu_tasks_trace_gp.c
index df4873558634..189c05c6abcc 100644
--- a/tools/testing/selftests/bpf/progs/rcu_tasks_trace_gp.c
+++ b/tools/testing/selftests/bpf/progs/rcu_tasks_trace_gp.c
@@ -1,36 +1,14 @@
// SPDX-License-Identifier: GPL-2.0
#include <vmlinux.h>
-#include <bpf/bpf_tracing.h>
#include <bpf/bpf_helpers.h>
+#include "../test_kmods/bpf_testmod_kfunc.h"
-struct task_ls_map {
- __uint(type, BPF_MAP_TYPE_TASK_STORAGE);
- __uint(map_flags, BPF_F_NO_PREALLOC);
- __type(key, int);
- __type(value, int);
-} task_ls_map SEC(".maps");
-
-long gp_seq;
+int done;
SEC("syscall")
-int do_call_rcu_tasks_trace(void *ctx)
-{
- struct task_struct *current;
- int *v;
-
- current = bpf_get_current_task_btf();
- v = bpf_task_storage_get(&task_ls_map, current, NULL, BPF_LOCAL_STORAGE_GET_F_CREATE);
- if (!v)
- return 1;
- /* Invoke call_rcu_tasks_trace */
- return bpf_task_storage_delete(&task_ls_map, current);
-}
-
-SEC("kprobe/rcu_tasks_trace_postgp")
-int rcu_tasks_trace_postgp(void *ctx)
+int call_rcu_tasks_trace(void *ctx)
{
- __sync_add_and_fetch(&gp_seq, 1);
- return 0;
+ return bpf_kfunc_call_test_call_rcu_tasks_trace(&done);
}
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/read_bpf_task_storage_busy.c b/tools/testing/selftests/bpf/progs/read_bpf_task_storage_busy.c
deleted file mode 100644
index 69da05bb6c63..000000000000
--- a/tools/testing/selftests/bpf/progs/read_bpf_task_storage_busy.c
+++ /dev/null
@@ -1,38 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/* Copyright (C) 2022. Huawei Technologies Co., Ltd */
-#include "vmlinux.h"
-#include <bpf/bpf_helpers.h>
-#include <bpf/bpf_tracing.h>
-
-extern bool CONFIG_PREEMPTION __kconfig __weak;
-extern const int bpf_task_storage_busy __ksym;
-
-char _license[] SEC("license") = "GPL";
-
-int pid = 0;
-int busy = 0;
-
-struct {
- __uint(type, BPF_MAP_TYPE_TASK_STORAGE);
- __uint(map_flags, BPF_F_NO_PREALLOC);
- __type(key, int);
- __type(value, long);
-} task SEC(".maps");
-
-SEC("raw_tp/sys_enter")
-int BPF_PROG(read_bpf_task_storage_busy)
-{
- int *value;
-
- if (!CONFIG_PREEMPTION)
- return 0;
-
- if (bpf_get_current_pid_tgid() >> 32 != pid)
- return 0;
-
- value = bpf_this_cpu_ptr(&bpf_task_storage_busy);
- if (value)
- busy = *value;
-
- return 0;
-}
diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr.c b/tools/testing/selftests/bpf/progs/refcounted_kptr.c
index 1aca85d86aeb..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;
@@ -368,6 +384,427 @@ INSERT_STASH_READ(true, "insert_stash_read: remove from tree");
INSERT_STASH_READ(false, "insert_stash_read: don't remove from tree");
SEC("tc")
+__description("list_empty_test: list empty before add, non-empty after add")
+__success __retval(0)
+int list_empty_test(void *ctx)
+{
+ struct node_data *node_new;
+
+ bpf_spin_lock(&lock);
+ if (!bpf_list_empty(&head)) {
+ bpf_spin_unlock(&lock);
+ return -1;
+ }
+ bpf_spin_unlock(&lock);
+
+ node_new = bpf_obj_new(typeof(*node_new));
+ if (!node_new)
+ return -2;
+
+ bpf_spin_lock(&lock);
+ bpf_list_push_front(&head, &node_new->l);
+
+ if (bpf_list_empty(&head)) {
+ bpf_spin_unlock(&lock);
+ return -3;
+ }
+ bpf_spin_unlock(&lock);
+ return 0;
+}
+
+static struct node_data *__add_in_list(struct bpf_list_head *head,
+ struct bpf_spin_lock *lock)
+{
+ struct node_data *node_new, *node_ref;
+
+ node_new = bpf_obj_new(typeof(*node_new));
+ if (!node_new)
+ return NULL;
+
+ node_ref = bpf_refcount_acquire(node_new);
+
+ bpf_spin_lock(lock);
+ bpf_list_push_front(head, &node_new->l);
+ bpf_spin_unlock(lock);
+ return node_ref;
+}
+
+SEC("tc")
+__description("list_is_edge_test1: is_first on first node, is_last on last node")
+__success __retval(0)
+int list_is_edge_test1(void *ctx)
+{
+ struct node_data *node_first, *node_last;
+ int err = 0;
+
+ node_last = __add_in_list(&head, &lock);
+ if (!node_last)
+ return -1;
+
+ node_first = __add_in_list(&head, &lock);
+ if (!node_first) {
+ bpf_obj_drop(node_last);
+ return -2;
+ }
+
+ bpf_spin_lock(&lock);
+ if (!bpf_list_is_first(&head, &node_first->l)) {
+ err = -3;
+ goto fail;
+ }
+ if (!bpf_list_is_last(&head, &node_last->l))
+ err = -4;
+
+fail:
+ bpf_spin_unlock(&lock);
+ bpf_obj_drop(node_first);
+ bpf_obj_drop(node_last);
+ return err;
+}
+
+SEC("tc")
+__description("list_is_edge_test2: accept list_front/list_back return value")
+__success __retval(0)
+int list_is_edge_test2(void *ctx)
+{
+ struct bpf_list_node *front, *back;
+ struct node_data *a, *b;
+ long err = 0;
+
+ a = __add_in_list(&head, &lock);
+ if (!a)
+ return -1;
+
+ b = __add_in_list(&head, &lock);
+ if (!b) {
+ bpf_obj_drop(a);
+ return -2;
+ }
+
+ bpf_spin_lock(&lock);
+ front = bpf_list_front(&head);
+ back = bpf_list_back(&head);
+ if (!front || !back) {
+ err = -3;
+ goto out_unlock;
+ }
+
+ if (!bpf_list_is_first(&head, front) || bpf_list_is_last(&head, front)) {
+ err = -4;
+ goto out_unlock;
+ }
+
+ if (!bpf_list_is_last(&head, back) || bpf_list_is_first(&head, back)) {
+ err = -5;
+ goto out_unlock;
+ }
+
+out_unlock:
+ bpf_spin_unlock(&lock);
+ bpf_obj_drop(a);
+ bpf_obj_drop(b);
+ return err;
+}
+
+SEC("tc")
+__description("list_is_edge_test3: single node is both first and last")
+__success __retval(0)
+int list_is_edge_test3(void *ctx)
+{
+ struct node_data *tmp;
+ struct bpf_list_node *node;
+ long err = 0;
+
+ tmp = __add_in_list(&head, &lock);
+ if (!tmp)
+ return -1;
+
+ bpf_spin_lock(&lock);
+ node = bpf_list_front(&head);
+ if (!node) {
+ bpf_spin_unlock(&lock);
+ bpf_obj_drop(tmp);
+ return -2;
+ }
+
+ if (!bpf_list_is_first(&head, node) || !bpf_list_is_last(&head, node))
+ err = -3;
+ bpf_spin_unlock(&lock);
+
+ bpf_obj_drop(tmp);
+ return err;
+}
+
+SEC("tc")
+__description("list_del_test1: del returns removed nodes")
+__success __retval(0)
+int list_del_test1(void *ctx)
+{
+ struct node_data *node_first, *node_last;
+ struct bpf_list_node *bpf_node_first, *bpf_node_last;
+ int err = 0;
+
+ node_last = __add_in_list(&head, &lock);
+ if (!node_last)
+ return -1;
+
+ node_first = __add_in_list(&head, &lock);
+ if (!node_first) {
+ bpf_obj_drop(node_last);
+ return -2;
+ }
+
+ bpf_spin_lock(&lock);
+ bpf_node_last = bpf_list_del(&head, &node_last->l);
+ bpf_node_first = bpf_list_del(&head, &node_first->l);
+ bpf_spin_unlock(&lock);
+
+ if (bpf_node_first)
+ bpf_obj_drop(container_of(bpf_node_first, struct node_data, l));
+ else
+ err = -3;
+
+ if (bpf_node_last)
+ bpf_obj_drop(container_of(bpf_node_last, struct node_data, l));
+ else
+ err = -4;
+
+ bpf_obj_drop(node_first);
+ bpf_obj_drop(node_last);
+ return err;
+}
+
+SEC("tc")
+__description("list_del_test2: remove an arbitrary node from the list")
+__success __retval(0)
+int list_del_test2(void *ctx)
+{
+ struct bpf_rb_node *rb;
+ struct bpf_list_node *l;
+ struct node_data *n;
+ long err;
+
+ err = __insert_in_tree_and_list(&head, &root, &lock);
+ if (err)
+ return err;
+
+ bpf_spin_lock(&lock);
+ rb = bpf_rbtree_first(&root);
+ if (!rb) {
+ bpf_spin_unlock(&lock);
+ return -4;
+ }
+
+ rb = bpf_rbtree_remove(&root, rb);
+ if (!rb) {
+ bpf_spin_unlock(&lock);
+ return -5;
+ }
+
+ n = container_of(rb, struct node_data, r);
+ l = bpf_list_del(&head, &n->l);
+ bpf_spin_unlock(&lock);
+ bpf_obj_drop(n);
+ if (!l)
+ return -6;
+
+ bpf_obj_drop(container_of(l, struct node_data, l));
+ return 0;
+}
+
+SEC("tc")
+__description("list_del_test3: list_del accepts list_front return value as node")
+__success __retval(0)
+int list_del_test3(void *ctx)
+{
+ struct node_data *tmp;
+ struct bpf_list_node *bpf_node, *l;
+ long err = 0;
+
+ tmp = __add_in_list(&head, &lock);
+ if (!tmp)
+ return -1;
+
+ bpf_spin_lock(&lock);
+ bpf_node = bpf_list_front(&head);
+ if (!bpf_node) {
+ bpf_spin_unlock(&lock);
+ err = -2;
+ goto fail;
+ }
+
+ l = bpf_list_del(&head, bpf_node);
+ bpf_spin_unlock(&lock);
+ if (!l) {
+ err = -3;
+ goto fail;
+ }
+
+ bpf_obj_drop(container_of(l, struct node_data, l));
+ bpf_obj_drop(tmp);
+ return 0;
+
+fail:
+ bpf_obj_drop(tmp);
+ return err;
+}
+
+SEC("tc")
+__description("list_add_test1: insert new node after prev")
+__success __retval(0)
+int list_add_test1(void *ctx)
+{
+ struct node_data *node_first;
+ struct node_data *new_node;
+ long err = 0;
+
+ node_first = __add_in_list(&head, &lock);
+ if (!node_first)
+ return -1;
+
+ new_node = bpf_obj_new(typeof(*new_node));
+ if (!new_node) {
+ err = -2;
+ goto fail;
+ }
+
+ bpf_spin_lock(&lock);
+ err = bpf_list_add(&head, &new_node->l, &node_first->l);
+ bpf_spin_unlock(&lock);
+ if (err) {
+ err = -3;
+ goto fail;
+ }
+
+fail:
+ bpf_obj_drop(node_first);
+ return err;
+}
+
+SEC("tc")
+__description("list_add_test2: list_add accepts list_front return value as prev")
+__success __retval(0)
+int list_add_test2(void *ctx)
+{
+ struct node_data *new_node, *tmp;
+ struct bpf_list_node *bpf_node;
+ long err = 0;
+
+ tmp = __add_in_list(&head, &lock);
+ if (!tmp)
+ return -1;
+
+ new_node = bpf_obj_new(typeof(*new_node));
+ if (!new_node) {
+ err = -2;
+ goto fail;
+ }
+
+ bpf_spin_lock(&lock);
+ bpf_node = bpf_list_front(&head);
+ if (!bpf_node) {
+ bpf_spin_unlock(&lock);
+ bpf_obj_drop(new_node);
+ err = -3;
+ goto fail;
+ }
+
+ err = bpf_list_add(&head, &new_node->l, bpf_node);
+ bpf_spin_unlock(&lock);
+ if (err) {
+ err = -4;
+ goto fail;
+ }
+
+fail:
+ bpf_obj_drop(tmp);
+ return err;
+}
+
+struct uninit_head_val {
+ struct bpf_spin_lock lock;
+ struct bpf_list_head head __contains(node_data, l);
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, int);
+ __type(value, struct uninit_head_val);
+ __uint(max_entries, 1);
+} uninit_head_map SEC(".maps");
+
+SEC("tc")
+__description("list_push_back_uninit_head: push_back on 0-initialized list head")
+__success __retval(0)
+int list_push_back_uninit_head(void *ctx)
+{
+ struct uninit_head_val *st;
+ struct node_data *node;
+ int ret = -1, key = 0;
+
+ st = bpf_map_lookup_elem(&uninit_head_map, &key);
+ if (!st)
+ return -1;
+
+ node = bpf_obj_new(typeof(*node));
+ if (!node)
+ return -1;
+
+ bpf_spin_lock(&st->lock);
+ ret = bpf_list_push_back(&st->head, &node->l);
+ bpf_spin_unlock(&st->lock);
+
+ return ret;
+}
+
+SEC("?tc")
+__failure __msg("bpf_spin_lock at off=32 must be held for bpf_list_head")
+long list_del_without_lock_fail(void *ctx)
+{
+ struct node_data *n;
+ struct bpf_list_node *l;
+
+ n = bpf_obj_new(typeof(*n));
+ if (!n)
+ return -1;
+
+ /* Error case: delete list node without holding lock */
+ l = bpf_list_del(&head, &n->l);
+ bpf_obj_drop(n);
+ if (!l)
+ return -2;
+ bpf_obj_drop(container_of(l, struct node_data, l));
+
+ return 0;
+}
+
+SEC("?tc")
+__failure __msg("bpf_spin_lock at off=32 must be held for bpf_list_head")
+long list_add_without_lock_fail(void *ctx)
+{
+ struct node_data *n, *prev;
+ long err;
+
+ n = bpf_obj_new(typeof(*n));
+ if (!n)
+ return -1;
+
+ prev = bpf_obj_new(typeof(*prev));
+ if (!prev) {
+ bpf_obj_drop(n);
+ return -1;
+ }
+
+ /* Error case: add list node without holding lock */
+ err = bpf_list_add(&head, &n->l, &prev->l);
+ bpf_obj_drop(prev);
+ if (err)
+ return -2;
+
+ return 0;
+}
+
+SEC("tc")
__success
long rbtree_refcounted_node_ref_escapes(void *ctx)
{
@@ -411,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);
@@ -500,7 +982,7 @@ long rbtree_wrong_owner_remove_fail_a2(void *ctx)
return 0;
}
-SEC("?fentry.s/bpf_testmod_test_read")
+SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
__success
int BPF_PROG(rbtree_sleepable_rcu,
struct file *file, struct kobject *kobj,
@@ -534,7 +1016,7 @@ err_out:
return 0;
}
-SEC("?fentry.s/bpf_testmod_test_read")
+SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
__success
int BPF_PROG(rbtree_sleepable_rcu_no_explicit_rcu_lock,
struct file *file, struct kobject *kobj,
@@ -615,13 +1097,31 @@ int percpu_hash_refcount_leak(void *ctx)
struct map_value *v;
int key = 0;
- v = bpf_map_lookup_elem(&percpu_hash, &key);
+ v = bpf_map_lookup_percpu_elem(&percpu_hash, &key, 0);
if (!v)
return 0;
return __insert_in_list(&head, &lock, &v->node);
}
+SEC("syscall")
+int clear_percpu_hash_kptr(void *ctx)
+{
+ struct node_data *n;
+ struct map_value *v;
+ int key = 0;
+
+ v = bpf_map_lookup_percpu_elem(&percpu_hash, &key, 0);
+ if (!v)
+ return 0;
+
+ n = bpf_kptr_xchg(&v->node, NULL);
+ if (!n)
+ return 0;
+ bpf_obj_drop(n);
+ return probe_read_refcount();
+}
+
SEC("tc")
int check_percpu_hash_refcount(void *ctx)
{
diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
index 836c8ab7b908..338e43822ffe 100644
--- a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
@@ -13,12 +13,56 @@ struct node_acquire {
struct bpf_refcount refcount;
};
+struct node_refcounted {
+ long key;
+ struct bpf_list_node list;
+ 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;
#define private(name) SEC(".data." #name) __hidden __attribute__((aligned(8)))
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)
{
@@ -54,7 +98,8 @@ long rbtree_refcounted_node_ref_escapes(void *ctx)
}
SEC("?tc")
-__failure __msg("Possibly NULL pointer passed to trusted arg0")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
+__msg("requires a non-NULL value of type (void *)")
long refcount_acquire_maybe_null(void *ctx)
{
struct node_acquire *n, *m;
@@ -73,6 +118,128 @@ long refcount_acquire_maybe_null(void *ctx)
}
SEC("?tc")
+__failure __msg("R1 is neither owning or non-owning ref")
+__msg("expects a pointer to a BPF-managed refcounted object, but R1 is a context pointer")
+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)
{
@@ -93,7 +260,33 @@ long rbtree_refcounted_node_ref_escapes_owning_input(void *ctx)
return 0;
}
-SEC("?fentry.s/bpf_testmod_test_read")
+SEC("?tc")
+__failure __msg("dereference of modified ptr_ ptr R1")
+long refcount_acquire_list_node_offset(void *ctx)
+{
+ struct node_refcounted *node, *base, *ref;
+ struct bpf_list_node *list_node;
+
+ node = bpf_obj_new(typeof(*node));
+ if (!node)
+ return 1;
+
+ bpf_spin_lock(&lock);
+ bpf_list_push_front(&head, &node->list);
+ list_node = bpf_list_pop_front(&head);
+ bpf_spin_unlock(&lock);
+ if (!list_node)
+ return 2;
+
+ base = container_of(list_node, struct node_refcounted, list);
+ ref = bpf_refcount_acquire(list_node);
+ if (ref)
+ bpf_obj_drop(ref);
+ bpf_obj_drop(base);
+ return 0;
+}
+
+SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
__failure __msg("function calls are not allowed while holding a lock")
int BPF_PROG(rbtree_fail_sleepable_lock_across_rcu,
struct file *file, struct kobject *kobj,
diff --git a/tools/testing/selftests/bpf/progs/rhash.c b/tools/testing/selftests/bpf/progs/rhash.c
new file mode 100644
index 000000000000..aea4de8dc781
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/rhash.c
@@ -0,0 +1,360 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <stdbool.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+
+#define ENOENT 2
+#define EEXIST 17
+
+char _license[] SEC("license") = "GPL";
+
+int err;
+
+struct elem {
+ char arr[128];
+ 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);
+ __uint(max_entries, 128);
+ __type(key, int);
+ __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)
+{
+ int key = 5;
+ struct elem empty = {.val = 3, .arr = {0}};
+ struct elem *e;
+
+ err = 1;
+ e = bpf_map_lookup_elem(&rhmap, &key);
+ if (e)
+ return 1;
+
+ err = bpf_map_update_elem(&rhmap, &key, &empty, BPF_NOEXIST);
+ if (err)
+ return 1;
+
+ e = bpf_map_lookup_elem(&rhmap, &key);
+ if (!e || e->val != empty.val) {
+ err = 2;
+ return 2;
+ }
+
+ err = 0;
+ return 0;
+}
+
+SEC("syscall")
+int test_rhash_update_delete(void *ctx)
+{
+ int key = 6;
+ struct elem empty = {.val = 4, .arr = {0}};
+ struct elem *e;
+
+ err = 1;
+ e = bpf_map_lookup_elem(&rhmap, &key);
+ if (e)
+ return 1;
+
+ err = bpf_map_update_elem(&rhmap, &key, &empty, BPF_NOEXIST);
+ if (err)
+ return 2;
+
+ err = bpf_map_delete_elem(&rhmap, &key);
+ if (err)
+ return 3;
+
+ e = bpf_map_lookup_elem(&rhmap, &key);
+ if (e) {
+ err = 4;
+ return 4;
+ }
+
+ err = 0;
+ return 0;
+}
+
+SEC("syscall")
+int test_rhash_update_elements(void *ctx)
+{
+ int key = 0;
+ struct elem empty = {.val = 4, .arr = {0}};
+ struct elem *e;
+ int i;
+
+ err = 1;
+
+ for (i = 0; i < 128; ++i) {
+ key = i;
+ e = bpf_map_lookup_elem(&rhmap, &key);
+ if (e)
+ return 1;
+
+ empty.val = key;
+ err = bpf_map_update_elem(&rhmap, &key, &empty, BPF_NOEXIST);
+ if (err)
+ return 2;
+
+ e = bpf_map_lookup_elem(&rhmap, &key);
+ if (!e || e->val != key) {
+ err = 4;
+ return 4;
+ }
+ }
+
+ for (i = 0; i < 128; ++i) {
+ key = i;
+ err = bpf_map_delete_elem(&rhmap, &key);
+ if (err)
+ return 3;
+
+ e = bpf_map_lookup_elem(&rhmap, &key);
+ if (e) {
+ err = 5;
+ return 5;
+ }
+ }
+
+ err = 0;
+ return 0;
+}
+
+SEC("syscall")
+int test_rhash_update_exist(void *ctx)
+{
+ int key = 10;
+ struct elem val1 = {.val = 100, .arr = {0}};
+ struct elem val2 = {.val = 200, .arr = {0}};
+ struct elem *e;
+ int ret;
+
+ err = 1;
+
+ /* BPF_EXIST on non-existent key should fail with -ENOENT */
+ ret = bpf_map_update_elem(&rhmap, &key, &val1, BPF_EXIST);
+ if (ret != -ENOENT)
+ return 1;
+
+ /* Insert element first */
+ ret = bpf_map_update_elem(&rhmap, &key, &val1, BPF_NOEXIST);
+ if (ret)
+ return 2;
+
+ /* Verify initial value */
+ e = bpf_map_lookup_elem(&rhmap, &key);
+ if (!e || e->val != 100)
+ return 3;
+
+ /* BPF_EXIST on existing key should succeed and update value */
+ ret = bpf_map_update_elem(&rhmap, &key, &val2, BPF_EXIST);
+ if (ret)
+ return 4;
+
+ /* Verify value was updated */
+ e = bpf_map_lookup_elem(&rhmap, &key);
+ if (!e || e->val != 200)
+ return 5;
+
+ /* Cleanup */
+ bpf_map_delete_elem(&rhmap, &key);
+ err = 0;
+ return 0;
+}
+
+SEC("syscall")
+int test_rhash_update_any(void *ctx)
+{
+ int key = 11;
+ struct elem val1 = {.val = 111, .arr = {0}};
+ struct elem val2 = {.val = 222, .arr = {0}};
+ struct elem *e;
+ int ret;
+
+ err = 1;
+
+ /* BPF_ANY on non-existent key should insert */
+ ret = bpf_map_update_elem(&rhmap, &key, &val1, BPF_ANY);
+ if (ret)
+ return 1;
+
+ e = bpf_map_lookup_elem(&rhmap, &key);
+ if (!e || e->val != 111)
+ return 2;
+
+ /* BPF_ANY on existing key should update */
+ ret = bpf_map_update_elem(&rhmap, &key, &val2, BPF_ANY);
+ if (ret)
+ return 3;
+
+ e = bpf_map_lookup_elem(&rhmap, &key);
+ if (!e || e->val != 222)
+ return 4;
+
+ /* Cleanup */
+ bpf_map_delete_elem(&rhmap, &key);
+ err = 0;
+ return 0;
+}
+
+SEC("syscall")
+int test_rhash_noexist_duplicate(void *ctx)
+{
+ int key = 12;
+ struct elem val = {.val = 600, .arr = {0}};
+ int ret;
+
+ err = 1;
+
+ /* Insert element */
+ ret = bpf_map_update_elem(&rhmap, &key, &val, BPF_NOEXIST);
+ if (ret)
+ return 1;
+
+ /* Try to insert again with BPF_NOEXIST - should fail with -EEXIST */
+ ret = bpf_map_update_elem(&rhmap, &key, &val, BPF_NOEXIST);
+ if (ret != -EEXIST)
+ return 2;
+
+ /* Cleanup */
+ bpf_map_delete_elem(&rhmap, &key);
+ err = 0;
+ return 0;
+}
+
+SEC("syscall")
+int test_rhash_delete_nonexistent(void *ctx)
+{
+ int key = 99999;
+ int ret;
+
+ err = 1;
+
+ /* Delete non-existent key should return -ENOENT */
+ ret = bpf_map_delete_elem(&rhmap, &key);
+ if (ret != -ENOENT)
+ return 1;
+
+ 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/setget_sockopt.c b/tools/testing/selftests/bpf/progs/setget_sockopt.c
index d330b1511979..d96e99b67aeb 100644
--- a/tools/testing/selftests/bpf/progs/setget_sockopt.c
+++ b/tools/testing/selftests/bpf/progs/setget_sockopt.c
@@ -69,12 +69,14 @@ static const struct sockopt_test sol_tcp_tests[] = {
static const struct sockopt_test sol_ip_tests[] = {
{ .opt = IP_TOS, .new = 0xe1, .expected = 0xe1, .tcp_expected = 0xe0, },
+ { .opt = IP_TRANSPARENT, .flip = 1, },
{ .opt = 0, },
};
static const struct sockopt_test sol_ipv6_tests[] = {
{ .opt = IPV6_TCLASS, .new = 0xe1, .expected = 0xe1, .tcp_expected = 0xe0, },
{ .opt = IPV6_AUTOFLOWLABEL, .flip = 1, },
+ { .opt = IPV6_TRANSPARENT, .flip = 1, },
{ .opt = 0, },
};
@@ -387,6 +389,24 @@ int _getsockopt(struct bpf_sockopt *ctx)
return 1;
}
+int v4mapped_v6_ip_tos_enable;
+int v4mapped_v6_ip_tos_ret;
+int v4mapped_v6_ip_tos_cnt;
+int v4mapped_v6_ip_tos_val;
+
+static void test_v4mapped_v6_ip_tos(struct bpf_sock_ops *skops)
+{
+ int tos = v4mapped_v6_ip_tos_val;
+
+ if (!v4mapped_v6_ip_tos_enable || skops->op != BPF_SOCK_OPS_TCP_CONNECT_CB)
+ return;
+ if (skops->family != AF_INET6)
+ return;
+
+ v4mapped_v6_ip_tos_cnt++;
+ v4mapped_v6_ip_tos_ret = bpf_setsockopt(skops, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
+}
+
SEC("sockops")
int skops_sockopt(struct bpf_sock_ops *skops)
{
@@ -401,6 +421,11 @@ int skops_sockopt(struct bpf_sock_ops *skops)
if (!sk)
return 1;
+ if (v4mapped_v6_ip_tos_enable) {
+ test_v4mapped_v6_ip_tos(skops);
+ return 1;
+ }
+
switch (skops->op) {
case BPF_SOCK_OPS_TCP_LISTEN_CB:
nr_listen += !(bpf_test_sockopt(skops, sk) ||
diff --git a/tools/testing/selftests/bpf/progs/sk_bypass_prot_mem.c b/tools/testing/selftests/bpf/progs/sk_bypass_prot_mem.c
index 09a00d11ffcc..bae5283fca6b 100644
--- a/tools/testing/selftests/bpf/progs/sk_bypass_prot_mem.c
+++ b/tools/testing/selftests/bpf/progs/sk_bypass_prot_mem.c
@@ -5,6 +5,7 @@
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <errno.h>
+#include "err.h"
extern int tcp_memory_per_cpu_fw_alloc __ksym;
extern int udp_memory_per_cpu_fw_alloc __ksym;
@@ -97,6 +98,7 @@ int sock_create(struct bpf_sock *ctx)
return 1;
err:
+ set_if_not_errno_or_zero(err, -EFAULT);
bpf_set_retval(err);
return 0;
}
diff --git a/tools/testing/selftests/bpf/progs/sk_storage_omem_uncharge.c b/tools/testing/selftests/bpf/progs/sk_storage_omem_uncharge.c
index 46d6eb2a3b17..c8f4815c8dfb 100644
--- a/tools/testing/selftests/bpf/progs/sk_storage_omem_uncharge.c
+++ b/tools/testing/selftests/bpf/progs/sk_storage_omem_uncharge.c
@@ -6,7 +6,6 @@
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
-void *local_storage_ptr = NULL;
void *sk_ptr = NULL;
int cookie_found = 0;
__u64 cookie = 0;
@@ -19,21 +18,17 @@ struct {
__type(value, int);
} sk_storage SEC(".maps");
-SEC("fexit/bpf_local_storage_destroy")
-int BPF_PROG(bpf_local_storage_destroy, struct bpf_local_storage *local_storage)
+SEC("fexit/bpf_sk_storage_free")
+int BPF_PROG(bpf_sk_storage_free, struct sock *sk)
{
- struct sock *sk;
-
- if (local_storage_ptr != local_storage)
+ if (sk_ptr != sk)
return 0;
- sk = bpf_core_cast(sk_ptr, struct sock);
if (sk->sk_cookie.counter != cookie)
return 0;
cookie_found++;
omem = sk->sk_omem_alloc.counter;
- local_storage_ptr = NULL;
return 0;
}
@@ -50,7 +45,6 @@ int BPF_PROG(inet6_sock_destruct, struct sock *sk)
if (value && *value == 0xdeadbeef) {
cookie_found++;
sk_ptr = sk;
- local_storage_ptr = sk->sk_bpf_storage;
}
return 0;
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/sock_destroy_prog.c b/tools/testing/selftests/bpf/progs/sock_destroy_prog.c
index 9e0bf7a54cec..0a8887543218 100644
--- a/tools/testing/selftests/bpf/progs/sock_destroy_prog.c
+++ b/tools/testing/selftests/bpf/progs/sock_destroy_prog.c
@@ -7,6 +7,8 @@
#include "bpf_tracing_net.h"
__be16 serv_port = 0;
+int tw_found = 0;
+int tw_destroy_err = 0;
int bpf_sock_destroy(struct sock_common *sk) __ksym;
@@ -100,6 +102,34 @@ int iter_tcp6_server(struct bpf_iter__tcp *ctx)
return 0;
}
+SEC("iter/tcp")
+int iter_tcp6_timewait(struct bpf_iter__tcp *ctx)
+{
+ struct sock_common *sk_common = ctx->sk_common;
+ __u64 *val;
+ int key = 0;
+
+ if (!sk_common)
+ return 0;
+
+ if (sk_common->skc_family != AF_INET6)
+ return 0;
+
+ if (!bpf_skc_to_tcp_timewait_sock(sk_common))
+ return 0;
+
+ val = bpf_map_lookup_elem(&tcp_conn_sockets, &key);
+ if (!val)
+ return 0;
+ /* The timewait sock inherits the cookie of the closed client sock. */
+ if (bpf_get_socket_cookie(sk_common) != *val)
+ return 0;
+
+ tw_found++;
+ tw_destroy_err = bpf_sock_destroy(sk_common);
+
+ return 0;
+}
SEC("iter/udp")
int iter_udp6_client(struct bpf_iter__udp *ctx)
diff --git a/tools/testing/selftests/bpf/progs/sock_ops_get_sk.c b/tools/testing/selftests/bpf/progs/sock_ops_get_sk.c
new file mode 100644
index 000000000000..3a0689f8ce7c
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/sock_ops_get_sk.c
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+/*
+ * Test the SOCK_OPS_GET_SK() and SOCK_OPS_GET_FIELD() macros in
+ * sock_ops_convert_ctx_access() when dst_reg == src_reg.
+ *
+ * When dst_reg == src_reg, the macros borrow a temporary register to load
+ * is_fullsock / is_locked_tcp_sock, because dst_reg holds the ctx pointer
+ * and cannot be clobbered before ctx->sk / ctx->field is read. If
+ * is_fullsock == 0 (e.g., TCP_NEW_SYN_RECV with a request_sock), the macro
+ * must still zero dst_reg so the verifier's PTR_TO_SOCKET_OR_NULL /
+ * SCALAR_VALUE type is correct at runtime. A missing clear leaves a stale
+ * ctx pointer in dst_reg that passes NULL checks (GET_SK) or leaks a kernel
+ * address as a scalar (GET_FIELD).
+ *
+ * When dst_reg != src_reg, dst_reg itself is used to load is_fullsock, so
+ * the JEQ (dst_reg == 0) naturally leaves it zeroed on the !fullsock path.
+ */
+
+int bug_detected;
+int null_seen;
+
+SEC("sockops")
+__naked void sock_ops_get_sk_same_reg(void)
+{
+ asm volatile (
+ "r7 = *(u32 *)(r1 + %[is_fullsock_off]);"
+ "r1 = *(u64 *)(r1 + %[sk_off]);"
+ "if r7 != 0 goto 2f;"
+ "if r1 == 0 goto 1f;"
+ "r1 = %[bug_detected] ll;"
+ "r2 = 1;"
+ "*(u32 *)(r1 + 0) = r2;"
+ "goto 2f;"
+ "1:"
+ "r1 = %[null_seen] ll;"
+ "r2 = 1;"
+ "*(u32 *)(r1 + 0) = r2;"
+ "2:"
+ "r0 = 1;"
+ "exit;"
+ :
+ : __imm_const(is_fullsock_off, offsetof(struct bpf_sock_ops, is_fullsock)),
+ __imm_const(sk_off, offsetof(struct bpf_sock_ops, sk)),
+ __imm_addr(bug_detected),
+ __imm_addr(null_seen)
+ : __clobber_all);
+}
+
+/* SOCK_OPS_GET_FIELD: same-register, is_locked_tcp_sock == 0 path. */
+int field_bug_detected;
+int field_null_seen;
+
+SEC("sockops")
+__naked void sock_ops_get_field_same_reg(void)
+{
+ asm volatile (
+ "r7 = *(u32 *)(r1 + %[is_fullsock_off]);"
+ "r1 = *(u32 *)(r1 + %[snd_cwnd_off]);"
+ "if r7 != 0 goto 2f;"
+ "if r1 == 0 goto 1f;"
+ "r1 = %[field_bug_detected] ll;"
+ "r2 = 1;"
+ "*(u32 *)(r1 + 0) = r2;"
+ "goto 2f;"
+ "1:"
+ "r1 = %[field_null_seen] ll;"
+ "r2 = 1;"
+ "*(u32 *)(r1 + 0) = r2;"
+ "2:"
+ "r0 = 1;"
+ "exit;"
+ :
+ : __imm_const(is_fullsock_off, offsetof(struct bpf_sock_ops, is_fullsock)),
+ __imm_const(snd_cwnd_off, offsetof(struct bpf_sock_ops, snd_cwnd)),
+ __imm_addr(field_bug_detected),
+ __imm_addr(field_null_seen)
+ : __clobber_all);
+}
+
+/* SOCK_OPS_GET_SK: different-register, is_fullsock == 0 path. */
+int diff_reg_bug_detected;
+int diff_reg_null_seen;
+
+SEC("sockops")
+__naked void sock_ops_get_sk_diff_reg(void)
+{
+ asm volatile (
+ "r7 = r1;"
+ "r6 = *(u32 *)(r7 + %[is_fullsock_off]);"
+ "r2 = *(u64 *)(r7 + %[sk_off]);"
+ "if r6 != 0 goto 2f;"
+ "if r2 == 0 goto 1f;"
+ "r1 = %[diff_reg_bug_detected] ll;"
+ "r3 = 1;"
+ "*(u32 *)(r1 + 0) = r3;"
+ "goto 2f;"
+ "1:"
+ "r1 = %[diff_reg_null_seen] ll;"
+ "r3 = 1;"
+ "*(u32 *)(r1 + 0) = r3;"
+ "2:"
+ "r0 = 1;"
+ "exit;"
+ :
+ : __imm_const(is_fullsock_off, offsetof(struct bpf_sock_ops, is_fullsock)),
+ __imm_const(sk_off, offsetof(struct bpf_sock_ops, sk)),
+ __imm_addr(diff_reg_bug_detected),
+ __imm_addr(diff_reg_null_seen)
+ : __clobber_all);
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/sock_read_xattr.c b/tools/testing/selftests/bpf/progs/sock_read_xattr.c
new file mode 100644
index 000000000000..c4a8eae8cc3c
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/sock_read_xattr.c
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Christian Brauner */
+
+#include <vmlinux.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_core_read.h>
+#include "bpf_experimental.h"
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+char value[16];
+int read_ret = -1;
+__u32 monitored_pid = 0;
+
+static __always_inline void read_xattr(struct socket *sock)
+{
+ struct bpf_dynptr value_ptr;
+
+ bpf_dynptr_from_mem(value, sizeof(value), 0, &value_ptr);
+ bpf_sock_read_xattr(sock, "user.bpf_test", &value_ptr);
+}
+
+SEC("lsm.s/socket_connect")
+__success
+int BPF_PROG(trusted_sock_ptr_sleepable, struct socket *sock)
+{
+ read_xattr(sock);
+ return 0;
+}
+
+SEC("lsm/socket_connect")
+__success
+int BPF_PROG(trusted_sock_ptr_non_sleepable, struct socket *sock)
+{
+ read_xattr(sock);
+ return 0;
+}
+
+SEC("lsm.s/socket_connect")
+__success
+int BPF_PROG(read_sock_xattr, struct socket *sock)
+{
+ struct bpf_dynptr value_ptr;
+ __u32 pid = bpf_get_current_pid_tgid() >> 32;
+
+ if (pid != monitored_pid)
+ return 0;
+
+ bpf_dynptr_from_mem(value, sizeof(value), 0, &value_ptr);
+ read_ret = bpf_sock_read_xattr(sock, "user.bpf_test", &value_ptr);
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/sockmap_parse_prog.c b/tools/testing/selftests/bpf/progs/sockmap_parse_prog.c
index c9abfe3a11af..56e9aebf05f2 100644
--- a/tools/testing/selftests/bpf/progs/sockmap_parse_prog.c
+++ b/tools/testing/selftests/bpf/progs/sockmap_parse_prog.c
@@ -5,28 +5,6 @@
SEC("sk_skb1")
int bpf_prog1(struct __sk_buff *skb)
{
- void *data_end = (void *)(long) skb->data_end;
- void *data = (void *)(long) skb->data;
- __u8 *d = data;
- int err;
-
- if (data + 10 > data_end) {
- err = bpf_skb_pull_data(skb, 10);
- if (err)
- return SK_DROP;
-
- data_end = (void *)(long)skb->data_end;
- data = (void *)(long)skb->data;
- if (data + 10 > data_end)
- return SK_DROP;
- }
-
- /* This write/read is a bit pointless but tests the verifier and
- * strparser handler for read/write pkt data and access into sk
- * fields.
- */
- d = data;
- d[7] = 1;
return skb->len;
}
diff --git a/tools/testing/selftests/bpf/progs/sockmap_verdict_prog.c b/tools/testing/selftests/bpf/progs/sockmap_verdict_prog.c
index 0660f29dca95..3177bc5b733a 100644
--- a/tools/testing/selftests/bpf/progs/sockmap_verdict_prog.c
+++ b/tools/testing/selftests/bpf/progs/sockmap_verdict_prog.c
@@ -44,8 +44,18 @@ int bpf_prog2(struct __sk_buff *skb)
__sink(lport);
__sink(rport);
- if (data + 8 > data_end)
- return SK_DROP;
+ if (data + 8 > data_end) {
+ if (bpf_skb_pull_data(skb, 8))
+ return SK_DROP;
+
+ data = (void *)(long)skb->data;
+ data_end = (void *)(long)skb->data_end;
+
+ if (data + 8 > data_end)
+ return SK_DROP;
+
+ d = data;
+ }
map = d[0];
sk = d[1];
diff --git a/tools/testing/selftests/bpf/progs/sockopt_sk.c b/tools/testing/selftests/bpf/progs/sockopt_sk.c
index cb990a7d3d45..5e0b27e7855c 100644
--- a/tools/testing/selftests/bpf/progs/sockopt_sk.c
+++ b/tools/testing/selftests/bpf/progs/sockopt_sk.c
@@ -149,6 +149,20 @@ int _setsockopt(struct bpf_sockopt *ctx)
if (sk && sk->family == AF_NETLINK)
goto out;
+ if (sk && sk->family == AF_INET && sk->type == SOCK_RAW) {
+ struct bpf_tcp_sock *tp = bpf_tcp_sock(sk);
+
+ if (tp) {
+ char saved_syn[60];
+
+ bpf_getsockopt(sk, SOL_TCP, TCP_SAVED_SYN,
+ &saved_syn, sizeof(saved_syn));
+ goto consumed;
+ }
+
+ goto out;
+ }
+
/* Make sure bpf_get_netns_cookie is callable.
*/
if (bpf_get_netns_cookie(NULL) == 0)
@@ -224,6 +238,8 @@ int _setsockopt(struct bpf_sockopt *ctx)
return 0; /* couldn't get sk storage */
storage->val = optval[0];
+
+consumed:
ctx->optlen = -1; /* BPF has consumed this option, don't call kernel
* setsockopt handler.
*/
diff --git a/tools/testing/selftests/bpf/progs/stack_arg.c b/tools/testing/selftests/bpf/progs/stack_arg.c
new file mode 100644
index 000000000000..944e3bb603e7
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/stack_arg.c
@@ -0,0 +1,273 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <stdbool.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_kfuncs.h"
+
+#define CLOCK_MONOTONIC 1
+
+struct timer_elem {
+ struct bpf_timer timer;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, int);
+ __type(value, struct timer_elem);
+} timer_map SEC(".maps");
+
+int timer_result;
+
+#if (defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) && \
+ defined(__BPF_FEATURE_STACK_ARGUMENT)
+
+const volatile bool has_stack_arg = true;
+
+__noinline static int static_func_many_args(int a, int b, int c, int d,
+ int e, int f, int g, int h,
+ int i, int j)
+{
+ return a + b + c + d + e + f + g + h + i + j;
+}
+
+__noinline int global_calls_many_args(int a, int b, int c)
+{
+ return static_func_many_args(a, b, c, a + 3, a + 4, a + 5, a + 6,
+ a + 7, a + 8, a + 9);
+}
+
+SEC("tc")
+int test_global_many_args(void)
+{
+ return global_calls_many_args(1, 2, 3);
+}
+
+struct test_data {
+ long x;
+ long y;
+};
+
+/* 1+2+3+4+5+6+7+8+9+10+20 = 75 */
+__noinline static long func_with_ptr_stack_arg(long a, long b, long c, long d,
+ long e, long f, long g, long h,
+ long i, struct test_data *p)
+{
+ return a + b + c + d + e + f + g + h + i + p->x + p->y;
+}
+
+__noinline long global_ptr_stack_arg(long a, long b, long c, long d, long e)
+{
+ struct test_data data = { .x = 10, .y = 20 };
+
+ return func_with_ptr_stack_arg(a, b, c, d, e, a + 5, a + 6, a + 7,
+ a + 8, &data);
+}
+
+SEC("tc")
+int test_bpf2bpf_ptr_stack_arg(void)
+{
+ return global_ptr_stack_arg(1, 2, 3, 4, 5);
+}
+
+/* 1+2+3+4+5+6+7+10+8+20 = 66 */
+__noinline static long func_with_mix_stack_args(long a, long b, long c, long d,
+ long e, long f, long g,
+ struct test_data *p,
+ long h, struct test_data *q)
+{
+ return a + b + c + d + e + f + g + p->x + h + q->y;
+}
+
+__noinline long global_mix_stack_args(long a, long b, long c, long d, long e)
+{
+ struct test_data p = { .x = 10 };
+ struct test_data q = { .y = 20 };
+
+ return func_with_mix_stack_args(a, b, c, d, e, e + 1, e + 2, &p,
+ e + 3, &q);
+}
+
+SEC("tc")
+int test_bpf2bpf_mix_stack_args(void)
+{
+ return global_mix_stack_args(1, 2, 3, 4, 5);
+}
+
+/*
+ * Nesting test: func_outer calls func_inner, both with struct pointer
+ * as stack arg.
+ *
+ * func_inner: (a+1)+...+(i+1) + p->x + p->y
+ * = 2+3+4+5+6+7+8+9+10+10+20 = 84
+ */
+__noinline static long func_inner_ptr(long a, long b, long c, long d,
+ long e, long f, long g, long h,
+ long i, struct test_data *p)
+{
+ return a + b + c + d + e + f + g + h + i + p->x + p->y;
+}
+
+__noinline static long func_outer_ptr(long a, long b, long c, long d,
+ long e, long f, long g, long h,
+ long i, struct test_data *p)
+{
+ return func_inner_ptr(a + 1, b + 1, c + 1, d + 1, e + 1,
+ f + 1, g + 1, h + 1, i + 1, p);
+}
+
+__noinline long global_nesting_ptr(long a, long b, long c, long d, long e)
+{
+ struct test_data data = { .x = 10, .y = 20 };
+
+ return func_outer_ptr(a, b, c, d, e, a + 5, a + 6, a + 7, a + 8,
+ &data);
+}
+
+SEC("tc")
+int test_bpf2bpf_nesting_stack_arg(void)
+{
+ return global_nesting_ptr(1, 2, 3, 4, 5);
+}
+
+/* 1+2+3+4+5+6+7+8+9+sizeof(pkt_v4) = 45+54 = 99 */
+__noinline static long func_with_dynptr(long a, long b, long c, long d,
+ long e, long f, long g, long h,
+ long i, struct bpf_dynptr *ptr)
+{
+ return a + b + c + d + e + f + g + h + i + bpf_dynptr_size(ptr);
+}
+
+__noinline long global_dynptr_stack_arg(void *ctx __arg_ctx, long a, long b,
+ long c, long d)
+{
+ struct bpf_dynptr ptr;
+
+ bpf_dynptr_from_skb(ctx, 0, &ptr);
+ return func_with_dynptr(a, b, c, d, d + 1, d + 2, d + 3, d + 4,
+ d + 5, &ptr);
+}
+
+SEC("tc")
+int test_bpf2bpf_dynptr_stack_arg(struct __sk_buff *skb)
+{
+ return global_dynptr_stack_arg(skb, 1, 2, 3, 4);
+}
+
+/* foo1: a+b+c+d+e+f+g+h+i+j */
+__noinline static int foo1(int a, int b, int c, int d, int e,
+ int f, int g, int h, int i, int j)
+{
+ return a + b + c + d + e + f + g + h + i + j;
+}
+
+/* foo2: a+b+c+d+e+f+g+h+i+j+k+l */
+__noinline static int foo2(int a, int b, int c, int d, int e,
+ int f, int g, int h, int i, int j,
+ int k, int l)
+{
+ return a + b + c + d + e + f + g + h + i + j + k + l;
+}
+
+/* global_two_callees calls foo1 (5 stack args) and foo2 (7 stack args).
+ * The outgoing stack arg area is sized for foo2 (the larger callee).
+ * Stores for foo1 are a subset of the area used by foo2.
+ * Result: foo1(1..10) + foo2(1..12) = 55 + 78 = 133
+ *
+ * Pass a-e through so the compiler can't constant-fold the stack args away.
+ */
+__noinline int global_two_callees(int a, int b, int c, int d, int e)
+{
+ int ret;
+
+ ret = foo1(a, b, c, d, e, a + 5, a + 6, a + 7, a + 8, a + 9);
+ ret += foo2(a, b, c, d, e, a + 5, a + 6, a + 7, a + 8, a + 9,
+ a + 10, a + 11);
+ return ret;
+}
+
+SEC("tc")
+int test_two_callees(void)
+{
+ return global_two_callees(1, 2, 3, 4, 5);
+}
+
+const volatile int timer_base = 10;
+
+static int timer_cb_many_args(void *map, int *key, struct bpf_timer *timer)
+{
+ int v = timer_base;
+
+ timer_result = static_func_many_args(v, v * 2, v * 3, v * 4, v * 5,
+ v * 6, v * 7, v * 8, v * 9,
+ v * 10);
+ return 0;
+}
+
+SEC("tc")
+int test_async_cb_many_args(void)
+{
+ struct timer_elem *elem;
+ int key = 0;
+
+ elem = bpf_map_lookup_elem(&timer_map, &key);
+ if (!elem)
+ return -1;
+
+ bpf_timer_init(&elem->timer, &timer_map, CLOCK_MONOTONIC);
+ bpf_timer_set_callback(&elem->timer, timer_cb_many_args);
+ bpf_timer_start(&elem->timer, 1, 0);
+ return 0;
+}
+
+#else
+
+const volatile bool has_stack_arg = false;
+
+SEC("tc")
+int test_global_many_args(void)
+{
+ return 0;
+}
+
+SEC("tc")
+int test_bpf2bpf_ptr_stack_arg(void)
+{
+ return 0;
+}
+
+SEC("tc")
+int test_bpf2bpf_mix_stack_args(void)
+{
+ return 0;
+}
+
+SEC("tc")
+int test_bpf2bpf_nesting_stack_arg(void)
+{
+ return 0;
+}
+
+SEC("tc")
+int test_bpf2bpf_dynptr_stack_arg(struct __sk_buff *skb)
+{
+ return 0;
+}
+
+SEC("tc")
+int test_two_callees(void)
+{
+ return 0;
+}
+
+SEC("tc")
+int test_async_cb_many_args(void)
+{
+ return 0;
+}
+
+#endif
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/stack_arg_fail.c b/tools/testing/selftests/bpf/progs/stack_arg_fail.c
new file mode 100644
index 000000000000..ad9d4bfe15dc
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/stack_arg_fail.c
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "../test_kmods/bpf_testmod_kfunc.h"
+#include "bpf_misc.h"
+
+#if defined(__BPF_FEATURE_STACK_ARGUMENT)
+
+SEC("tc")
+__failure __msg("Unrecognized *(R11-8) type STRUCT")
+int test_stack_arg_big(struct __sk_buff *skb)
+{
+ struct prog_test_big_arg s = { .a = 1, .b = 2 };
+
+ return bpf_kfunc_call_stack_arg_big(1, 2, 3, 4, 5, s);
+}
+
+SEC("socket")
+__description("r11 in ALU instruction")
+__failure __msg("R11 is invalid")
+__naked void r11_alu_reject(void)
+{
+ asm volatile (
+ "r11 += 1;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+SEC("socket")
+__description("r11 store with non-DW size")
+__failure __msg("R11 is invalid")
+__naked void r11_store_non_dw(void)
+{
+ asm volatile (
+ "*(u32 *)(r11 - 8) = r1;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+SEC("socket")
+__description("r11 store with unaligned offset")
+__failure __msg("R11 is invalid")
+__naked void r11_store_unaligned(void)
+{
+ asm volatile (
+ "*(u64 *)(r11 - 4) = r1;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+SEC("socket")
+__description("r11 store with positive offset")
+__failure __msg("R11 is invalid")
+__naked void r11_store_positive_off(void)
+{
+ asm volatile (
+ "*(u64 *)(r11 + 8) = r1;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+SEC("socket")
+__description("r11 load with negative offset")
+__failure __msg("R11 is invalid")
+__naked void r11_load_negative_off(void)
+{
+ asm volatile (
+ "r0 = *(u64 *)(r11 - 8);"
+ "exit;"
+ ::: __clobber_all);
+}
+
+SEC("socket")
+__description("r11 load with non-DW size")
+__failure __msg("R11 is invalid")
+__naked void r11_load_non_dw(void)
+{
+ asm volatile (
+ "r0 = *(u32 *)(r11 + 8);"
+ "exit;"
+ ::: __clobber_all);
+}
+
+SEC("socket")
+__description("r11 store with zero offset")
+__failure __msg("R11 is invalid")
+__naked void r11_store_zero_off(void)
+{
+ asm volatile (
+ "*(u64 *)(r11 + 0) = r1;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+#else
+
+SEC("tc")
+__description("stack_arg_fail: not supported, dummy test")
+__success
+int test_stack_arg_big(struct __sk_buff *skb)
+{
+ return 0;
+}
+
+#endif
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/stack_arg_kfunc.c b/tools/testing/selftests/bpf/progs/stack_arg_kfunc.c
new file mode 100644
index 000000000000..345f2da2e361
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/stack_arg_kfunc.c
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_kfuncs.h"
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+#if (defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) && \
+ defined(__BPF_FEATURE_STACK_ARGUMENT)
+
+const volatile bool has_stack_arg = true;
+
+struct bpf_iter_testmod_seq {
+ u64 :64;
+ u64 :64;
+};
+
+extern int bpf_iter_testmod_seq_new(struct bpf_iter_testmod_seq *it, s64 value, int cnt) __ksym;
+extern void bpf_iter_testmod_seq_destroy(struct bpf_iter_testmod_seq *it) __ksym;
+
+struct timer_map_value {
+ struct bpf_timer timer;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, int);
+ __type(value, struct timer_map_value);
+} kfunc_timer_map SEC(".maps");
+
+SEC("tc")
+int test_stack_arg_scalar(struct __sk_buff *skb)
+{
+ return bpf_kfunc_call_stack_arg(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+}
+
+SEC("tc")
+int test_stack_arg_ptr(struct __sk_buff *skb)
+{
+ struct prog_test_pass1 p = { .x0 = 10, .x1 = 20 };
+
+ return bpf_kfunc_call_stack_arg_ptr(1, 2, 3, 4, 5, 6, 7, 8, 9, &p);
+}
+
+SEC("tc")
+int test_stack_arg_mix(struct __sk_buff *skb)
+{
+ struct prog_test_pass1 p = { .x0 = 10 };
+ struct prog_test_pass1 q = { .x1 = 20 };
+
+ return bpf_kfunc_call_stack_arg_mix(1, 2, 3, 4, 5, 6, 7, &p, 8, &q);
+}
+
+/* 1+2+3+4+5+6+7+8+9+sizeof(pkt_v4) = 45+54 = 99 */
+SEC("tc")
+int test_stack_arg_dynptr(struct __sk_buff *skb)
+{
+ struct bpf_dynptr ptr;
+
+ bpf_dynptr_from_skb(skb, 0, &ptr);
+ return bpf_kfunc_call_stack_arg_dynptr(1, 2, 3, 4, 5, 6, 7, 8, 9, &ptr);
+}
+
+/* 1 + 2 + 3 + 4 + 5 + (1 + 2 + ... + 16) = 15 + 136 = 151 */
+SEC("tc")
+int test_stack_arg_mem(struct __sk_buff *skb)
+{
+ char buf[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+
+ return bpf_kfunc_call_stack_arg_mem(1, 2, 3, 4, 5, buf, sizeof(buf));
+}
+
+/* 1+2+3+4+5+6+7+8+9+100 = 145 */
+SEC("tc")
+int test_stack_arg_iter(struct __sk_buff *skb)
+{
+ struct bpf_iter_testmod_seq it;
+ u64 ret;
+
+ bpf_iter_testmod_seq_new(&it, 100, 10);
+ ret = bpf_kfunc_call_stack_arg_iter(1, 2, 3, 4, 5, 6, 7, 8, 9, &it);
+ bpf_iter_testmod_seq_destroy(&it);
+ return ret;
+}
+
+const char cstr[] = "hello";
+
+/* 1+2+3+4+5+6+7+8+9 = 45 */
+SEC("tc")
+int test_stack_arg_const_str(struct __sk_buff *skb)
+{
+ return bpf_kfunc_call_stack_arg_const_str(1, 2, 3, 4, 5, 6, 7, 8, 9,
+ cstr);
+}
+
+/* 1+2+3+4+5+6+7+8+9 = 45 */
+SEC("tc")
+int test_stack_arg_timer(struct __sk_buff *skb)
+{
+ struct timer_map_value *val;
+ int key = 0;
+
+ val = bpf_map_lookup_elem(&kfunc_timer_map, &key);
+ if (!val)
+ return 0;
+ return bpf_kfunc_call_stack_arg_timer(1, 2, 3, 4, 5, 6, 7, 8, 9,
+ &val->timer);
+}
+
+#else
+
+const volatile bool has_stack_arg = false;
+
+SEC("tc")
+int test_stack_arg_scalar(struct __sk_buff *skb)
+{
+ return 0;
+}
+
+SEC("tc")
+int test_stack_arg_ptr(struct __sk_buff *skb)
+{
+ return 0;
+}
+
+SEC("tc")
+int test_stack_arg_mix(struct __sk_buff *skb)
+{
+ return 0;
+}
+
+SEC("tc")
+int test_stack_arg_dynptr(struct __sk_buff *skb)
+{
+ return 0;
+}
+
+SEC("tc")
+int test_stack_arg_mem(struct __sk_buff *skb)
+{
+ return 0;
+}
+
+SEC("tc")
+int test_stack_arg_iter(struct __sk_buff *skb)
+{
+ return 0;
+}
+
+SEC("tc")
+int test_stack_arg_const_str(struct __sk_buff *skb)
+{
+ return 0;
+}
+
+SEC("tc")
+int test_stack_arg_timer(struct __sk_buff *skb)
+{
+ return 0;
+}
+
+#endif
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/stack_arg_precision.c b/tools/testing/selftests/bpf/progs/stack_arg_precision.c
new file mode 100644
index 000000000000..bee2eeec021d
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/stack_arg_precision.c
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "../test_kmods/bpf_testmod_kfunc.h"
+#include "bpf_misc.h"
+
+#if (defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) && \
+ defined(__BPF_FEATURE_STACK_ARGUMENT)
+
+/* Force kfunc extern BTF generation for inline asm call below.
+ * Uses its own SEC so it's not included as a .text subprog.
+ * The '?' prefix sets autoload=false so libbpf won't load it.
+ */
+SEC("?tc")
+int __btf_kfunc_gen(struct __sk_buff *ctx)
+{
+ char buf[8] = {};
+
+ return bpf_kfunc_call_stack_arg_mem(0, 0, 0, 0, 0, buf, sizeof(buf));
+}
+
+/*
+ * Test precision backtracking across bpf-to-bpf call for kfunc stack arg.
+ * subprog_call_mem_kfunc receives a size as incoming stack arg (arg6)
+ * and forwards it as mem__sz (arg7) to bpf_kfunc_call_stack_arg_mem.
+ */
+__naked __noinline __used
+static long subprog_call_mem_kfunc(long a, long b, long c, long d, long e, long size)
+{
+ asm volatile (
+ "r1 = *(u64 *)(r11 + 8);" /* r1 = incoming arg6 (size) */
+ "r2 = 0x0807060504030201 ll;" /* r2 = buf contents */
+ "*(u64 *)(r10 - 8) = r2;" /* store buf to stack */
+ "r2 = r10;"
+ "r2 += -8;" /* r2 = &buf */
+ "*(u64 *)(r11 - 8) = r2;" /* outgoing arg6 = buf */
+ "*(u64 *)(r11 - 16) = r1;" /* outgoing arg7 = size */
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ "call %[bpf_kfunc_call_stack_arg_mem];"
+ "exit;"
+ :
+ : __imm(bpf_kfunc_call_stack_arg_mem)
+ : __clobber_all
+ );
+}
+
+SEC("tc")
+__description("stack_arg: precision backtracking across bpf2bpf call for kfunc")
+__success
+__log_level(2)
+__flag(BPF_F_TEST_STATE_FREQ)
+__btf_func_path("btf__stack_arg_precision.bpf.o")
+__msg("mark_precise: frame1: last_idx 26 first_idx 13 subseq_idx -1")
+__msg("mark_precise: frame1: regs= stack= before 25: (b7) r5 = 5")
+__msg("mark_precise: frame1: regs= stack= before 24: (b7) r4 = 4")
+__msg("mark_precise: frame1: regs= stack= before 23: (b7) r3 = 3")
+__msg("mark_precise: frame1: regs= stack= before 22: (b7) r2 = 2")
+__msg("mark_precise: frame1: regs= stack= before 21: (b7) r1 = 1")
+__msg("mark_precise: frame1: regs= stack= before 20: (7b) *(u64 *)(r11 -16) = r1")
+__msg("mark_precise: frame1: regs=r1 stack= before 19: (7b) *(u64 *)(r11 -8) = r2")
+__msg("mark_precise: frame1: regs=r1 stack= before 18: (07) r2 += -8")
+__msg("mark_precise: frame1: regs=r1 stack= before 17: (bf) r2 = r10")
+__msg("mark_precise: frame1: regs=r1 stack= before 16: (7b) *(u64 *)(r10 -8) = r2")
+__msg("mark_precise: frame1: regs=r1 stack= before 14: (18) r2 = 0x807060504030201")
+__msg("mark_precise: frame1: regs=r1 stack= before 13: (79) r1 = *(u64 *)(r11 +8)")
+__msg("mark_precise: frame1: parent state regs= stack=: frame1: R10=fp0")
+__msg("mark_precise: frame0: parent state regs= stack=: R10=fp0")
+__msg("mark_precise: frame1: last_idx 11 first_idx 11 subseq_idx 13")
+__msg("mark_precise: frame1: regs= stack= before 11: (85) call pc+1")
+__msg("mark_precise: frame0: parent state regs= stack=: R1=1 R2=2 R3=3 R4=4 R5=5 R10=fp0")
+__msg("mark_precise: frame0: last_idx 9 first_idx 7 subseq_idx 11")
+__msg("mark_precise: frame0: regs= stack= before 9: (05) goto pc+1")
+__msg("mark_precise: frame0: regs= stack= before 8: (7a) *(u64 *)(r11 -8) = 4")
+__msg("mark_precise: frame1: last_idx 26 first_idx 13 subseq_idx -1 ")
+__msg("mark_precise: frame1: regs= stack= before 25: (b7) r5 = 5")
+__msg("mark_precise: frame1: regs= stack= before 24: (b7) r4 = 4")
+__msg("mark_precise: frame1: regs= stack= before 23: (b7) r3 = 3")
+__msg("mark_precise: frame1: regs= stack= before 22: (b7) r2 = 2")
+__msg("mark_precise: frame1: regs= stack= before 21: (b7) r1 = 1")
+__msg("mark_precise: frame1: regs= stack= before 20: (7b) *(u64 *)(r11 -16) = r1")
+__msg("mark_precise: frame1: regs=r1 stack= before 19: (7b) *(u64 *)(r11 -8) = r2")
+__msg("mark_precise: frame1: regs=r1 stack= before 18: (07) r2 += -8")
+__msg("mark_precise: frame1: regs=r1 stack= before 17: (bf) r2 = r10")
+__msg("mark_precise: frame1: regs=r1 stack= before 16: (7b) *(u64 *)(r10 -8) = r2")
+__msg("mark_precise: frame1: regs=r1 stack= before 14: (18) r2 = 0x807060504030201")
+__msg("mark_precise: frame1: regs=r1 stack= before 13: (79) r1 = *(u64 *)(r11 +8)")
+__msg("mark_precise: frame1: parent state regs= stack=: frame1: R10=fp0")
+__msg("mark_precise: frame0: parent state regs= stack=: R10=fp0")
+__msg("mark_precise: frame1: last_idx 11 first_idx 11 subseq_idx 13 ")
+__msg("mark_precise: frame1: regs= stack= before 11: (85) call pc+1")
+__msg("mark_precise: frame0: parent state regs= stack=: R1=1 R2=2 R3=3 R4=4 R5=5 R10=fp0")
+__msg("mark_precise: frame0: last_idx 10 first_idx 10 subseq_idx 11 ")
+__msg("mark_precise: frame0: regs= stack= before 10: (7a) *(u64 *)(r11 -8) = 6")
+__naked void stack_arg_precision_bpf2bpf(void)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "r6 = r0;"
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ "if r6 < 2 goto l0_%=;"
+ "*(u64 *)(r11 - 8) = 4;"
+ "goto l1_%=;"
+ "l0_%=:"
+ "*(u64 *)(r11 - 8) = 6;"
+ "l1_%=:"
+ "call subprog_call_mem_kfunc;"
+ "exit;"
+ :: __imm(bpf_get_prandom_u32)
+ : __clobber_all
+ );
+}
+
+#else
+
+SEC("socket")
+__description("stack_arg_precision: not supported, dummy test")
+__success
+int dummy_test(void)
+{
+ return 0;
+}
+
+#endif
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/stacktrace_ips.c b/tools/testing/selftests/bpf/progs/stacktrace_ips.c
index a96c8150d7f5..6830f2978613 100644
--- a/tools/testing/selftests/bpf/progs/stacktrace_ips.c
+++ b/tools/testing/selftests/bpf/progs/stacktrace_ips.c
@@ -31,6 +31,13 @@ int unused(void)
__u32 stack_key;
+SEC("kprobe")
+int kprobe_test(struct pt_regs *ctx)
+{
+ stack_key = bpf_get_stackid(ctx, &stackmap, 0);
+ return 0;
+}
+
SEC("kprobe.multi")
int kprobe_multi_test(struct pt_regs *ctx)
{
@@ -46,4 +53,24 @@ int rawtp_test(void *ctx)
return 0;
}
+SEC("fentry/bpf_testmod_stacktrace_test")
+int fentry_test(struct pt_regs *ctx)
+{
+ /*
+ * Skip 2 bpf_program/trampoline stack entries:
+ * - bpf_prog_bd1f7a949f55fb03_fentry_test
+ * - bpf_trampoline_182536277701
+ */
+ stack_key = bpf_get_stackid(ctx, &stackmap, 2);
+ return 0;
+}
+
+SEC("fexit/bpf_testmod_stacktrace_test")
+int fexit_test(struct pt_regs *ctx)
+{
+ /* Skip 2 bpf_program/trampoline stack entries, check fentry_test. */
+ stack_key = bpf_get_stackid(ctx, &stackmap, 2);
+ return 0;
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/stream.c b/tools/testing/selftests/bpf/progs/stream.c
index 4a5bd852f10c..8e8e1339dc74 100644
--- a/tools/testing/selftests/bpf/progs/stream.c
+++ b/tools/testing/selftests/bpf/progs/stream.c
@@ -5,7 +5,7 @@
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
#include "bpf_experimental.h"
-#include "bpf_arena_common.h"
+#include <bpf_arena_common.h>
struct arr_elem {
struct bpf_res_spin_lock lock;
@@ -42,6 +42,10 @@ int size;
u64 fault_addr;
void *arena_ptr;
+#define private(name) SEC(".bss." #name) __hidden __attribute__((aligned(8)))
+
+private(STREAM) struct bpf_spin_lock block;
+
SEC("syscall")
__success __retval(0)
int stream_exhaust(void *ctx)
@@ -60,6 +64,8 @@ SEC("syscall")
__arch_x86_64
__arch_arm64
__arch_s390x
+__arch_riscv64
+__arch_loongarch
__success __retval(0)
__stderr("ERROR: Timeout detected for may_goto instruction")
__stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}")
@@ -180,6 +186,151 @@ int stream_arena_read_fault(void *ctx)
return 0;
}
+SEC("syscall")
+__arch_x86_64
+__arch_arm64
+__success __retval(0)
+__stderr("ERROR: Arena READ access at unmapped address 0x{{.*}}")
+__stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}")
+__stderr("Call trace:\n"
+"{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n"
+"|[ \t]+[^\n]+\n)*}}")
+int stream_arena_load_acquire_fault(void *ctx)
+{
+ static const struct bpf_insn load_acquire_insn = {
+ .code = 0xc3, /* BPF_STX | BPF_ATOMIC | BPF_W */
+ .dst_reg = 0, /* BPF_REG_0 */
+ .src_reg = 1, /* BPF_REG_1 */
+ .off = 0x7fff,
+ .imm = 0x100, /* BPF_LOAD_ACQ */
+ };
+ struct bpf_arena *ptr = (void *)&arena;
+ u64 user_vm_start, val;
+
+ /*
+ * Prevent GCC bounds warning: casting &arena to struct bpf_arena *
+ * triggers bounds checking since the map definition is smaller than
+ * struct bpf_arena. barrier_var() makes the pointer opaque to GCC,
+ * preventing the bounds analysis.
+ */
+ barrier_var(ptr);
+ user_vm_start = ptr->user_vm_start;
+ fault_addr = user_vm_start + 0x7fff;
+ bpf_addr_space_cast(user_vm_start, 0, 1);
+ asm volatile (
+ "r1 = %[user_vm_start];"
+ "r0 = 1;"
+ ".8byte %[load_acquire_insn];" /* r0 = load_acquire((u32 *)(r1 + 0x7fff)) */
+ "%[val] = r0;"
+ : [val] "=r" (val)
+ : [user_vm_start] "r" (user_vm_start),
+ __imm_insn(load_acquire_insn, load_acquire_insn)
+ : "r0", "r1"
+ );
+ return val;
+}
+
+SEC("syscall")
+__arch_x86_64
+__arch_arm64
+__success __retval(0)
+__stderr("ERROR: Arena WRITE access at unmapped address 0x{{.*}}")
+__stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}")
+__stderr("Call trace:\n"
+"{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n"
+"|[ \t]+[^\n]+\n)*}}")
+int stream_arena_xchg_fault(void *ctx)
+{
+ static const struct bpf_insn xchg_insn = {
+ .code = 0xc3, /* BPF_STX | BPF_ATOMIC | BPF_W */
+ .dst_reg = 1, /* BPF_REG_1 */
+ .src_reg = 2, /* BPF_REG_2 */
+ .off = 0x7fff,
+ .imm = 0xe1, /* BPF_XCHG */
+ };
+ struct bpf_arena *ptr = (void *)&arena;
+ u64 user_vm_start, val;
+
+ /*
+ * Prevent GCC bounds warning: casting &arena to struct bpf_arena *
+ * triggers bounds checking since the map definition is smaller than
+ * struct bpf_arena. barrier_var() makes the pointer opaque to GCC,
+ * preventing the bounds analysis.
+ */
+ barrier_var(ptr);
+ user_vm_start = ptr->user_vm_start;
+ fault_addr = user_vm_start + 0x7fff;
+ bpf_addr_space_cast(user_vm_start, 0, 1);
+ /*
+ * A read-modify-write carrying BPF_FETCH writes to memory, so the fault
+ * has to be reported as a WRITE from the dst_reg address, but it also
+ * reads the old value into src_reg, so the exception handler has to
+ * clear src_reg. Poison it up front, the returned value must be 0.
+ */
+ asm volatile (
+ "r1 = %[user_vm_start];"
+ "r2 = 1;"
+ ".8byte %[xchg_insn];" /* r2 = xchg((u32 *)(r1 + 0x7fff), r2) */
+ "%[val] = r2;"
+ : [val] "=r" (val)
+ : [user_vm_start] "r" (user_vm_start),
+ __imm_insn(xchg_insn, xchg_insn)
+ : "r1", "r2"
+ );
+ return val;
+}
+
+SEC("syscall")
+__arch_x86_64
+__arch_arm64
+__success __retval(0)
+__stderr("ERROR: Arena WRITE access at unmapped address 0x{{.*}}")
+__stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}")
+__stderr("Call trace:\n"
+"{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n"
+"|[ \t]+[^\n]+\n)*}}")
+int stream_arena_cmpxchg_fault(void *ctx)
+{
+ static const struct bpf_insn cmpxchg_insn = {
+ .code = 0xc3, /* BPF_STX | BPF_ATOMIC | BPF_W */
+ .dst_reg = 1, /* BPF_REG_1 */
+ .src_reg = 2, /* BPF_REG_2 */
+ .off = 0x7fff,
+ .imm = 0xf1, /* BPF_CMPXCHG */
+ };
+ struct bpf_arena *ptr = (void *)&arena;
+ u64 user_vm_start, val;
+
+ /*
+ * Prevent GCC bounds warning: casting &arena to struct bpf_arena *
+ * triggers bounds checking since the map definition is smaller than
+ * struct bpf_arena. barrier_var() makes the pointer opaque to GCC,
+ * preventing the bounds analysis.
+ */
+ barrier_var(ptr);
+ user_vm_start = ptr->user_vm_start;
+ fault_addr = user_vm_start + 0x7fff;
+ bpf_addr_space_cast(user_vm_start, 0, 1);
+ /*
+ * Same as the exchange above, except that a BPF_CMPXCHG reads the old
+ * value into r0 rather than into src_reg, so r0 is the register the
+ * exception handler has to clear. It doubles as the compare value, but
+ * the comparison never happens since the access faults first.
+ */
+ asm volatile (
+ "r1 = %[user_vm_start];"
+ "r0 = 1;"
+ "r2 = 2;"
+ ".8byte %[cmpxchg_insn];" /* r0 = cmpxchg((u32 *)(r1 + 0x7fff), r0, r2) */
+ "%[val] = r0;"
+ : [val] "=r" (val)
+ : [user_vm_start] "r" (user_vm_start),
+ __imm_insn(cmpxchg_insn, cmpxchg_insn)
+ : "r0", "r1", "r2"
+ );
+ return val;
+}
+
static __noinline void subprog(void)
{
int __arena *addr = (int __arena *)0xdeadbeef;
@@ -234,4 +385,53 @@ int stream_arena_callback_fault(void *ctx)
return 0;
}
+SEC("syscall")
+__arch_x86_64
+__arch_arm64
+__success __retval(0)
+__stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}")
+__stderr("Call trace:\n"
+"{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n"
+"|[ \t]+[^\n]+\n)*}}")
+int stream_print_stack_kfunc(void *ctx)
+{
+ return bpf_stream_print_stack(BPF_STDERR);
+}
+
+SEC("syscall")
+__success __retval(-2)
+int stream_print_stack_invalid_id(void *ctx)
+{
+ /* Try to pass an invalid stream ID. */
+ return bpf_stream_print_stack((enum bpf_stream_id)0xbadcafe);
+}
+
+SEC("syscall")
+__arch_x86_64
+__arch_arm64
+__success __retval(0)
+__stdout(_STR)
+__stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}")
+__stderr("Call trace:\n"
+"{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n"
+"|[ \t]+[^\n]+\n)*}}")
+int stream_print_kfuncs_locked(void *ctx)
+{
+ int ret;
+
+ bpf_spin_lock(&block);
+
+ ret = bpf_stream_printk(BPF_STDOUT, _STR);
+ if (ret)
+ goto out;
+
+ ret = bpf_stream_print_stack(BPF_STDERR);
+
+out:
+ bpf_spin_unlock(&block);
+
+ return ret;
+}
+
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/stream_fail.c b/tools/testing/selftests/bpf/progs/stream_fail.c
index 3662515f0107..21428bb1ee59 100644
--- a/tools/testing/selftests/bpf/progs/stream_fail.c
+++ b/tools/testing/selftests/bpf/progs/stream_fail.c
@@ -10,7 +10,7 @@ SEC("syscall")
__failure __msg("Possibly NULL pointer passed")
int stream_vprintk_null_arg(void *ctx)
{
- bpf_stream_vprintk_impl(BPF_STDOUT, "", NULL, 0, NULL);
+ bpf_stream_vprintk(BPF_STDOUT, "", NULL, 0);
return 0;
}
@@ -18,15 +18,15 @@ SEC("syscall")
__failure __msg("R3 type=scalar expected=")
int stream_vprintk_scalar_arg(void *ctx)
{
- bpf_stream_vprintk_impl(BPF_STDOUT, "", (void *)46, 0, NULL);
+ bpf_stream_vprintk(BPF_STDOUT, "", (void *)46, 0);
return 0;
}
SEC("syscall")
-__failure __msg("arg#1 doesn't point to a const string")
+__failure __msg("R2 doesn't point to a const string")
int stream_vprintk_string_arg(void *ctx)
{
- bpf_stream_vprintk_impl(BPF_STDOUT, ctx, NULL, 0, NULL);
+ bpf_stream_vprintk(BPF_STDOUT, ctx, NULL, 0);
return 0;
}
diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c b/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
index 826e6b6aff7e..bddc4e8579d2 100644
--- a/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
+++ b/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
@@ -33,6 +33,8 @@ SEC("syscall") __retval(USER_PTR_ERR) int test_strcmp_null1(void *ctx) { return
SEC("syscall") __retval(USER_PTR_ERR)int test_strcmp_null2(void *ctx) { return bpf_strcmp("hello", NULL); }
SEC("syscall") __retval(USER_PTR_ERR) int test_strcasecmp_null1(void *ctx) { return bpf_strcasecmp(NULL, "HELLO"); }
SEC("syscall") __retval(USER_PTR_ERR)int test_strcasecmp_null2(void *ctx) { return bpf_strcasecmp("HELLO", NULL); }
+SEC("syscall") __retval(USER_PTR_ERR)int test_strncasecmp_null1(void *ctx) { return bpf_strncasecmp(NULL, "HELLO", 5); }
+SEC("syscall") __retval(USER_PTR_ERR)int test_strncasecmp_null2(void *ctx) { return bpf_strncasecmp("HELLO", NULL, 5); }
SEC("syscall") __retval(USER_PTR_ERR)int test_strchr_null(void *ctx) { return bpf_strchr(NULL, 'a'); }
SEC("syscall") __retval(USER_PTR_ERR)int test_strchrnul_null(void *ctx) { return bpf_strchrnul(NULL, 'a'); }
SEC("syscall") __retval(USER_PTR_ERR)int test_strnchr_null(void *ctx) { return bpf_strnchr(NULL, 1, 'a'); }
@@ -57,6 +59,8 @@ SEC("syscall") __retval(USER_PTR_ERR) int test_strcmp_user_ptr1(void *ctx) { ret
SEC("syscall") __retval(USER_PTR_ERR) int test_strcmp_user_ptr2(void *ctx) { return bpf_strcmp("hello", user_ptr); }
SEC("syscall") __retval(USER_PTR_ERR) int test_strcasecmp_user_ptr1(void *ctx) { return bpf_strcasecmp(user_ptr, "HELLO"); }
SEC("syscall") __retval(USER_PTR_ERR) int test_strcasecmp_user_ptr2(void *ctx) { return bpf_strcasecmp("HELLO", user_ptr); }
+SEC("syscall") __retval(USER_PTR_ERR) int test_strncasecmp_user_ptr1(void *ctx) { return bpf_strncasecmp(user_ptr, "HELLO", 5); }
+SEC("syscall") __retval(USER_PTR_ERR) int test_strncasecmp_user_ptr2(void *ctx) { return bpf_strncasecmp("HELLO", user_ptr, 5); }
SEC("syscall") __retval(USER_PTR_ERR) int test_strchr_user_ptr(void *ctx) { return bpf_strchr(user_ptr, 'a'); }
SEC("syscall") __retval(USER_PTR_ERR) int test_strchrnul_user_ptr(void *ctx) { return bpf_strchrnul(user_ptr, 'a'); }
SEC("syscall") __retval(USER_PTR_ERR) int test_strnchr_user_ptr(void *ctx) { return bpf_strnchr(user_ptr, 1, 'a'); }
@@ -83,6 +87,8 @@ SEC("syscall") __retval(-EFAULT) int test_strcmp_pagefault1(void *ctx) { return
SEC("syscall") __retval(-EFAULT) int test_strcmp_pagefault2(void *ctx) { return bpf_strcmp("hello", invalid_kern_ptr); }
SEC("syscall") __retval(-EFAULT) int test_strcasecmp_pagefault1(void *ctx) { return bpf_strcasecmp(invalid_kern_ptr, "HELLO"); }
SEC("syscall") __retval(-EFAULT) int test_strcasecmp_pagefault2(void *ctx) { return bpf_strcasecmp("HELLO", invalid_kern_ptr); }
+SEC("syscall") __retval(-EFAULT) int test_strncasecmp_pagefault1(void *ctx) { return bpf_strncasecmp(invalid_kern_ptr, "HELLO", 5); }
+SEC("syscall") __retval(-EFAULT) int test_strncasecmp_pagefault2(void *ctx) { return bpf_strncasecmp("HELLO", invalid_kern_ptr, 5); }
SEC("syscall") __retval(-EFAULT) int test_strchr_pagefault(void *ctx) { return bpf_strchr(invalid_kern_ptr, 'a'); }
SEC("syscall") __retval(-EFAULT) int test_strchrnul_pagefault(void *ctx) { return bpf_strchrnul(invalid_kern_ptr, 'a'); }
SEC("syscall") __retval(-EFAULT) int test_strnchr_pagefault(void *ctx) { return bpf_strnchr(invalid_kern_ptr, 1, 'a'); }
diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c b/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
index 05e1da1f250f..412c53b87b18 100644
--- a/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
+++ b/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
@@ -8,6 +8,7 @@ char long_str[XATTR_SIZE_MAX + 1];
SEC("syscall") int test_strcmp_too_long(void *ctx) { return bpf_strcmp(long_str, long_str); }
SEC("syscall") int test_strcasecmp_too_long(void *ctx) { return bpf_strcasecmp(long_str, long_str); }
+SEC("syscall") int test_strncasecmp_too_long(void *ctx) { return bpf_strncasecmp(long_str, long_str, sizeof(long_str)); }
SEC("syscall") int test_strchr_too_long(void *ctx) { return bpf_strchr(long_str, 'b'); }
SEC("syscall") int test_strchrnul_too_long(void *ctx) { return bpf_strchrnul(long_str, 'b'); }
SEC("syscall") int test_strnchr_too_long(void *ctx) { return bpf_strnchr(long_str, sizeof(long_str), 'b'); }
diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_success.c b/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
index a8513964516b..f65b1226a81a 100644
--- a/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
+++ b/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
@@ -17,6 +17,13 @@ __test(0) int test_strcasecmp_eq2(void *ctx) { return bpf_strcasecmp(str, "HELLO
__test(0) int test_strcasecmp_eq3(void *ctx) { return bpf_strcasecmp(str, "HELLO world"); }
__test(1) int test_strcasecmp_neq1(void *ctx) { return bpf_strcasecmp(str, "hello"); }
__test(1) int test_strcasecmp_neq2(void *ctx) { return bpf_strcasecmp(str, "HELLO"); }
+__test(0) int test_strncasecmp_eq1(void *ctx) { return bpf_strncasecmp(str, "hello world", 11); }
+__test(0) int test_strncasecmp_eq2(void *ctx) { return bpf_strncasecmp(str, "HELLO WORLD", 11); }
+__test(0) int test_strncasecmp_eq3(void *ctx) { return bpf_strncasecmp(str, "HELLO world", 11); }
+__test(0) int test_strncasecmp_eq4(void *ctx) { return bpf_strncasecmp(str, "hello", 5); }
+__test(0) int test_strncasecmp_eq5(void *ctx) { return bpf_strncasecmp(str, "hello world!", 11); }
+__test(-1) int test_strncasecmp_neq1(void *ctx) { return bpf_strncasecmp(str, "hello!", 6); }
+__test(1) int test_strncasecmp_neq2(void *ctx) { return bpf_strncasecmp(str, "abc", 3); }
__test(1) int test_strchr_found(void *ctx) { return bpf_strchr(str, 'e'); }
__test(11) int test_strchr_null(void *ctx) { return bpf_strchr(str, '\0'); }
__test(-ENOENT) int test_strchr_notfound(void *ctx) { return bpf_strchr(str, 'x'); }
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_arena.c b/tools/testing/selftests/bpf/progs/struct_ops_arena.c
new file mode 100644
index 000000000000..8aa8639df91f
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_arena.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#define BPF_NO_KFUNC_PROTOTYPES
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_experimental.h"
+#include <bpf_arena_common.h>
+#include "../test_kmods/bpf_testmod.h"
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+char _license[] SEC("license") = "GPL";
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARENA);
+ __uint(map_flags, BPF_F_MMAPABLE);
+ /* page 0 hosts the arena globals, page 1 is for allocations */
+ __uint(max_entries, 2);
+} arena SEC(".maps");
+
+/* also associates the callbacks with the arena */
+u64 __arena arena_touch;
+/* raw value of the last __arena ctx argument, captured by test_arena_cb */
+u64 __arena cb_ptr_val;
+
+SEC("struct_ops/test_arena")
+int test_arena_cb(unsigned long long *ctx)
+{
+ u64 __arena *ptr = (u64 __arena *)ctx[0];
+
+ arena_touch++;
+ cb_ptr_val = ctx[0];
+ *ptr += 1;
+ return 0;
+}
+
+SEC("struct_ops/test_arena_nullable")
+int test_arena_nullable_cb(unsigned long long *ctx)
+{
+ u64 __arena *ptr = (u64 __arena *)ctx[0];
+
+ arena_touch++;
+ if (!ptr)
+ return 0xbee;
+ *ptr += 1;
+ return 0;
+}
+
+SEC("struct_ops/test_arena_stack")
+int test_arena_stack_cb(unsigned long long *ctx)
+{
+ u64 __arena *ptr = (u64 __arena *)ctx[8];
+
+ arena_touch++;
+ /* pin the slot layout: the leading args fill ctx[0]..ctx[7] */
+ if (ctx[0] != 1 || ctx[7] != 8)
+ return 0xbad;
+ *ptr += 1;
+ return 0;
+}
+
+SEC("struct_ops/test_arena_multislot")
+int test_arena_multislot_cb(unsigned long long *ctx)
+{
+ u64 __arena *ptr = (u64 __arena *)ctx[2];
+
+ arena_touch++;
+ /*
+ * The 16-byte struct occupies ctx[0] and ctx[1], so @ptr is argument
+ * one but slot two. Getting that wrong hands the callback a scalar.
+ */
+ if (ctx[0] != 11 || ctx[1] != 22)
+ return 0xbad;
+ *ptr += 1;
+ return 0;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_ops3 testmod_arena = {
+ .test_arena = (void *)test_arena_cb,
+ .test_arena_nullable = (void *)test_arena_nullable_cb,
+ .test_arena_stack = (void *)test_arena_stack_cb,
+ .test_arena_multislot = (void *)test_arena_multislot_cb,
+};
+
+SEC("syscall")
+int trigger(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ u64 __arena *val;
+ int ret;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ *val = 41;
+ ret = bpf_testmod_ops3_call_test_arena((u64 *)val);
+ if (ret)
+ return 2;
+ if (*val != 42)
+ return 3;
+
+ /*
+ * The callback must have seen exactly (u32)(kaddr - kern_vm_start),
+ * which is the arena offset of val with the upper 32 bits clear.
+ */
+ if (cb_ptr_val != (u32)(u64)val)
+ return 4;
+
+ ret = bpf_testmod_ops3_call_test_arena_nullable((u64 *)val);
+ if (ret)
+ return 5;
+ if (*val != 43)
+ return 6;
+
+ /* NULL survives the nullable kfunc and the trampoline as NULL */
+ ret = bpf_testmod_ops3_call_test_arena_nullable(NULL);
+ if (ret != 0xbee)
+ return 7;
+
+ /* the arena pointer is stack-passed into the trampoline here */
+ ret = bpf_testmod_ops3_call_test_arena_stack((u64 *)val);
+ if (ret)
+ return 8;
+ if (*val != 44)
+ return 9;
+
+ /* a multi-slot arg precedes the arena pointer here */
+ ret = bpf_testmod_ops3_call_test_arena_multislot((u64 *)val);
+ if (ret)
+ return 10;
+ if (*val != 45)
+ return 11;
+
+ bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_arena_attach.c b/tools/testing/selftests/bpf/progs/struct_ops_arena_attach.c
new file mode 100644
index 000000000000..081a770307e5
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_arena_attach.c
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+SEC("fentry")
+int BPF_PROG(fentry_test_arena, unsigned long long *st_ops_ctx)
+{
+ return 0;
+}
+
+SEC("fexit")
+int BPF_PROG(fexit_test_arena, unsigned long long *st_ops_ctx, int ret)
+{
+ return 0;
+}
+
+SEC("freplace")
+int freplace_test_arena(unsigned long long *st_ops_ctx)
+{
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_arena_fail.c b/tools/testing/selftests/bpf/progs/struct_ops_arena_fail.c
new file mode 100644
index 000000000000..1c0ec727d637
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_arena_fail.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "../test_kmods/bpf_testmod.h"
+
+char _license[] SEC("license") = "GPL";
+
+/* No arena in the program: attaching to test_arena must be rejected. */
+SEC("struct_ops/test_arena")
+int test_arena_no_arena(unsigned long long *ctx)
+{
+ return 0;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_ops3 testmod_arena_fail = {
+ .test_arena = (void *)test_arena_no_arena,
+};
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_assoc.c b/tools/testing/selftests/bpf/progs/struct_ops_assoc.c
new file mode 100644
index 000000000000..68842e3f936b
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_assoc.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+#include "../test_kmods/bpf_testmod.h"
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+char _license[] SEC("license") = "GPL";
+
+int test_pid;
+
+/* Programs associated with st_ops_map_a */
+
+#define MAP_A_MAGIC 1234
+int test_err_a;
+
+SEC("struct_ops")
+int BPF_PROG(test_1_a, struct st_ops_args *args)
+{
+ return MAP_A_MAGIC;
+}
+
+SEC("tp_btf/sys_enter")
+int BPF_PROG(sys_enter_prog_a, struct pt_regs *regs, long id)
+{
+ struct st_ops_args args = {};
+ struct task_struct *task;
+ int ret;
+
+ task = bpf_get_current_task_btf();
+ if (!test_pid || task->pid != test_pid)
+ return 0;
+
+ ret = bpf_kfunc_multi_st_ops_test_1_assoc(&args);
+ if (ret != MAP_A_MAGIC)
+ test_err_a++;
+
+ return 0;
+}
+
+SEC("syscall")
+int syscall_prog_a(void *ctx)
+{
+ struct st_ops_args args = {};
+ int ret;
+
+ ret = bpf_kfunc_multi_st_ops_test_1_assoc(&args);
+ if (ret != MAP_A_MAGIC)
+ test_err_a++;
+
+ return 0;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_multi_st_ops st_ops_map_a = {
+ .test_1 = (void *)test_1_a,
+};
+
+/* Programs associated with st_ops_map_b */
+
+#define MAP_B_MAGIC 5678
+int test_err_b;
+
+SEC("struct_ops")
+int BPF_PROG(test_1_b, struct st_ops_args *args)
+{
+ return MAP_B_MAGIC;
+}
+
+SEC("tp_btf/sys_enter")
+int BPF_PROG(sys_enter_prog_b, struct pt_regs *regs, long id)
+{
+ struct st_ops_args args = {};
+ struct task_struct *task;
+ int ret;
+
+ task = bpf_get_current_task_btf();
+ if (!test_pid || task->pid != test_pid)
+ return 0;
+
+ ret = bpf_kfunc_multi_st_ops_test_1_assoc(&args);
+ if (ret != MAP_B_MAGIC)
+ test_err_b++;
+
+ return 0;
+}
+
+SEC("syscall")
+int syscall_prog_b(void *ctx)
+{
+ struct st_ops_args args = {};
+ int ret;
+
+ ret = bpf_kfunc_multi_st_ops_test_1_assoc(&args);
+ if (ret != MAP_B_MAGIC)
+ test_err_b++;
+
+ return 0;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_multi_st_ops st_ops_map_b = {
+ .test_1 = (void *)test_1_b,
+};
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_assoc_in_timer.c b/tools/testing/selftests/bpf/progs/struct_ops_assoc_in_timer.c
new file mode 100644
index 000000000000..0bed49e9f217
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_assoc_in_timer.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+#include "../test_kmods/bpf_testmod.h"
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+char _license[] SEC("license") = "GPL";
+
+struct elem {
+ struct bpf_timer timer;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, int);
+ __type(value, struct elem);
+} array_map SEC(".maps");
+
+#define MAP_MAGIC 1234
+int recur;
+int test_err;
+int timer_ns;
+int timer_test_1_ret;
+int timer_cb_run;
+
+__noinline static int timer_cb(void *map, int *key, struct bpf_timer *timer)
+{
+ struct st_ops_args args = {};
+
+ recur++;
+ timer_test_1_ret = bpf_kfunc_multi_st_ops_test_1_assoc(&args);
+ recur--;
+
+ timer_cb_run++;
+
+ return 0;
+}
+
+SEC("struct_ops")
+int BPF_PROG(test_1, struct st_ops_args *args)
+{
+ struct bpf_timer *timer;
+ int key = 0;
+
+ if (!recur) {
+ timer = bpf_map_lookup_elem(&array_map, &key);
+ if (!timer)
+ return 0;
+
+ bpf_timer_init(timer, &array_map, 1);
+ bpf_timer_set_callback(timer, timer_cb);
+ bpf_timer_start(timer, timer_ns, 0);
+ }
+
+ return MAP_MAGIC;
+}
+
+SEC("syscall")
+int syscall_prog(void *ctx)
+{
+ struct st_ops_args args = {};
+ int ret;
+
+ ret = bpf_kfunc_multi_st_ops_test_1_assoc(&args);
+ if (ret != MAP_MAGIC)
+ test_err++;
+
+ return 0;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_multi_st_ops st_ops_map = {
+ .test_1 = (void *)test_1,
+};
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_assoc_reuse.c b/tools/testing/selftests/bpf/progs/struct_ops_assoc_reuse.c
new file mode 100644
index 000000000000..396b3e58c729
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_assoc_reuse.c
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+#include "../test_kmods/bpf_testmod.h"
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+char _license[] SEC("license") = "GPL";
+
+#define MAP_A_MAGIC 1234
+int test_err_a;
+int recur;
+
+/*
+ * test_1_a is reused. The kfunc should not be able to get the associated
+ * struct_ops and call test_1 recursively as it is ambiguous.
+ */
+SEC("struct_ops")
+int BPF_PROG(test_1_a, struct st_ops_args *args)
+{
+ int ret;
+
+ if (!recur) {
+ recur++;
+ ret = bpf_kfunc_multi_st_ops_test_1_assoc(args);
+ if (ret != -1)
+ test_err_a++;
+ recur--;
+ }
+
+ return MAP_A_MAGIC;
+}
+
+/* Programs associated with st_ops_map_a */
+
+SEC("syscall")
+int syscall_prog_a(void *ctx)
+{
+ struct st_ops_args args = {};
+ int ret;
+
+ ret = bpf_kfunc_multi_st_ops_test_1_assoc(&args);
+ if (ret != MAP_A_MAGIC)
+ test_err_a++;
+
+ return 0;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_multi_st_ops st_ops_map_a = {
+ .test_1 = (void *)test_1_a,
+};
+
+/* Programs associated with st_ops_map_b */
+
+int test_err_b;
+
+SEC("syscall")
+int syscall_prog_b(void *ctx)
+{
+ struct st_ops_args args = {};
+ int ret;
+
+ ret = bpf_kfunc_multi_st_ops_test_1_assoc(&args);
+ if (ret != MAP_A_MAGIC)
+ test_err_b++;
+
+ return 0;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_multi_st_ops st_ops_map_b = {
+ .test_1 = (void *)test_1_a,
+};
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_kptr_return_fail__wrong_type.c b/tools/testing/selftests/bpf/progs/struct_ops_kptr_return_fail__wrong_type.c
index 6a2dd5367802..c8d217e89eea 100644
--- a/tools/testing/selftests/bpf/progs/struct_ops_kptr_return_fail__wrong_type.c
+++ b/tools/testing/selftests/bpf/progs/struct_ops_kptr_return_fail__wrong_type.c
@@ -12,7 +12,7 @@ void bpf_task_release(struct task_struct *p) __ksym;
* reject programs returning a referenced kptr of the wrong type.
*/
SEC("struct_ops/test_return_ref_kptr")
-__failure __msg("At program exit the register R0 is not a known value (ptr_or_null_)")
+__failure __msg("At program exit the register R0 is not a known value (trusted_ptr_or_null_)")
struct task_struct *BPF_PROG(kptr_return_fail__wrong_type, int dummy,
struct task_struct *task, struct cgroup *cgrp)
{
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_multi_args.c b/tools/testing/selftests/bpf/progs/struct_ops_multi_args.c
new file mode 100644
index 000000000000..c62be15757f0
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_multi_args.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Varun R Mallya */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "../test_kmods/bpf_testmod.h"
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PROG_ARRAY);
+ __uint(max_entries, 1);
+ __uint(key_size, sizeof(__u32));
+ __uint(value_size, sizeof(__u32));
+} prog_array SEC(".maps");
+
+SEC("struct_ops/test_refcounted_multi")
+__failure __msg("program with __ref argument cannot tail call")
+int test_refcounted_multi(unsigned long long *ctx)
+{
+ /* ctx[2] is used because the refcounted variable is the third argument */
+ struct task_struct *refcounted_task = (struct task_struct *)ctx[2];
+
+ bpf_task_release(refcounted_task);
+ bpf_tail_call(ctx, &prog_array, 0);
+
+ return 0;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_ops testmod_ref_acquire = {
+ .test_refcounted_multi = (void *)test_refcounted_multi,
+};
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_private_stack.c b/tools/testing/selftests/bpf/progs/struct_ops_private_stack.c
index dbe646013811..3cd0c1a55cbd 100644
--- a/tools/testing/selftests/bpf/progs/struct_ops_private_stack.c
+++ b/tools/testing/selftests/bpf/progs/struct_ops_private_stack.c
@@ -7,12 +7,6 @@
char _license[] SEC("license") = "GPL";
-#if defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)
-bool skip __attribute((__section__(".data"))) = false;
-#else
-bool skip = true;
-#endif
-
void bpf_testmod_ops3_call_test_2(void) __ksym;
int val_i, val_j;
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_private_stack_fail.c b/tools/testing/selftests/bpf/progs/struct_ops_private_stack_fail.c
index 3d89ad7cbe2a..1442728f5604 100644
--- a/tools/testing/selftests/bpf/progs/struct_ops_private_stack_fail.c
+++ b/tools/testing/selftests/bpf/progs/struct_ops_private_stack_fail.c
@@ -7,12 +7,6 @@
char _license[] SEC("license") = "GPL";
-#if defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)
-bool skip __attribute((__section__(".data"))) = false;
-#else
-bool skip = true;
-#endif
-
void bpf_testmod_ops3_call_test_2(void) __ksym;
int val_i, val_j;
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_private_stack_recur.c b/tools/testing/selftests/bpf/progs/struct_ops_private_stack_recur.c
index b1f6d7e5a8e5..faaa0f8d65a4 100644
--- a/tools/testing/selftests/bpf/progs/struct_ops_private_stack_recur.c
+++ b/tools/testing/selftests/bpf/progs/struct_ops_private_stack_recur.c
@@ -7,12 +7,6 @@
char _license[] SEC("license") = "GPL";
-#if defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)
-bool skip __attribute((__section__(".data"))) = false;
-#else
-bool skip = true;
-#endif
-
void bpf_testmod_ops3_call_test_1(void) __ksym;
int val_i, val_j;
diff --git a/tools/testing/selftests/bpf/progs/tailcall3.c b/tools/testing/selftests/bpf/progs/tailcall3.c
index f60bcd7b8d4b..204f19c30a3e 100644
--- a/tools/testing/selftests/bpf/progs/tailcall3.c
+++ b/tools/testing/selftests/bpf/progs/tailcall3.c
@@ -5,7 +5,7 @@
struct {
__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
- __uint(max_entries, 1);
+ __uint(max_entries, 2);
__uint(key_size, sizeof(__u32));
__uint(value_size, sizeof(__u32));
} jmp_table SEC(".maps");
@@ -23,6 +23,9 @@ int classifier_0(struct __sk_buff *skb)
SEC("tc")
int entry(struct __sk_buff *skb)
{
+ /* prog == NULL case */
+ bpf_tail_call_static(skb, &jmp_table, 1);
+
bpf_tail_call_static(skb, &jmp_table, 0);
return 0;
}
diff --git a/tools/testing/selftests/bpf/progs/tailcall_bpf2bpf2.c b/tools/testing/selftests/bpf/progs/tailcall_bpf2bpf2.c
index ce97d141daee..c4fadee5aadc 100644
--- a/tools/testing/selftests/bpf/progs/tailcall_bpf2bpf2.c
+++ b/tools/testing/selftests/bpf/progs/tailcall_bpf2bpf2.c
@@ -13,11 +13,14 @@ struct {
static __noinline
int subprog_tail(struct __sk_buff *skb)
{
+ int ret = 1;
+
if (load_byte(skb, 0))
bpf_tail_call_static(skb, &jmp_table, 1);
else
bpf_tail_call_static(skb, &jmp_table, 0);
- return 1;
+ barrier_var(ret);
+ return ret;
}
int count = 0;
diff --git a/tools/testing/selftests/bpf/progs/tailcall_bpf2bpf_hierarchy1.c b/tools/testing/selftests/bpf/progs/tailcall_bpf2bpf_hierarchy1.c
index d556b19413d7..1fd07824d88a 100644
--- a/tools/testing/selftests/bpf/progs/tailcall_bpf2bpf_hierarchy1.c
+++ b/tools/testing/selftests/bpf/progs/tailcall_bpf2bpf_hierarchy1.c
@@ -16,20 +16,25 @@ int count = 0;
static __noinline
int subprog_tail(struct __sk_buff *skb)
{
+ int ret = 0;
+
bpf_tail_call_static(skb, &jmp_table, 0);
- return 0;
+ barrier_var(ret);
+ return ret;
}
SEC("tc")
int entry(struct __sk_buff *skb)
{
- int ret = 1;
+ int ret = 1, ret1, ret2;
clobber_regs_stack();
count++;
- subprog_tail(skb);
- subprog_tail(skb);
+ ret1 = subprog_tail(skb);
+ ret2 = subprog_tail(skb);
+ __sink(ret1);
+ __sink(ret2);
return ret;
}
diff --git a/tools/testing/selftests/bpf/progs/tailcall_bpf2bpf_hierarchy2.c b/tools/testing/selftests/bpf/progs/tailcall_bpf2bpf_hierarchy2.c
index ae94c9c70ab7..6fde0ab92148 100644
--- a/tools/testing/selftests/bpf/progs/tailcall_bpf2bpf_hierarchy2.c
+++ b/tools/testing/selftests/bpf/progs/tailcall_bpf2bpf_hierarchy2.c
@@ -25,8 +25,11 @@ int count1 = 0;
static __noinline
int subprog_tail0(struct __sk_buff *skb)
{
+ int ret = 0;
+
bpf_tail_call_static(skb, &jmp_table, 0);
- return 0;
+ barrier_var(ret);
+ return ret;
}
__auxiliary
@@ -41,16 +44,22 @@ int classifier_0(struct __sk_buff *skb)
static __noinline
int subprog_tail1(struct __sk_buff *skb)
{
+ int ret = 0;
+
bpf_tail_call_static(skb, &jmp_table, 1);
- return 0;
+ barrier_var(ret);
+ return ret;
}
__auxiliary
SEC("tc")
int classifier_1(struct __sk_buff *skb)
{
+ int ret;
+
count1++;
- subprog_tail1(skb);
+ ret = subprog_tail1(skb);
+ __sink(ret);
return 0;
}
@@ -59,13 +68,14 @@ __retval(33)
SEC("tc")
int tailcall_bpf2bpf_hierarchy_2(struct __sk_buff *skb)
{
- int ret = 0;
+ int ret = 0, ret1, ret2;
clobber_regs_stack();
- subprog_tail0(skb);
- subprog_tail1(skb);
-
+ ret1 = subprog_tail0(skb);
+ ret2 = subprog_tail1(skb);
+ __sink(ret1);
+ __sink(ret2);
__sink(ret);
return (count1 << 16) | count0;
}
diff --git a/tools/testing/selftests/bpf/progs/tailcall_bpf2bpf_hierarchy3.c b/tools/testing/selftests/bpf/progs/tailcall_bpf2bpf_hierarchy3.c
index 56b6b0099840..0ef9cfb2da8d 100644
--- a/tools/testing/selftests/bpf/progs/tailcall_bpf2bpf_hierarchy3.c
+++ b/tools/testing/selftests/bpf/progs/tailcall_bpf2bpf_hierarchy3.c
@@ -33,17 +33,24 @@ int count = 0;
static __noinline
int subprog_tail(struct __sk_buff *skb, void *jmp_table)
{
+ int ret = 0;
+
bpf_tail_call_static(skb, jmp_table, 0);
- return 0;
+ barrier_var(ret);
+ return ret;
}
__auxiliary
SEC("tc")
int classifier_0(struct __sk_buff *skb)
{
+ int ret1, ret2;
+
count++;
- subprog_tail(skb, &jmp_table0);
- subprog_tail(skb, &jmp_table1);
+ ret1 = subprog_tail(skb, &jmp_table0);
+ ret2 = subprog_tail(skb, &jmp_table1);
+ __sink(ret1);
+ __sink(ret2);
return count;
}
diff --git a/tools/testing/selftests/bpf/progs/tailcall_bpf2bpf_hierarchy_fentry.c b/tools/testing/selftests/bpf/progs/tailcall_bpf2bpf_hierarchy_fentry.c
index 5261395713cd..6db9afee2095 100644
--- a/tools/testing/selftests/bpf/progs/tailcall_bpf2bpf_hierarchy_fentry.c
+++ b/tools/testing/selftests/bpf/progs/tailcall_bpf2bpf_hierarchy_fentry.c
@@ -18,18 +18,25 @@ int count = 0;
static __noinline
int subprog_tail(void *ctx)
{
+ int ret = 0;
+
bpf_tail_call_static(ctx, &jmp_table, 0);
- return 0;
+ barrier_var(ret);
+ return ret;
}
SEC("fentry/dummy")
int BPF_PROG(fentry, struct sk_buff *skb)
{
+ int ret1, ret2;
+
clobber_regs_stack();
count++;
- subprog_tail(ctx);
- subprog_tail(ctx);
+ ret1 = subprog_tail(ctx);
+ ret2 = subprog_tail(ctx);
+ __sink(ret1);
+ __sink(ret2);
return 0;
}
diff --git a/tools/testing/selftests/bpf/progs/tailcall_callback.c b/tools/testing/selftests/bpf/progs/tailcall_callback.c
new file mode 100644
index 000000000000..14fa7a87028e
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tailcall_callback.c
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+#include "bpf_test_utils.h"
+
+int classifier_0(struct __sk_buff *skb);
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PROG_ARRAY);
+ __uint(max_entries, 1);
+ __uint(key_size, sizeof(__u32));
+ __array(values, void (void));
+} jmp_table SEC(".maps") = {
+ .values = {
+ [0] = (void *) &classifier_0,
+ },
+};
+
+__auxiliary
+SEC("tc")
+int classifier_0(struct __sk_buff *skb)
+{
+ return 0;
+}
+
+static __noinline
+int subprog_tail0(struct __sk_buff *skb)
+{
+ int ret = 0;
+
+ bpf_tail_call_static(skb, &jmp_table, 0);
+ barrier_var(ret);
+ return ret;
+}
+
+static __noinline
+int callback_loop(int index, void **cb_ctx)
+{
+ int ret;
+
+ ret = subprog_tail0(*cb_ctx);
+ barrier_var(ret);
+ return ret ? 1 : 0;
+}
+
+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;
+}
+
+/* callback involving subprog with tail call is rejected */
+SEC("tc")
+__failure __msg("cannot tail call within callback")
+int tailcall_callback_1(struct __sk_buff *skb)
+{
+ clobber_regs_stack();
+
+ bpf_loop(1, callback_loop, &skb, 0);
+ return 0;
+}
+
+/* subprogs with tailcall do not affect no-tailcall callback */
+SEC("tc")
+__success
+__retval(0)
+int tailcall_callback_2(struct __sk_buff *skb)
+{
+ int ret;
+
+ clobber_regs_stack();
+
+ ret = subprog_tail0(skb);
+ __sink(ret);
+
+ bpf_loop(1, callback_empty, NULL, 0);
+ 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/tailcall_cgrp_storage.c b/tools/testing/selftests/bpf/progs/tailcall_cgrp_storage.c
new file mode 100644
index 000000000000..4dd3a0033d75
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tailcall_cgrp_storage.c
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE);
+ __type(key, struct bpf_cgroup_storage_key);
+ __type(value, __u64);
+} storage_map SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PROG_ARRAY);
+ __uint(max_entries, 1);
+ __uint(key_size, sizeof(__u32));
+ __uint(value_size, sizeof(__u32));
+} prog_array SEC(".maps");
+
+SEC("cgroup_skb/egress")
+int caller_prog(struct __sk_buff *skb)
+{
+ __u64 *storage;
+
+ storage = bpf_get_local_storage(&storage_map, 0);
+ if (storage)
+ *storage = 1;
+
+ bpf_tail_call(skb, &prog_array, 0);
+ return 1;
+}
+
+SEC("cgroup_skb/egress")
+int callee_prog(struct __sk_buff *skb)
+{
+ __u64 *storage;
+
+ storage = bpf_get_local_storage(&storage_map, 0);
+ if (storage)
+ *storage = 1;
+
+ return 1;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/tailcall_cgrp_storage_no_storage.c b/tools/testing/selftests/bpf/progs/tailcall_cgrp_storage_no_storage.c
new file mode 100644
index 000000000000..5c69b0af6ff9
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tailcall_cgrp_storage_no_storage.c
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PROG_ARRAY);
+ __uint(max_entries, 1);
+ __uint(key_size, sizeof(__u32));
+ __uint(value_size, sizeof(__u32));
+} prog_array SEC(".maps");
+
+SEC("cgroup_skb/egress")
+int caller_prog(struct __sk_buff *skb)
+{
+ bpf_tail_call(skb, &prog_array, 0);
+ return 1;
+}
+
+SEC("cgroup_skb/egress")
+int leaf_prog(struct __sk_buff *skb)
+{
+ return 1;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/tailcall_cgrp_storage_owner.c b/tools/testing/selftests/bpf/progs/tailcall_cgrp_storage_owner.c
new file mode 100644
index 000000000000..d7e8ec9855c5
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tailcall_cgrp_storage_owner.c
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE);
+ __type(key, struct bpf_cgroup_storage_key);
+ __type(value, __u64);
+} storage_map SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PROG_ARRAY);
+ __uint(max_entries, 1);
+ __uint(key_size, sizeof(__u32));
+ __uint(value_size, sizeof(__u32));
+} prog_array SEC(".maps");
+
+SEC("cgroup_skb/egress")
+int prog_array_owner(struct __sk_buff *skb)
+{
+ __u64 *storage;
+
+ storage = bpf_get_local_storage(&storage_map, 0);
+ if (storage)
+ *storage = 1;
+
+ bpf_tail_call(skb, &prog_array, 0);
+ return 1;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/tailcall_sleepable.c b/tools/testing/selftests/bpf/progs/tailcall_sleepable.c
new file mode 100644
index 000000000000..d959a9eaaa9c
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tailcall_sleepable.c
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+#include "bpf_test_utils.h"
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PROG_ARRAY);
+ __uint(max_entries, 1);
+ __uint(key_size, sizeof(__u32));
+ __array(values, void (void));
+} jmp_table SEC(".maps");
+
+SEC("?uprobe")
+int uprobe_normal(void *ctx)
+{
+ bpf_tail_call_static(ctx, &jmp_table, 0);
+ return 0;
+}
+
+SEC("?uprobe.s")
+int uprobe_sleepable_1(void *ctx)
+{
+ bpf_tail_call_static(ctx, &jmp_table, 0);
+ return 0;
+}
+
+int executed = 0;
+int my_pid = 0;
+
+SEC("?uprobe.s")
+int uprobe_sleepable_2(void *ctx)
+{
+ int pid = bpf_get_current_pid_tgid() >> 32;
+
+ if (pid != my_pid)
+ return 0;
+
+ executed++;
+ return 0;
+}
+
+char __license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/task_kfunc_common.h b/tools/testing/selftests/bpf/progs/task_kfunc_common.h
index e9c4fea7a4bb..a0c599b58c29 100644
--- a/tools/testing/selftests/bpf/progs/task_kfunc_common.h
+++ b/tools/testing/selftests/bpf/progs/task_kfunc_common.h
@@ -20,12 +20,26 @@ struct {
__uint(max_entries, 1);
} __tasks_kfunc_map SEC(".maps");
+struct task_kptr_lock_value {
+ struct bpf_spin_lock lock;
+ struct task_struct __kptr * task;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, int);
+ __type(value, struct task_kptr_lock_value);
+ __uint(max_entries, 1);
+} task_kptr_lock_map SEC(".maps");
+
struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym;
void bpf_task_release(struct task_struct *p) __ksym;
struct task_struct *bpf_task_from_pid(s32 pid) __ksym;
struct task_struct *bpf_task_from_vpid(s32 vpid) __ksym;
void bpf_rcu_read_lock(void) __ksym;
void bpf_rcu_read_unlock(void) __ksym;
+void bpf_local_irq_save(unsigned long *flags) __weak __ksym;
+void bpf_local_irq_restore(unsigned long *flags) __weak __ksym;
static inline struct __tasks_kfunc_map_value *tasks_kfunc_map_value_lookup(struct task_struct *p)
{
diff --git a/tools/testing/selftests/bpf/progs/task_kfunc_failure.c b/tools/testing/selftests/bpf/progs/task_kfunc_failure.c
index 4c07ea193f72..f96b0c13ed1a 100644
--- a/tools/testing/selftests/bpf/progs/task_kfunc_failure.c
+++ b/tools/testing/selftests/bpf/progs/task_kfunc_failure.c
@@ -5,6 +5,7 @@
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_helpers.h>
+#include "../bpf_experimental.h"
#include "bpf_misc.h"
#include "task_kfunc_common.h"
@@ -28,7 +29,7 @@ static struct __tasks_kfunc_map_value *insert_lookup_task(struct task_struct *ta
}
SEC("tp_btf/task_newtask")
-__failure __msg("Possibly NULL pointer passed to trusted arg0")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
int BPF_PROG(task_kfunc_acquire_untrusted, struct task_struct *task, u64 clone_flags)
{
struct task_struct *acquired;
@@ -49,7 +50,7 @@ int BPF_PROG(task_kfunc_acquire_untrusted, struct task_struct *task, u64 clone_f
}
SEC("tp_btf/task_newtask")
-__failure __msg("arg#0 pointer type STRUCT task_struct must point")
+__failure __msg("R1 is fp expected STRUCT task_struct")
int BPF_PROG(task_kfunc_acquire_fp, struct task_struct *task, u64 clone_flags)
{
struct task_struct *acquired, *stack_task = (struct task_struct *)&clone_flags;
@@ -100,7 +101,7 @@ int BPF_PROG(task_kfunc_acquire_unsafe_kretprobe_rcu, struct task_struct *task,
}
SEC("tp_btf/task_newtask")
-__failure __msg("Possibly NULL pointer passed to trusted arg0")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
int BPF_PROG(task_kfunc_acquire_null, struct task_struct *task, u64 clone_flags)
{
struct task_struct *acquired;
@@ -149,7 +150,7 @@ int BPF_PROG(task_kfunc_xchg_unreleased, struct task_struct *task, u64 clone_fla
}
SEC("tp_btf/task_newtask")
-__failure __msg("Possibly NULL pointer passed to trusted arg0")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
int BPF_PROG(task_kfunc_acquire_release_no_null_check, struct task_struct *task, u64 clone_flags)
{
struct task_struct *acquired;
@@ -162,7 +163,7 @@ int BPF_PROG(task_kfunc_acquire_release_no_null_check, struct task_struct *task,
}
SEC("tp_btf/task_newtask")
-__failure __msg("Possibly NULL pointer passed to trusted arg0")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
int BPF_PROG(task_kfunc_release_untrusted, struct task_struct *task, u64 clone_flags)
{
struct __tasks_kfunc_map_value *v;
@@ -178,7 +179,7 @@ int BPF_PROG(task_kfunc_release_untrusted, struct task_struct *task, u64 clone_f
}
SEC("tp_btf/task_newtask")
-__failure __msg("arg#0 pointer type STRUCT task_struct must point")
+__failure __msg("release kfunc bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
int BPF_PROG(task_kfunc_release_fp, struct task_struct *task, u64 clone_flags)
{
struct task_struct *acquired = (struct task_struct *)&clone_flags;
@@ -190,7 +191,7 @@ int BPF_PROG(task_kfunc_release_fp, struct task_struct *task, u64 clone_flags)
}
SEC("tp_btf/task_newtask")
-__failure __msg("Possibly NULL pointer passed to trusted arg0")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
int BPF_PROG(task_kfunc_release_null, struct task_struct *task, u64 clone_flags)
{
struct __tasks_kfunc_map_value local, *v;
@@ -224,7 +225,7 @@ int BPF_PROG(task_kfunc_release_null, struct task_struct *task, u64 clone_flags)
}
SEC("tp_btf/task_newtask")
-__failure __msg("release kernel function bpf_task_release expects")
+__failure __msg("release kfunc bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
int BPF_PROG(task_kfunc_release_unacquired, struct task_struct *task, u64 clone_flags)
{
/* Cannot release trusted task pointer which was not acquired. */
@@ -234,7 +235,46 @@ int BPF_PROG(task_kfunc_release_unacquired, struct task_struct *task, u64 clone_
}
SEC("tp_btf/task_newtask")
-__failure __msg("Possibly NULL pointer passed to trusted arg0")
+__failure __msg("bpf_obj_drop cannot be used in tracing programs on types with NMI unsafe fields")
+int BPF_PROG(task_kfunc_obj_drop_with_kptr, struct task_struct *task, u64 clone_flags)
+{
+ struct __tasks_kfunc_map_value *local;
+
+ local = bpf_obj_new(typeof(*local));
+ if (!local)
+ return 0;
+
+ bpf_obj_drop(local);
+ return 0;
+}
+
+SEC("tp_btf/task_newtask")
+__failure __msg("bpf_obj_drop cannot be used in tracing programs on types with NMI unsafe fields")
+int BPF_PROG(task_kfunc_obj_drop_nmi_with_kptr, struct task_struct *task,
+ u64 clone_flags)
+{
+ struct __tasks_kfunc_map_value *local;
+ struct task_struct *acquired, *old;
+
+ (void)clone_flags;
+
+ local = bpf_obj_new(typeof(*local));
+ if (!local)
+ return 0;
+
+ acquired = bpf_task_acquire(task);
+ if (acquired) {
+ old = bpf_kptr_xchg(&local->task, acquired);
+ if (old)
+ bpf_task_release(old);
+ }
+
+ bpf_obj_drop(local);
+ return 0;
+}
+
+SEC("tp_btf/task_newtask")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
int BPF_PROG(task_kfunc_from_pid_no_null_check, struct task_struct *task, u64 clone_flags)
{
struct task_struct *acquired;
@@ -248,7 +288,7 @@ int BPF_PROG(task_kfunc_from_pid_no_null_check, struct task_struct *task, u64 cl
}
SEC("tp_btf/task_newtask")
-__failure __msg("Possibly NULL pointer passed to trusted arg0")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
int BPF_PROG(task_kfunc_from_vpid_no_null_check, struct task_struct *task, u64 clone_flags)
{
struct task_struct *acquired;
@@ -313,7 +353,7 @@ int BPF_PROG(task_access_comm4, struct task_struct *task, const char *buf, bool
}
SEC("tp_btf/task_newtask")
-__failure __msg("R1 must be referenced or trusted")
+__failure __msg("release kfunc bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
int BPF_PROG(task_kfunc_release_in_map, struct task_struct *task, u64 clone_flags)
{
struct task_struct *local;
@@ -338,3 +378,76 @@ int BPF_PROG(task_kfunc_release_in_map, struct task_struct *task, u64 clone_flag
return 0;
}
+
+SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
+__failure __msg("R1 must be a rcu pointer")
+int BPF_PROG(task_kfunc_acquire_after_final_spin_unlock)
+{
+ struct task_kptr_lock_value *v;
+ struct task_struct *task, *acquired;
+ int key = 0;
+
+ v = bpf_map_lookup_elem(&task_kptr_lock_map, &key);
+ if (!v)
+ return 0;
+
+ bpf_spin_lock(&v->lock);
+ task = v->task;
+ bpf_spin_unlock(&v->lock);
+ if (!task)
+ return 0;
+
+ acquired = bpf_task_acquire(task);
+ if (acquired)
+ bpf_task_release(acquired);
+ return 0;
+}
+
+SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
+__failure __msg("R1 must be a rcu pointer")
+int BPF_PROG(task_kfunc_acquire_after_preempt_enable)
+{
+ struct task_kptr_lock_value *v;
+ struct task_struct *task, *acquired;
+ int key = 0;
+
+ v = bpf_map_lookup_elem(&task_kptr_lock_map, &key);
+ if (!v)
+ return 0;
+
+ bpf_preempt_disable();
+ task = v->task;
+ bpf_preempt_enable();
+ if (!task)
+ return 0;
+
+ acquired = bpf_task_acquire(task);
+ if (acquired)
+ bpf_task_release(acquired);
+ return 0;
+}
+
+SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
+__failure __msg("R1 must be a rcu pointer")
+int BPF_PROG(task_kfunc_acquire_after_irq_restore)
+{
+ struct task_kptr_lock_value *v;
+ struct task_struct *task, *acquired;
+ unsigned long flags;
+ int key = 0;
+
+ v = bpf_map_lookup_elem(&task_kptr_lock_map, &key);
+ if (!v)
+ return 0;
+
+ bpf_local_irq_save(&flags);
+ task = v->task;
+ bpf_local_irq_restore(&flags);
+ if (!task)
+ return 0;
+
+ acquired = bpf_task_acquire(task);
+ if (acquired)
+ bpf_task_release(acquired);
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/task_kfunc_success.c b/tools/testing/selftests/bpf/progs/task_kfunc_success.c
index 5fb4fc19d26a..6545b124dee1 100644
--- a/tools/testing/selftests/bpf/progs/task_kfunc_success.c
+++ b/tools/testing/selftests/bpf/progs/task_kfunc_success.c
@@ -6,6 +6,7 @@
#include <bpf/bpf_helpers.h>
#include "../bpf_experimental.h"
+#include "bpf_misc.h"
#include "task_kfunc_common.h"
char _license[] SEC("license") = "GPL";
@@ -140,17 +141,17 @@ int BPF_PROG(test_task_acquire_leave_in_map, struct task_struct *task, u64 clone
return 0;
}
-SEC("tp_btf/task_newtask")
-int BPF_PROG(test_task_xchg_release, struct task_struct *task, u64 clone_flags)
+SEC("syscall")
+int test_task_xchg_release(const void *ctx)
{
- struct task_struct *kptr, *acquired;
+ struct task_struct *task, *kptr, *acquired;
struct __tasks_kfunc_map_value *v, *local;
int refcnt, refcnt_after_drop;
long status;
- if (!is_test_kfunc_task())
- return 0;
+ (void)ctx;
+ task = bpf_get_current_task_btf();
status = tasks_kfunc_map_insert(task);
if (status) {
err = 1;
@@ -191,7 +192,7 @@ int BPF_PROG(test_task_xchg_release, struct task_struct *task, u64 clone_flags)
return 0;
}
- /* Stash a copy into local kptr and check if it is released recursively */
+ /* Stash a copy into local kptr and check if it is released recursively. */
acquired = bpf_task_acquire(kptr);
if (!acquired) {
err = 7;
@@ -220,7 +221,6 @@ int BPF_PROG(test_task_xchg_release, struct task_struct *task, u64 clone_flags)
}
bpf_task_release(kptr);
-
return 0;
}
@@ -367,6 +367,200 @@ int BPF_PROG(task_kfunc_acquire_trusted_walked, struct task_struct *task, u64 cl
return 0;
}
+SEC("fentry/" SYS_PREFIX "sys_getpgid")
+int BPF_PROG(task_kfunc_acquire_after_spin_unlock_non_sleepable)
+{
+ struct task_kptr_lock_value *v;
+ struct task_struct *task, *acquired;
+ int key = 0;
+
+ v = bpf_map_lookup_elem(&task_kptr_lock_map, &key);
+ if (!v)
+ return 0;
+
+ bpf_spin_lock(&v->lock);
+ task = v->task;
+ bpf_spin_unlock(&v->lock);
+ if (!task)
+ return 0;
+
+ acquired = bpf_task_acquire(task);
+ if (acquired)
+ bpf_task_release(acquired);
+ return 0;
+}
+
+SEC("fentry.s/" SYS_PREFIX "sys_getpgid")
+int BPF_PROG(task_kfunc_acquire_after_spin_unlock_explicit_rcu)
+{
+ struct task_kptr_lock_value *v;
+ struct task_struct *task, *acquired;
+ int key = 0;
+
+ v = bpf_map_lookup_elem(&task_kptr_lock_map, &key);
+ if (!v)
+ return 0;
+
+ bpf_rcu_read_lock();
+ bpf_spin_lock(&v->lock);
+ task = v->task;
+ bpf_spin_unlock(&v->lock);
+ if (task) {
+ acquired = bpf_task_acquire(task);
+ if (acquired)
+ bpf_task_release(acquired);
+ }
+ bpf_rcu_read_unlock();
+ return 0;
+}
+
+SEC("fentry.s/" SYS_PREFIX "sys_getpgid")
+int BPF_PROG(task_kfunc_acquire_after_spin_unlock_preempt_disabled)
+{
+ struct task_kptr_lock_value *v;
+ struct task_struct *task, *acquired;
+ int key = 0;
+
+ v = bpf_map_lookup_elem(&task_kptr_lock_map, &key);
+ if (!v)
+ return 0;
+
+ bpf_preempt_disable();
+ bpf_spin_lock(&v->lock);
+ task = v->task;
+ bpf_spin_unlock(&v->lock);
+ if (task) {
+ acquired = bpf_task_acquire(task);
+ if (acquired)
+ bpf_task_release(acquired);
+ }
+ bpf_preempt_enable();
+ return 0;
+}
+
+SEC("fentry.s/" SYS_PREFIX "sys_getpgid")
+int BPF_PROG(task_kfunc_acquire_after_spin_unlock_irq_disabled)
+{
+ struct task_kptr_lock_value *v;
+ struct task_struct *task, *acquired;
+ unsigned long flags;
+ int key = 0;
+
+ v = bpf_map_lookup_elem(&task_kptr_lock_map, &key);
+ if (!v)
+ return 0;
+
+ bpf_local_irq_save(&flags);
+ bpf_spin_lock(&v->lock);
+ task = v->task;
+ bpf_spin_unlock(&v->lock);
+ if (task) {
+ acquired = bpf_task_acquire(task);
+ if (acquired)
+ bpf_task_release(acquired);
+ }
+ bpf_local_irq_restore(&flags);
+ return 0;
+}
+
+SEC("fentry.s/" SYS_PREFIX "sys_getpgid")
+int BPF_PROG(task_kfunc_acquire_after_rcu_unlock_preempt_disabled)
+{
+ struct task_kptr_lock_value *v;
+ struct task_struct *task, *acquired;
+ int key = 0;
+
+ v = bpf_map_lookup_elem(&task_kptr_lock_map, &key);
+ if (!v)
+ return 0;
+
+ bpf_preempt_disable();
+ bpf_rcu_read_lock();
+ task = v->task;
+ bpf_rcu_read_unlock();
+ if (task) {
+ acquired = bpf_task_acquire(task);
+ if (acquired)
+ bpf_task_release(acquired);
+ }
+ bpf_preempt_enable();
+ return 0;
+}
+
+SEC("fentry.s/" SYS_PREFIX "sys_getpgid")
+int BPF_PROG(task_kfunc_acquire_after_rcu_unlock_irq_disabled)
+{
+ struct task_kptr_lock_value *v;
+ struct task_struct *task, *acquired;
+ unsigned long flags;
+ int key = 0;
+
+ v = bpf_map_lookup_elem(&task_kptr_lock_map, &key);
+ if (!v)
+ return 0;
+
+ bpf_local_irq_save(&flags);
+ bpf_rcu_read_lock();
+ task = v->task;
+ bpf_rcu_read_unlock();
+ if (task) {
+ acquired = bpf_task_acquire(task);
+ if (acquired)
+ bpf_task_release(acquired);
+ }
+ bpf_local_irq_restore(&flags);
+ return 0;
+}
+
+SEC("fentry.s/" SYS_PREFIX "sys_getpgid")
+int BPF_PROG(task_kfunc_acquire_after_preempt_enable_explicit_rcu)
+{
+ struct task_kptr_lock_value *v;
+ struct task_struct *task, *acquired;
+ int key = 0;
+
+ v = bpf_map_lookup_elem(&task_kptr_lock_map, &key);
+ if (!v)
+ return 0;
+
+ bpf_preempt_disable();
+ task = v->task;
+ bpf_rcu_read_lock();
+ bpf_preempt_enable();
+ if (task) {
+ acquired = bpf_task_acquire(task);
+ if (acquired)
+ bpf_task_release(acquired);
+ }
+ bpf_rcu_read_unlock();
+ return 0;
+}
+
+SEC("fentry.s/" SYS_PREFIX "sys_getpgid")
+int BPF_PROG(task_kfunc_acquire_after_irq_restore_explicit_rcu)
+{
+ struct task_kptr_lock_value *v;
+ struct task_struct *task, *acquired;
+ unsigned long flags;
+ int key = 0;
+
+ v = bpf_map_lookup_elem(&task_kptr_lock_map, &key);
+ if (!v)
+ return 0;
+
+ bpf_local_irq_save(&flags);
+ task = v->task;
+ bpf_rcu_read_lock();
+ bpf_local_irq_restore(&flags);
+ if (task) {
+ acquired = bpf_task_acquire(task);
+ if (acquired)
+ bpf_task_release(acquired);
+ }
+ bpf_rcu_read_unlock();
+ return 0;
+}
+
SEC("syscall")
int test_task_from_vpid_current(const void *ctx)
{
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 432fff2af844..a31a399870be 100644
--- a/tools/testing/selftests/bpf/progs/task_local_data.bpf.h
+++ b/tools/testing/selftests/bpf/progs/task_local_data.bpf.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0 */
+/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __TASK_LOCAL_DATA_BPF_H
#define __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
@@ -80,19 +81,20 @@ struct tld_metadata {
};
struct tld_meta_u {
- __u8 cnt;
+ __u16 cnt;
__u16 size;
struct tld_metadata metadata[TLD_MAX_DATA_CNT];
};
struct tld_data_u {
- __u64 start; /* offset of tld_data_u->data in a page */
- char data[__PAGE_SIZE - sizeof(__u64)];
+ __u64 unused;
+ char data[__PAGE_SIZE - sizeof(__u64)] __attribute__((aligned(8)));
};
struct tld_map_value {
struct tld_data_u __uptr *data;
struct tld_meta_u __uptr *meta;
+ __u16 start; /* offset of tld_data_u->data in a page */
};
typedef struct tld_uptr_dummy {
@@ -176,7 +178,7 @@ static int __tld_fetch_key(struct tld_object *tld_obj, const char *name, int i_s
if (!tld_obj->data_map || !tld_obj->data_map->data || !tld_obj->data_map->meta)
return 0;
- start = tld_obj->data_map->data->start;
+ start = tld_obj->data_map->start;
cnt = tld_obj->data_map->meta->cnt;
metadata = tld_obj->data_map->meta->metadata;
@@ -188,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/task_local_storage.c b/tools/testing/selftests/bpf/progs/task_local_storage.c
index 80a0a20db88d..34fa3d6451d2 100644
--- a/tools/testing/selftests/bpf/progs/task_local_storage.c
+++ b/tools/testing/selftests/bpf/progs/task_local_storage.c
@@ -14,12 +14,15 @@ struct {
__type(value, long);
} enter_id SEC(".maps");
+#include "err.h"
+
#define MAGIC_VALUE 0xabcd1234
pid_t target_pid = 0;
int mismatch_cnt = 0;
int enter_cnt = 0;
int exit_cnt = 0;
+long update_err = 0;
SEC("tp_btf/sys_enter")
int BPF_PROG(on_enter, struct pt_regs *regs, long id)
@@ -62,3 +65,19 @@ int BPF_PROG(on_exit, struct pt_regs *regs, long id)
__sync_fetch_and_add(&mismatch_cnt, 1);
return 0;
}
+
+SEC("fexit/bpf_local_storage_update")
+int BPF_PROG(fexit_update, void *owner, struct bpf_local_storage_map *smap,
+ void *value, u64 map_flags, bool swap_uptrs,
+ struct bpf_local_storage_data *ret)
+{
+ struct task_struct *task = bpf_get_current_task_btf();
+
+ if (task->pid != target_pid)
+ return 0;
+
+ if (IS_ERR_VALUE(ret))
+ update_err = PTR_ERR(ret);
+
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/task_ls_recursion.c b/tools/testing/selftests/bpf/progs/task_ls_recursion.c
index f1853c38aada..b37359432692 100644
--- a/tools/testing/selftests/bpf/progs/task_ls_recursion.c
+++ b/tools/testing/selftests/bpf/progs/task_ls_recursion.c
@@ -36,14 +36,9 @@ int BPF_PROG(on_update)
if (!test_pid || task->pid != test_pid)
return 0;
+ /* This will succeed as there is no real deadlock */
ptr = bpf_task_storage_get(&map_a, task, 0,
BPF_LOCAL_STORAGE_GET_F_CREATE);
- /* ptr will not be NULL when it is called from
- * the bpf_task_storage_get(&map_b,...F_CREATE) in
- * the BPF_PROG(on_enter) below. It is because
- * the value can be found in map_a and the kernel
- * does not need to acquire any spin_lock.
- */
if (ptr) {
int err;
@@ -53,12 +48,7 @@ int BPF_PROG(on_update)
nr_del_errs++;
}
- /* This will still fail because map_b is empty and
- * this BPF_PROG(on_update) has failed to acquire
- * the percpu busy lock => meaning potential
- * deadlock is detected and it will fail to create
- * new storage.
- */
+ /* This will succeed as there is no real deadlock */
ptr = bpf_task_storage_get(&map_b, task, 0,
BPF_LOCAL_STORAGE_GET_F_CREATE);
if (ptr)
diff --git a/tools/testing/selftests/bpf/progs/task_storage_nodeadlock.c b/tools/testing/selftests/bpf/progs/task_storage_nodeadlock.c
index 986829aaf73a..6ce98fe9f387 100644
--- a/tools/testing/selftests/bpf/progs/task_storage_nodeadlock.c
+++ b/tools/testing/selftests/bpf/progs/task_storage_nodeadlock.c
@@ -1,15 +1,12 @@
// SPDX-License-Identifier: GPL-2.0
#include "vmlinux.h"
+#include <errno.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
char _license[] SEC("license") = "GPL";
-#ifndef EBUSY
-#define EBUSY 16
-#endif
-
extern bool CONFIG_PREEMPTION __kconfig __weak;
int nr_get_errs = 0;
int nr_del_errs = 0;
@@ -40,7 +37,7 @@ int BPF_PROG(socket_post_create, struct socket *sock, int family, int type,
ret = bpf_task_storage_delete(&task_storage,
bpf_get_current_task_btf());
- if (ret == -EBUSY)
+ if (ret == -EDEADLK || ret == -ETIMEDOUT)
__sync_fetch_and_add(&nr_del_errs, 1);
return 0;
diff --git a/tools/testing/selftests/bpf/progs/task_work.c b/tools/testing/selftests/bpf/progs/task_work.c
index 663a80990f8f..a6009d105158 100644
--- a/tools/testing/selftests/bpf/progs/task_work.c
+++ b/tools/testing/selftests/bpf/progs/task_work.c
@@ -65,8 +65,7 @@ int oncpu_hash_map(struct pt_regs *args)
work = bpf_map_lookup_elem(&hmap, &key);
if (!work)
return 0;
-
- bpf_task_work_schedule_resume_impl(task, &work->tw, &hmap, process_work, NULL);
+ bpf_task_work_schedule_resume(task, &work->tw, &hmap, process_work);
return 0;
}
@@ -80,7 +79,7 @@ int oncpu_array_map(struct pt_regs *args)
work = bpf_map_lookup_elem(&arrmap, &key);
if (!work)
return 0;
- bpf_task_work_schedule_signal_impl(task, &work->tw, &arrmap, process_work, NULL);
+ bpf_task_work_schedule_signal(task, &work->tw, &arrmap, process_work);
return 0;
}
@@ -102,6 +101,6 @@ int oncpu_lru_map(struct pt_regs *args)
work = bpf_map_lookup_elem(&lrumap, &key);
if (!work || work->data[0])
return 0;
- bpf_task_work_schedule_resume_impl(task, &work->tw, &lrumap, process_work, NULL);
+ bpf_task_work_schedule_resume(task, &work->tw, &lrumap, process_work);
return 0;
}
diff --git a/tools/testing/selftests/bpf/progs/task_work_fail.c b/tools/testing/selftests/bpf/progs/task_work_fail.c
index 1270953fd092..3186e7b4b24e 100644
--- a/tools/testing/selftests/bpf/progs/task_work_fail.c
+++ b/tools/testing/selftests/bpf/progs/task_work_fail.c
@@ -53,35 +53,35 @@ int mismatch_map(struct pt_regs *args)
work = bpf_map_lookup_elem(&arrmap, &key);
if (!work)
return 0;
- bpf_task_work_schedule_resume_impl(task, &work->tw, &hmap, process_work, NULL);
+ bpf_task_work_schedule_resume(task, &work->tw, &hmap, process_work);
return 0;
}
SEC("perf_event")
-__failure __msg("arg#1 doesn't point to a map value")
+__failure __msg("R2 doesn't point to a map value")
int no_map_task_work(struct pt_regs *args)
{
struct task_struct *task;
struct bpf_task_work tw;
task = bpf_get_current_task_btf();
- bpf_task_work_schedule_resume_impl(task, &tw, &hmap, process_work, NULL);
+ bpf_task_work_schedule_resume(task, &tw, &hmap, process_work);
return 0;
}
SEC("perf_event")
-__failure __msg("Possibly NULL pointer passed to trusted arg1")
+__failure __msg("Possibly NULL pointer passed to trusted R2")
int task_work_null(struct pt_regs *args)
{
struct task_struct *task;
task = bpf_get_current_task_btf();
- bpf_task_work_schedule_resume_impl(task, NULL, &hmap, process_work, NULL);
+ bpf_task_work_schedule_resume(task, NULL, &hmap, process_work);
return 0;
}
SEC("perf_event")
-__failure __msg("Possibly NULL pointer passed to trusted arg2")
+__failure __msg("Possibly NULL pointer passed to trusted R3")
int map_null(struct pt_regs *args)
{
struct elem *work;
@@ -91,6 +91,6 @@ int map_null(struct pt_regs *args)
work = bpf_map_lookup_elem(&arrmap, &key);
if (!work)
return 0;
- bpf_task_work_schedule_resume_impl(task, &work->tw, NULL, process_work, NULL);
+ bpf_task_work_schedule_resume(task, &work->tw, NULL, process_work);
return 0;
}
diff --git a/tools/testing/selftests/bpf/progs/task_work_stress.c b/tools/testing/selftests/bpf/progs/task_work_stress.c
index 55e555f7f41b..1d4378f351ef 100644
--- a/tools/testing/selftests/bpf/progs/task_work_stress.c
+++ b/tools/testing/selftests/bpf/progs/task_work_stress.c
@@ -51,8 +51,8 @@ int schedule_task_work(void *ctx)
if (!work)
return 0;
}
- err = bpf_task_work_schedule_signal_impl(bpf_get_current_task_btf(), &work->tw, &hmap,
- process_work, NULL);
+ err = bpf_task_work_schedule_signal(bpf_get_current_task_btf(), &work->tw, &hmap,
+ process_work);
if (err)
__sync_fetch_and_add(&schedule_error, 1);
else
diff --git a/tools/testing/selftests/bpf/progs/tcp_ca_kfunc.c b/tools/testing/selftests/bpf/progs/tcp_ca_kfunc.c
index f95862f570b7..0a3e9d35bf6f 100644
--- a/tools/testing/selftests/bpf/progs/tcp_ca_kfunc.c
+++ b/tools/testing/selftests/bpf/progs/tcp_ca_kfunc.c
@@ -8,7 +8,7 @@ extern void bbr_init(struct sock *sk) __ksym;
extern void bbr_main(struct sock *sk, u32 ack, int flag, const struct rate_sample *rs) __ksym;
extern u32 bbr_sndbuf_expand(struct sock *sk) __ksym;
extern u32 bbr_undo_cwnd(struct sock *sk) __ksym;
-extern void bbr_cwnd_event(struct sock *sk, enum tcp_ca_event event) __ksym;
+extern void bbr_cwnd_event_tx_start(struct sock *sk) __ksym;
extern u32 bbr_ssthresh(struct sock *sk) __ksym;
extern u32 bbr_min_tso_segs(struct sock *sk) __ksym;
extern void bbr_set_state(struct sock *sk, u8 new_state) __ksym;
@@ -16,6 +16,7 @@ extern void bbr_set_state(struct sock *sk, u8 new_state) __ksym;
extern void dctcp_init(struct sock *sk) __ksym;
extern void dctcp_update_alpha(struct sock *sk, u32 flags) __ksym;
extern void dctcp_cwnd_event(struct sock *sk, enum tcp_ca_event ev) __ksym;
+extern void dctcp_cwnd_event_tx_start(struct sock *sk) __ksym;
extern u32 dctcp_ssthresh(struct sock *sk) __ksym;
extern u32 dctcp_cwnd_undo(struct sock *sk) __ksym;
extern void dctcp_state(struct sock *sk, u8 new_state) __ksym;
@@ -24,7 +25,7 @@ extern void cubictcp_init(struct sock *sk) __ksym;
extern u32 cubictcp_recalc_ssthresh(struct sock *sk) __ksym;
extern void cubictcp_cong_avoid(struct sock *sk, u32 ack, u32 acked) __ksym;
extern void cubictcp_state(struct sock *sk, u8 new_state) __ksym;
-extern void cubictcp_cwnd_event(struct sock *sk, enum tcp_ca_event event) __ksym;
+extern void cubictcp_cwnd_event_tx_start(struct sock *sk) __ksym;
extern void cubictcp_acked(struct sock *sk, const struct ack_sample *sample) __ksym;
SEC("struct_ops")
@@ -69,9 +70,15 @@ u32 BPF_PROG(undo_cwnd, struct sock *sk)
SEC("struct_ops")
void BPF_PROG(cwnd_event, struct sock *sk, enum tcp_ca_event event)
{
- bbr_cwnd_event(sk, event);
dctcp_cwnd_event(sk, event);
- cubictcp_cwnd_event(sk, event);
+}
+
+SEC("struct_ops")
+void BPF_PROG(cwnd_event_tx_start, struct sock *sk)
+{
+ bbr_cwnd_event_tx_start(sk);
+ dctcp_cwnd_event_tx_start(sk);
+ cubictcp_cwnd_event_tx_start(sk);
}
SEC("struct_ops")
@@ -111,6 +118,7 @@ struct tcp_congestion_ops tcp_ca_kfunc = {
.sndbuf_expand = (void *)sndbuf_expand,
.undo_cwnd = (void *)undo_cwnd,
.cwnd_event = (void *)cwnd_event,
+ .cwnd_event_tx_start = (void *)cwnd_event_tx_start,
.ssthresh = (void *)ssthresh,
.min_tso_segs = (void *)min_tso_segs,
.set_state = (void *)set_state,
diff --git a/tools/testing/selftests/bpf/progs/tcp_ca_untrusted_btf_write.c b/tools/testing/selftests/bpf/progs/tcp_ca_untrusted_btf_write.c
new file mode 100644
index 000000000000..eda4697aac80
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tcp_ca_untrusted_btf_write.c
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "bpf_tracing_net.h"
+#include <bpf/bpf_core_read.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+SEC("struct_ops")
+void BPF_PROG(untrusted_btf_write_init, struct sock *sk)
+{
+ struct tcp_sock *tp;
+ int v = 1;
+ void *p;
+
+ p = bpf_rdonly_cast(&v, 0);
+ tp = bpf_rdonly_cast(p, bpf_core_type_id_kernel(struct tcp_sock));
+ tp->snd_cwnd = 1;
+}
+
+SEC(".struct_ops")
+struct tcp_congestion_ops untrusted_btf_write = {
+ .init = (void *)untrusted_btf_write_init,
+ .name = "bpf_ro_btf",
+};
diff --git a/tools/testing/selftests/bpf/progs/test_access_variable_array.c b/tools/testing/selftests/bpf/progs/test_access_variable_array.c
deleted file mode 100644
index 326b7d1f496a..000000000000
--- a/tools/testing/selftests/bpf/progs/test_access_variable_array.c
+++ /dev/null
@@ -1,19 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/* Copyright (c) 2023 Bytedance */
-
-#include "vmlinux.h"
-#include <bpf/bpf_helpers.h>
-#include <bpf/bpf_tracing.h>
-
-unsigned long span = 0;
-
-SEC("fentry/sched_balance_rq")
-int BPF_PROG(fentry_fentry, int this_cpu, struct rq *this_rq,
- struct sched_domain *sd)
-{
- span = sd->span[0];
-
- return 0;
-}
-
-char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_bpf_nf.c b/tools/testing/selftests/bpf/progs/test_bpf_nf.c
index f7b330ddd007..eda9b7bbab75 100644
--- a/tools/testing/selftests/bpf/progs/test_bpf_nf.c
+++ b/tools/testing/selftests/bpf/progs/test_bpf_nf.c
@@ -10,16 +10,19 @@
#define EINVAL 22
#define ENOENT 2
+#define CT_OPTS_ERROR_GUARD 0x12345678
+
#define NF_CT_ZONE_DIR_ORIG (1 << IP_CT_DIR_ORIGINAL)
#define NF_CT_ZONE_DIR_REPL (1 << IP_CT_DIR_REPLY)
extern unsigned long CONFIG_HZ __kconfig;
-int test_einval_bpf_tuple = 0;
int test_einval_reserved = 0;
int test_einval_reserved_new = 0;
int test_einval_netns_id = 0;
int test_einval_len_opts = 0;
+int test_einval_len_opts_small_lookup = 0;
+int test_einval_len_opts_small_alloc = 0;
int test_eproto_l4proto = 0;
int test_enonet_netns_id = 0;
int test_enoent_lookup = 0;
@@ -99,12 +102,6 @@ nf_ct_test(struct nf_conn *(*lookup_fn)(void *, struct bpf_sock_tuple *, u32,
__builtin_memset(&bpf_tuple, 0, sizeof(bpf_tuple.ipv4));
- ct = lookup_fn(ctx, NULL, 0, &opts_def, sizeof(opts_def));
- if (ct)
- bpf_ct_release(ct);
- else
- test_einval_bpf_tuple = opts_def.error;
-
opts_def.reserved[0] = 1;
ct = lookup_fn(ctx, &bpf_tuple, sizeof(bpf_tuple.ipv4), &opts_def,
sizeof(opts_def));
@@ -131,6 +128,28 @@ nf_ct_test(struct nf_conn *(*lookup_fn)(void *, struct bpf_sock_tuple *, u32,
else
test_einval_len_opts = opts_def.error;
+ opts_def.error = CT_OPTS_ERROR_GUARD;
+ ct = lookup_fn(ctx, &bpf_tuple, sizeof(bpf_tuple.ipv4), &opts_def,
+ sizeof(opts_def.netns_id));
+ if (ct) {
+ bpf_ct_release(ct);
+ test_einval_len_opts_small_lookup = -EINVAL;
+ } else {
+ test_einval_len_opts_small_lookup = opts_def.error;
+ }
+
+ opts_def.error = CT_OPTS_ERROR_GUARD;
+ ct = alloc_fn(ctx, &bpf_tuple, sizeof(bpf_tuple.ipv4), &opts_def,
+ sizeof(opts_def.netns_id));
+ if (ct) {
+ ct = bpf_ct_insert_entry(ct);
+ if (ct)
+ bpf_ct_release(ct);
+ test_einval_len_opts_small_alloc = -EINVAL;
+ } else {
+ test_einval_len_opts_small_alloc = opts_def.error;
+ }
+
opts_def.l4proto = IPPROTO_ICMP;
ct = lookup_fn(ctx, &bpf_tuple, sizeof(bpf_tuple.ipv4), &opts_def,
sizeof(opts_def));
@@ -171,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;
@@ -274,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/test_bpf_nf_fail.c b/tools/testing/selftests/bpf/progs/test_bpf_nf_fail.c
index a586f087ffeb..332cda89caba 100644
--- a/tools/testing/selftests/bpf/progs/test_bpf_nf_fail.c
+++ b/tools/testing/selftests/bpf/progs/test_bpf_nf_fail.c
@@ -4,6 +4,7 @@
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
+#include "bpf_misc.h"
struct nf_conn;
@@ -18,6 +19,10 @@ struct nf_conn *bpf_skb_ct_alloc(struct __sk_buff *, struct bpf_sock_tuple *, u3
struct bpf_ct_opts___local *, u32) __ksym;
struct nf_conn *bpf_skb_ct_lookup(struct __sk_buff *, struct bpf_sock_tuple *, u32,
struct bpf_ct_opts___local *, u32) __ksym;
+struct nf_conn *bpf_xdp_ct_alloc(struct xdp_md *, struct bpf_sock_tuple *, u32,
+ struct bpf_ct_opts___local *, u32) __ksym;
+struct nf_conn *bpf_xdp_ct_lookup(struct xdp_md *, struct bpf_sock_tuple *, u32,
+ struct bpf_ct_opts___local *, u32) __ksym;
struct nf_conn *bpf_ct_insert_entry(struct nf_conn *) __ksym;
void bpf_ct_release(struct nf_conn *) __ksym;
void bpf_ct_set_timeout(struct nf_conn *, u32) __ksym;
@@ -146,4 +151,56 @@ int change_status_after_alloc(struct __sk_buff *ctx)
return 0;
}
+SEC("?tc")
+__failure __msg("Possibly NULL pointer passed to trusted R2")
+int lookup_null_bpf_tuple(struct __sk_buff *ctx)
+{
+ struct bpf_ct_opts___local opts = {};
+ struct nf_conn *ct;
+
+ ct = bpf_skb_ct_lookup(ctx, NULL, 0, &opts, sizeof(opts));
+ if (ct)
+ bpf_ct_release(ct);
+ return 0;
+}
+
+SEC("?tc")
+__failure __msg("Possibly NULL pointer passed to trusted R4")
+int lookup_null_bpf_opts(struct __sk_buff *ctx)
+{
+ struct bpf_sock_tuple tup = {};
+ struct nf_conn *ct;
+
+ ct = bpf_skb_ct_lookup(ctx, &tup, sizeof(tup.ipv4), NULL, sizeof(struct bpf_ct_opts___local));
+ if (ct)
+ bpf_ct_release(ct);
+ return 0;
+}
+
+SEC("?xdp")
+__failure __msg("Possibly NULL pointer passed to trusted R2")
+int xdp_lookup_null_bpf_tuple(struct xdp_md *ctx)
+{
+ struct bpf_ct_opts___local opts = {};
+ struct nf_conn *ct;
+
+ ct = bpf_xdp_ct_lookup(ctx, NULL, 0, &opts, sizeof(opts));
+ if (ct)
+ bpf_ct_release(ct);
+ return 0;
+}
+
+SEC("?xdp")
+__failure __msg("Possibly NULL pointer passed to trusted R4")
+int xdp_lookup_null_bpf_opts(struct xdp_md *ctx)
+{
+ struct bpf_sock_tuple tup = {};
+ struct nf_conn *ct;
+
+ ct = bpf_xdp_ct_lookup(ctx, &tup, sizeof(tup.ipv4), NULL, sizeof(struct bpf_ct_opts___local));
+ if (ct)
+ bpf_ct_release(ct);
+ return 0;
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_btf_decl_tag.c b/tools/testing/selftests/bpf/progs/test_btf_decl_tag.c
index c88ccc53529a..0c3df19626cb 100644
--- a/tools/testing/selftests/bpf/progs/test_btf_decl_tag.c
+++ b/tools/testing/selftests/bpf/progs/test_btf_decl_tag.c
@@ -33,7 +33,7 @@ struct {
} hashmap1 SEC(".maps");
-static __noinline int foo(int x __tag1 __tag2) __tag1 __tag2
+static __noinline __tag1 __tag2 int foo(int x __tag1 __tag2)
{
struct key_t key;
value_t val = {};
diff --git a/tools/testing/selftests/bpf/progs/test_ctx.c b/tools/testing/selftests/bpf/progs/test_ctx.c
new file mode 100644
index 000000000000..7d4995506717
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_ctx.c
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Valve Corporation.
+ * Author: Changwoo Min <changwoo@igalia.com>
+ */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_experimental.h"
+
+char _license[] SEC("license") = "GPL";
+
+extern void bpf_kfunc_trigger_ctx_check(void) __ksym;
+
+int count_hardirq;
+int count_softirq;
+int count_task;
+
+/* Triggered via bpf_prog_test_run from user-space */
+SEC("syscall")
+int trigger_all_contexts(void *ctx)
+{
+ if (bpf_in_task())
+ __sync_fetch_and_add(&count_task, 1);
+
+ /* Trigger the firing of a hardirq and softirq for test. */
+ bpf_kfunc_trigger_ctx_check();
+ return 0;
+}
+
+/* Observer for HardIRQ */
+SEC("fentry/bpf_testmod_test_hardirq_fn")
+int BPF_PROG(on_hardirq)
+{
+ if (bpf_in_hardirq())
+ __sync_fetch_and_add(&count_hardirq, 1);
+ return 0;
+}
+
+/* Observer for SoftIRQ */
+SEC("fentry/bpf_testmod_test_softirq_fn")
+int BPF_PROG(on_softirq)
+{
+ if (bpf_in_serving_softirq())
+ __sync_fetch_and_add(&count_softirq, 1);
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/test_d_path.c b/tools/testing/selftests/bpf/progs/test_d_path.c
index 84e1f883f97b..561b2f861808 100644
--- a/tools/testing/selftests/bpf/progs/test_d_path.c
+++ b/tools/testing/selftests/bpf/progs/test_d_path.c
@@ -17,6 +17,7 @@ int rets_close[MAX_FILES] = {};
int called_stat = 0;
int called_close = 0;
+int path_match_fallocate = 0;
SEC("fentry/security_inode_getattr")
int BPF_PROG(prog_stat, struct path *path, struct kstat *stat,
@@ -62,4 +63,26 @@ int BPF_PROG(prog_close, struct file *file, void *id)
return 0;
}
+SEC("fentry/vfs_fallocate")
+int BPF_PROG(prog_fallocate, struct file *file, int mode, loff_t offset, loff_t len)
+{
+ pid_t pid = bpf_get_current_pid_tgid() >> 32;
+ int ret = 0;
+ char path_fallocate[MAX_PATH_LEN] = {};
+
+ if (pid != my_pid)
+ return 0;
+
+ ret = bpf_d_path(&file->f_path,
+ path_fallocate, MAX_PATH_LEN);
+ if (ret < 0)
+ return 0;
+
+ if (!path_fallocate[0])
+ return 0;
+
+ path_match_fallocate = 1;
+ return 0;
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_dst_clear.c b/tools/testing/selftests/bpf/progs/test_dst_clear.c
new file mode 100644
index 000000000000..c22a6eeb4798
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_dst_clear.c
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include "vmlinux.h"
+#include "bpf_tracing_net.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+#define UDP_TEST_PORT 7777
+
+void *bpf_cast_to_kern_ctx(void *) __ksym;
+
+bool had_dst = false;
+bool dst_cleared = false;
+
+SEC("tc/egress")
+int dst_clear(struct __sk_buff *skb)
+{
+ struct sk_buff *kskb;
+ struct iphdr iph;
+ struct udphdr udph;
+ int err;
+
+ if (skb->protocol != __bpf_constant_htons(ETH_P_IP))
+ return TC_ACT_OK;
+
+ if (bpf_skb_load_bytes(skb, ETH_HLEN, &iph, sizeof(iph)))
+ return TC_ACT_OK;
+
+ if (iph.protocol != IPPROTO_UDP)
+ return TC_ACT_OK;
+
+ if (bpf_skb_load_bytes(skb, ETH_HLEN + sizeof(iph), &udph, sizeof(udph)))
+ return TC_ACT_OK;
+
+ if (udph.dest != __bpf_constant_htons(UDP_TEST_PORT))
+ return TC_ACT_OK;
+
+ kskb = bpf_cast_to_kern_ctx(skb);
+ had_dst = (kskb->_skb_refdst != 0);
+
+ /* Same-protocol encap (IPIP): protocol stays IPv4, but the dst
+ * from the original routing is no longer valid for the outer hdr.
+ */
+ err = bpf_skb_adjust_room(skb, (s32)sizeof(struct iphdr),
+ BPF_ADJ_ROOM_MAC,
+ BPF_F_ADJ_ROOM_FIXED_GSO |
+ BPF_F_ADJ_ROOM_ENCAP_L3_IPV4);
+ if (err)
+ return TC_ACT_SHOT;
+
+ dst_cleared = (kskb->_skb_refdst == 0);
+
+ return TC_ACT_SHOT;
+}
+
+char __license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_fill_link_info.c b/tools/testing/selftests/bpf/progs/test_fill_link_info.c
index fac33a14f200..c85081538e93 100644
--- a/tools/testing/selftests/bpf/progs/test_fill_link_info.c
+++ b/tools/testing/selftests/bpf/progs/test_fill_link_info.c
@@ -12,7 +12,7 @@ extern bool CONFIG_PPC64 __kconfig __weak;
/* This function is here to have CONFIG_X86_KERNEL_IBT,
* CONFIG_PPC_FTRACE_OUT_OF_LINE, CONFIG_KPROBES_ON_FTRACE,
- * CONFIG_PPC6 used and added to object BTF.
+ * CONFIG_PPC64 used and added to object BTF.
*/
int unused(void)
{
@@ -58,4 +58,10 @@ int BPF_PROG(umulti_run)
return 0;
}
+SEC("fentry.multi")
+int BPF_PROG(tmulti_run)
+{
+ return 0;
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_global_func3.c b/tools/testing/selftests/bpf/progs/test_global_func3.c
index 142b682d3c2f..b66abb350fb0 100644
--- a/tools/testing/selftests/bpf/progs/test_global_func3.c
+++ b/tools/testing/selftests/bpf/progs/test_global_func3.c
@@ -5,57 +5,105 @@
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
-__attribute__ ((noinline))
+static __attribute__ ((noinline))
int f1(struct __sk_buff *skb)
{
return skb->len;
}
-__attribute__ ((noinline))
+static __attribute__ ((noinline))
int f2(int val, struct __sk_buff *skb)
{
return f1(skb) + val;
}
-__attribute__ ((noinline))
+static __attribute__ ((noinline))
int f3(int val, struct __sk_buff *skb, int var)
{
return f2(var, skb) + val;
}
-__attribute__ ((noinline))
+static __attribute__ ((noinline))
int f4(struct __sk_buff *skb)
{
return f3(1, skb, 2);
}
-__attribute__ ((noinline))
+static __attribute__ ((noinline))
int f5(struct __sk_buff *skb)
{
return f4(skb);
}
-__attribute__ ((noinline))
+static __attribute__ ((noinline))
int f6(struct __sk_buff *skb)
{
return f5(skb);
}
-__attribute__ ((noinline))
+static __attribute__ ((noinline))
int f7(struct __sk_buff *skb)
{
return f6(skb);
}
-__attribute__ ((noinline))
+static __attribute__ ((noinline))
int f8(struct __sk_buff *skb)
{
return f7(skb);
}
+static __attribute__ ((noinline))
+int f9(struct __sk_buff *skb)
+{
+ return f8(skb);
+}
+
+static __attribute__ ((noinline))
+int f10(struct __sk_buff *skb)
+{
+ return f9(skb);
+}
+
+static __attribute__ ((noinline))
+int f11(struct __sk_buff *skb)
+{
+ return f10(skb);
+}
+
+static __attribute__ ((noinline))
+int f12(struct __sk_buff *skb)
+{
+ return f11(skb);
+}
+
+static __attribute__ ((noinline))
+int f13(struct __sk_buff *skb)
+{
+ return f12(skb);
+}
+
+static __attribute__ ((noinline))
+int f14(struct __sk_buff *skb)
+{
+ return f13(skb);
+}
+
+static __attribute__ ((noinline))
+int f15(struct __sk_buff *skb)
+{
+ return f14(skb);
+}
+
+static __attribute__ ((noinline))
+int f16(struct __sk_buff *skb)
+{
+ return f15(skb);
+}
+
SEC("tc")
-__failure __msg("the call stack of 8 frames")
+__failure __msg("the call stack of 17 frames")
int global_func3(struct __sk_buff *skb)
{
- return f8(skb);
+ return f16(skb);
}
diff --git a/tools/testing/selftests/bpf/progs/test_global_func7.c b/tools/testing/selftests/bpf/progs/test_global_func7.c
index f182febfde3c..9e59625c1c92 100644
--- a/tools/testing/selftests/bpf/progs/test_global_func7.c
+++ b/tools/testing/selftests/bpf/progs/test_global_func7.c
@@ -12,7 +12,7 @@ void foo(struct __sk_buff *skb)
}
SEC("tc")
-__failure __msg("foo() doesn't return scalar")
+__success
int global_func7(struct __sk_buff *skb)
{
foo(skb);
diff --git a/tools/testing/selftests/bpf/progs/test_global_func_deep_stack.c b/tools/testing/selftests/bpf/progs/test_global_func_deep_stack.c
new file mode 100644
index 000000000000..1b634b543b62
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_global_func_deep_stack.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 Meta Platforms, Inc and affiliates. */
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+/*
+ * Macro tricks to tersely define for long non-recursive call chains. Add
+ * computation to the functions prevent tail recursion from reducing the
+ * stack size to 0.
+ */
+
+#define CAT(a, b) a ## b
+#define XCAT(a, b) CAT(a, b)
+
+#define F_0 \
+__attribute__((noinline)) \
+int f0(unsigned long a) \
+{ \
+ volatile long b = a + 16; \
+ if (a == 0) \
+ return 0; \
+ return b; \
+}
+
+#define FN(n, prev) \
+__attribute__((noinline)) \
+int XCAT(f, n)(unsigned long a) \
+{ \
+ volatile long b = XCAT(f, prev)(a - 1); \
+ if (!b) \
+ return 0; \
+ return b + 1; \
+}
+
+/* Call chain 33 levels deep. */
+#define F_1 F_0 FN(1, 0)
+#define F_2 F_1 FN(2, 1)
+#define F_3 F_2 FN(3, 2)
+#define F_4 F_3 FN(4, 3)
+#define F_5 F_4 FN(5, 4)
+#define F_6 F_5 FN(6, 5)
+#define F_7 F_6 FN(7, 6)
+#define F_8 F_7 FN(8, 7)
+#define F_9 F_8 FN(9, 8)
+#define F_10 F_9 FN(10, 9)
+#define F_11 F_10 FN(11, 10)
+#define F_12 F_11 FN(12, 11)
+#define F_13 F_12 FN(13, 12)
+#define F_14 F_13 FN(14, 13)
+#define F_15 F_14 FN(15, 14)
+#define F_16 F_15 FN(16, 15)
+#define F_17 F_16 FN(17, 16)
+#define F_18 F_17 FN(18, 17)
+#define F_19 F_18 FN(19, 18)
+#define F_20 F_19 FN(20, 19)
+#define F_21 F_20 FN(21, 20)
+#define F_22 F_21 FN(22, 21)
+#define F_23 F_22 FN(23, 22)
+#define F_24 F_23 FN(24, 23)
+#define F_25 F_24 FN(25, 24)
+#define F_26 F_25 FN(26, 25)
+#define F_27 F_26 FN(27, 26)
+#define F_28 F_27 FN(28, 27)
+#define F_29 F_28 FN(29, 28)
+#define F_30 F_29 FN(30, 29)
+#define F_31 F_30 FN(31, 30)
+#define F_32 F_31 FN(32, 31)
+
+#define CAT2(a, b) a ## b
+#define XCAT2(a, b) CAT2(a, b)
+
+#define F(n) XCAT2(F_, n)
+
+F(32)
+
+/* Ensure that even 32 levels deep, the function verifies. */
+SEC("syscall")
+__success
+int global_func_deep_stack_success(struct __sk_buff *skb)
+{
+ return f31(55);
+}
+
+/*
+ * Check we actually honor stack limits (33 * 16 = 528 > 512 = MAX_STACK_DEPTH).
+ * The stack depth is 16 because the verifier calls round_up_stack_depth() on
+ * the size.
+ */
+SEC("syscall")
+__failure __msg("combined stack size of 34 calls")
+int global_func_deep_stack_fail(struct __sk_buff *skb)
+{
+ return f32(123);
+}
diff --git a/tools/testing/selftests/bpf/progs/test_global_percpu_data.c b/tools/testing/selftests/bpf/progs/test_global_percpu_data.c
new file mode 100644
index 000000000000..5dc21b3b4cb5
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_global_percpu_data.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+/* Used for testing map name. */
+int loong SEC(".percpu.looooooooong");
+int data3 SEC(".data.percpu");
+int data2 SEC(".percpu.data");
+
+int run;
+/* cpu_id as array to verify map value resizing. */
+int cpu_id[1] SEC(".percpu");
+int data SEC(".percpu") = -1;
+int nums[7] SEC(".percpu");
+bool set SEC(".percpu") = false;
+struct {
+ char set;
+ int i;
+ int nums[7];
+} struct_data SEC(".percpu") = {
+ .set = 0,
+ .i = -1,
+};
+
+SEC("raw_tp/task_rename")
+__auxiliary
+int update_percpu_data(void *ctx)
+{
+ struct_data.nums[6] = 0xc0de;
+ struct_data.set = 1;
+ struct_data.i = 1;
+ nums[6] = 0xc0de;
+ data = 1;
+ run++;
+ set = true;
+ cpu_id[0] = bpf_get_smp_processor_id();
+ return 0;
+}
+
+static const char fmt[] SEC(".percpu.fmt") = "data %d\n";
+
+SEC("?kprobe")
+__failure __msg("R{{[0-9]+}} points to percpu_array map which cannot be used as const string")
+int verifier_strncmp(void *ctx)
+{
+ return bpf_strncmp("test", 5, fmt);
+}
+
+SEC("?kprobe")
+__failure __msg("R{{[0-9]+}} points to percpu_array map which cannot be used as const string")
+int verifier_snprintf(void *ctx)
+{
+ u64 args[] = { data };
+ char buf[128];
+ int len;
+
+ len = bpf_snprintf(buf, sizeof(buf), fmt, args, sizeof(args));
+ if (len > 0)
+ bpf_printk("snprintf: %s\n", buf);
+ return 0;
+}
+
+volatile const __u32 num_cpus = 0;
+volatile const int num_off;
+volatile const int elem_sz;
+__u32 sum = 0;
+bool run_iter = false;
+
+SEC("iter/bpf_map_elem")
+__auxiliary
+int dump_percpu_data(struct bpf_iter__bpf_map_elem *ctx)
+{
+ void *pptr = ctx->value;
+ int i;
+
+ if (!pptr)
+ return 0;
+
+ run_iter = true;
+
+ for (i = 0; i < num_cpus; i++) {
+ sum += *(int *) (pptr + num_off);
+ pptr += elem_sz;
+ }
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c b/tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c
index 061befb004c2..bf48fc43c7ab 100644
--- a/tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c
+++ b/tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c
@@ -11,12 +11,7 @@
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include "bpf_misc.h"
-
-extern struct bpf_key *bpf_lookup_system_key(__u64 id) __ksym;
-extern void bpf_key_put(struct bpf_key *key) __ksym;
-extern int bpf_verify_pkcs7_signature(struct bpf_dynptr *data_ptr,
- struct bpf_dynptr *sig_ptr,
- struct bpf_key *trusted_keyring) __ksym;
+#include "bpf_kfuncs.h"
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
@@ -38,20 +33,19 @@ SEC("?lsm.s/bpf")
__failure __msg("cannot pass in dynptr at an offset=-8")
int BPF_PROG(not_valid_dynptr, int cmd, union bpf_attr *attr, unsigned int size, bool kernel)
{
- unsigned long val;
+ unsigned long val = 0;
return bpf_verify_pkcs7_signature((struct bpf_dynptr *)&val,
(struct bpf_dynptr *)&val, NULL);
}
SEC("?lsm.s/bpf")
-__failure __msg("arg#0 expected pointer to stack or const struct bpf_dynptr")
+__failure __msg("R1 expected pointer to stack or const struct bpf_dynptr")
int BPF_PROG(not_ptr_to_stack, int cmd, union bpf_attr *attr, unsigned int size, bool kernel)
{
- unsigned long val = 0;
+ static struct bpf_dynptr val;
- return bpf_verify_pkcs7_signature((struct bpf_dynptr *)val,
- (struct bpf_dynptr *)val, NULL);
+ return bpf_verify_pkcs7_signature(&val, &val, NULL);
}
SEC("lsm.s/bpf")
diff --git a/tools/testing/selftests/bpf/progs/test_kfunc_param_nullable.c b/tools/testing/selftests/bpf/progs/test_kfunc_param_nullable.c
index 0ad1bf1ede8d..ca35b92ea095 100644
--- a/tools/testing/selftests/bpf/progs/test_kfunc_param_nullable.c
+++ b/tools/testing/selftests/bpf/progs/test_kfunc_param_nullable.c
@@ -29,7 +29,7 @@ int kfunc_dynptr_nullable_test2(struct __sk_buff *skb)
}
SEC("tc")
-__failure __msg("expected pointer to stack or const struct bpf_dynptr")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
int kfunc_dynptr_nullable_test3(struct __sk_buff *skb)
{
struct bpf_dynptr data;
diff --git a/tools/testing/selftests/bpf/progs/test_lirc_mode2_kern.c b/tools/testing/selftests/bpf/progs/test_lirc_mode2_kern.c
index 7a6620671a83..cbe4284c032f 100644
--- a/tools/testing/selftests/bpf/progs/test_lirc_mode2_kern.c
+++ b/tools/testing/selftests/bpf/progs/test_lirc_mode2_kern.c
@@ -13,9 +13,9 @@ int bpf_decoder(unsigned int *sample)
if (LIRC_IS_PULSE(*sample)) {
unsigned int duration = LIRC_VALUE(*sample);
- if (duration & 0x10000)
+ if (duration & 0x1000)
bpf_rc_keydown(sample, 0x40, duration & 0xffff, 0);
- if (duration & 0x20000)
+ if (duration & 0x2000)
bpf_rc_pointer_rel(sample, (duration >> 8) & 0xff,
duration & 0xff);
}
diff --git a/tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c b/tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c
index d6cb986e7533..4a934fccf8f5 100644
--- a/tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c
+++ b/tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c
@@ -1,11 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
-#include <stddef.h>
+#include "vmlinux.h"
#include <string.h>
-#include <linux/bpf.h>
-#include <linux/ip.h>
-#include <linux/ipv6.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
+#include <bpf/bpf_tracing.h>
struct grehdr {
__be16 flags;
@@ -64,13 +62,13 @@ int bpf_lwt_encap_gre6(struct __sk_buff *skb)
hdr.ip6hdr.nexthdr = 47; /* IPPROTO_GRE */
hdr.ip6hdr.hop_limit = 0x40;
/* fb01::1 */
- hdr.ip6hdr.saddr.s6_addr[0] = 0xfb;
- hdr.ip6hdr.saddr.s6_addr[1] = 1;
- hdr.ip6hdr.saddr.s6_addr[15] = 1;
+ hdr.ip6hdr.saddr.in6_u.u6_addr8[0] = 0xfb;
+ hdr.ip6hdr.saddr.in6_u.u6_addr8[1] = 1;
+ hdr.ip6hdr.saddr.in6_u.u6_addr8[15] = 1;
/* fb10::1 */
- hdr.ip6hdr.daddr.s6_addr[0] = 0xfb;
- hdr.ip6hdr.daddr.s6_addr[1] = 0x10;
- hdr.ip6hdr.daddr.s6_addr[15] = 1;
+ hdr.ip6hdr.daddr.in6_u.u6_addr8[0] = 0xfb;
+ hdr.ip6hdr.daddr.in6_u.u6_addr8[1] = 0x10;
+ hdr.ip6hdr.daddr.in6_u.u6_addr8[15] = 1;
hdr.greh.protocol = skb->protocol;
@@ -82,4 +80,141 @@ int bpf_lwt_encap_gre6(struct __sk_buff *skb)
return BPF_LWT_REROUTE;
}
+#define VXLAN_PORT 4789
+#define VXLAN_FLAGS 0x08000000
+#define VXLAN_VNI 1
+
+#define ETH_ALEN 6 /* Octets in one ethernet addr */
+#define ETH_P_IP 0x0800 /* Internet Protocol packet */
+#define ETH_P_IPV6 0x86DD /* IPv6 over bluebook */
+
+static const __u8 bcast[ETH_ALEN] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+};
+
+static const __u8 srcmac[ETH_ALEN] = {
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x01,
+};
+
+SEC("encap_vxlan")
+int bpf_lwt_encap_vxlan(struct __sk_buff *skb)
+{
+ struct encap_hdr {
+ struct iphdr iph;
+ struct udphdr udph;
+ struct vxlanhdr vxh;
+ struct ethhdr eth;
+ } __attribute__((__packed__)) hdr;
+ int err;
+
+ memset(&hdr, 0, sizeof(hdr));
+
+ hdr.iph.ihl = 5;
+ hdr.iph.version = 4;
+ hdr.iph.ttl = 0x40;
+ hdr.iph.protocol = 17; /* IPPROTO_UDP */
+ hdr.iph.tot_len = bpf_htons(skb->len + sizeof(hdr));
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ hdr.iph.saddr = 0x640510ac; /* 172.16.5.100 */
+ hdr.iph.daddr = 0x641110ac; /* 172.16.17.100 */
+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+ hdr.iph.saddr = 0xac100564; /* 172.16.5.100 */
+ hdr.iph.daddr = 0xac101164; /* 172.16.17.100 */
+#else
+#error "Fix your compiler's __BYTE_ORDER__?!"
+#endif
+
+ hdr.udph.source = bpf_htons(VXLAN_PORT);
+ hdr.udph.dest = bpf_htons(VXLAN_PORT);
+ hdr.udph.len = bpf_htons(skb->len + sizeof(hdr.udph) + sizeof(hdr.vxh) +
+ sizeof(hdr.eth));
+
+ hdr.vxh.vx_flags = bpf_htonl(VXLAN_FLAGS);
+ hdr.vxh.vx_vni = bpf_htonl(VXLAN_VNI << 8);
+
+ __builtin_memcpy(hdr.eth.h_dest, bcast, ETH_ALEN);
+ __builtin_memcpy(hdr.eth.h_source, srcmac, ETH_ALEN);
+ hdr.eth.h_proto = bpf_htons(ETH_P_IP);
+
+ err = bpf_lwt_push_encap(skb, BPF_LWT_ENCAP_IP, &hdr, sizeof(hdr));
+ if (err)
+ return BPF_DROP;
+
+ return BPF_LWT_REROUTE;
+}
+
+SEC("encap_vxlan6")
+int bpf_lwt_encap_vxlan6(struct __sk_buff *skb)
+{
+ struct encap_hdr {
+ struct ipv6hdr ip6hdr;
+ struct udphdr udph;
+ struct vxlanhdr vxh;
+ struct ethhdr eth;
+ } __attribute__((__packed__)) hdr;
+ int err;
+
+ memset(&hdr, 0, sizeof(hdr));
+
+ hdr.ip6hdr.version = 6;
+ hdr.ip6hdr.nexthdr = 17; /* IPPROTO_UDP */
+ hdr.ip6hdr.hop_limit = 0x40;
+ hdr.ip6hdr.payload_len = bpf_htons(skb->len + sizeof(hdr.udph) + sizeof(hdr.vxh) +
+ sizeof(hdr.eth));
+ /* fb05::1 */
+ hdr.ip6hdr.saddr.in6_u.u6_addr8[0] = 0xfb;
+ hdr.ip6hdr.saddr.in6_u.u6_addr8[1] = 0x05;
+ hdr.ip6hdr.saddr.in6_u.u6_addr8[15] = 1;
+ /* fb11::1 */
+ hdr.ip6hdr.daddr.in6_u.u6_addr8[0] = 0xfb;
+ hdr.ip6hdr.daddr.in6_u.u6_addr8[1] = 0x11;
+ hdr.ip6hdr.daddr.in6_u.u6_addr8[15] = 1;
+
+ hdr.udph.source = bpf_htons(VXLAN_PORT);
+ hdr.udph.dest = bpf_htons(VXLAN_PORT);
+ hdr.udph.len = bpf_htons(skb->len + sizeof(hdr.udph) + sizeof(hdr.vxh) +
+ sizeof(hdr.eth));
+
+ hdr.vxh.vx_flags = bpf_htonl(VXLAN_FLAGS);
+ hdr.vxh.vx_vni = bpf_htonl(VXLAN_VNI << 8);
+
+ __builtin_memcpy(hdr.eth.h_dest, bcast, ETH_ALEN);
+ __builtin_memcpy(hdr.eth.h_source, srcmac, ETH_ALEN);
+ hdr.eth.h_proto = bpf_htons(ETH_P_IPV6);
+
+ err = bpf_lwt_push_encap(skb, BPF_LWT_ENCAP_IP, &hdr, sizeof(hdr));
+ if (err)
+ return BPF_DROP;
+
+ return BPF_LWT_REROUTE;
+}
+
+volatile const int tgt_ip_version;
+
+__u16 transport_hdr = 0;
+__u16 network_hdr = 0;
+bool fexit_triggered = false;
+
+SEC("?fexit/bpf_lwt_push_ip_encap")
+int BPF_PROG(fexit_lwt_push_ip_encap, struct sk_buff *skb, void *hdr, u32 len, bool ingress,
+ int retval)
+{
+ struct iphdr *iph;
+
+ if (retval || fexit_triggered)
+ return 0;
+
+ iph = (typeof(iph)) (skb->head + skb->network_header);
+ if (iph->version != tgt_ip_version)
+ return 0;
+
+ if ((iph->version == 4 && iph->protocol == 17 /* IPPROTO_UDP */) ||
+ (iph->version == 6 && ((struct ipv6hdr *)iph)->nexthdr == 17 /* IPPROTO_UDP */)) {
+ fexit_triggered = true;
+ transport_hdr = skb->transport_header;
+ network_hdr = skb->network_header;
+ }
+ return 0;
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_misc_tcp_hdr_options.c b/tools/testing/selftests/bpf/progs/test_misc_tcp_hdr_options.c
index d487153a839d..ed5a0011b863 100644
--- a/tools/testing/selftests/bpf/progs/test_misc_tcp_hdr_options.c
+++ b/tools/testing/selftests/bpf/progs/test_misc_tcp_hdr_options.c
@@ -29,6 +29,10 @@ unsigned int nr_syn = 0;
unsigned int nr_fin = 0;
unsigned int nr_hwtstamp = 0;
+bool nodelay_est_ok = false;
+bool nodelay_hdr_len_reject = false;
+bool nodelay_write_hdr_reject = false;
+
/* Check the header received from the active side */
static int __check_active_hdr_in(struct bpf_sock_ops *skops, bool check_syn)
{
@@ -300,7 +304,7 @@ static int handle_passive_estab(struct bpf_sock_ops *skops)
SEC("sockops")
int misc_estab(struct bpf_sock_ops *skops)
{
- int true_val = 1;
+ int true_val = 1, false_val = 0, ret;
switch (skops->op) {
case BPF_SOCK_OPS_TCP_LISTEN_CB:
@@ -316,10 +320,19 @@ int misc_estab(struct bpf_sock_ops *skops)
case BPF_SOCK_OPS_PARSE_HDR_OPT_CB:
return handle_parse_hdr(skops);
case BPF_SOCK_OPS_HDR_OPT_LEN_CB:
+ ret = bpf_setsockopt(skops, SOL_TCP, TCP_NODELAY, &true_val, sizeof(true_val));
+ if (ret == -EOPNOTSUPP)
+ nodelay_hdr_len_reject = true;
return handle_hdr_opt_len(skops);
case BPF_SOCK_OPS_WRITE_HDR_OPT_CB:
+ ret = bpf_setsockopt(skops, SOL_TCP, TCP_NODELAY, &true_val, sizeof(true_val));
+ if (ret == -EOPNOTSUPP)
+ nodelay_write_hdr_reject = true;
return handle_write_hdr_opt(skops);
case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB:
+ ret = bpf_setsockopt(skops, SOL_TCP, TCP_NODELAY, &false_val, sizeof(false_val));
+ if (!ret)
+ nodelay_est_ok = true;
return handle_passive_estab(skops);
}
diff --git a/tools/testing/selftests/bpf/progs/test_module_attach.c b/tools/testing/selftests/bpf/progs/test_module_attach.c
index 03d7f89787a1..5609e388fb58 100644
--- a/tools/testing/selftests/bpf/progs/test_module_attach.c
+++ b/tools/testing/selftests/bpf/progs/test_module_attach.c
@@ -7,23 +7,21 @@
#include <bpf/bpf_core_read.h>
#include "../test_kmods/bpf_testmod.h"
-__u32 raw_tp_read_sz = 0;
+__u32 sz = 0;
-SEC("raw_tp/bpf_testmod_test_read")
+SEC("?raw_tp/bpf_testmod_test_read")
int BPF_PROG(handle_raw_tp,
struct task_struct *task, struct bpf_testmod_test_read_ctx *read_ctx)
{
- raw_tp_read_sz = BPF_CORE_READ(read_ctx, len);
+ sz = BPF_CORE_READ(read_ctx, len);
return 0;
}
-__u32 raw_tp_bare_write_sz = 0;
-
-SEC("raw_tp/bpf_testmod_test_write_bare_tp")
+SEC("?raw_tp/bpf_testmod_test_write_bare_tp")
int BPF_PROG(handle_raw_tp_bare,
struct task_struct *task, struct bpf_testmod_test_write_ctx *write_ctx)
{
- raw_tp_bare_write_sz = BPF_CORE_READ(write_ctx, len);
+ sz = BPF_CORE_READ(write_ctx, len);
return 0;
}
@@ -31,7 +29,7 @@ int raw_tp_writable_bare_in_val = 0;
int raw_tp_writable_bare_early_ret = 0;
int raw_tp_writable_bare_out_val = 0;
-SEC("raw_tp.w/bpf_testmod_test_writable_bare_tp")
+SEC("?raw_tp.w/bpf_testmod_test_writable_bare_tp")
int BPF_PROG(handle_raw_tp_writable_bare,
struct bpf_testmod_test_writable_ctx *writable)
{
@@ -41,76 +39,65 @@ int BPF_PROG(handle_raw_tp_writable_bare,
return 0;
}
-__u32 tp_btf_read_sz = 0;
-
-SEC("tp_btf/bpf_testmod_test_read")
+SEC("?tp_btf/bpf_testmod_test_read")
int BPF_PROG(handle_tp_btf,
struct task_struct *task, struct bpf_testmod_test_read_ctx *read_ctx)
{
- tp_btf_read_sz = read_ctx->len;
+ sz = read_ctx->len;
return 0;
}
-__u32 fentry_read_sz = 0;
-
-SEC("fentry/bpf_testmod_test_read")
+SEC("?fentry/bpf_testmod_test_read")
int BPF_PROG(handle_fentry,
struct file *file, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buf, loff_t off, size_t len)
{
- fentry_read_sz = len;
+ sz = len;
return 0;
}
-__u32 fentry_manual_read_sz = 0;
-
-SEC("fentry")
+SEC("?fentry")
int BPF_PROG(handle_fentry_manual,
struct file *file, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buf, loff_t off, size_t len)
{
- fentry_manual_read_sz = len;
+ sz = len;
return 0;
}
-__u32 fentry_explicit_read_sz = 0;
-
-SEC("fentry/bpf_testmod:bpf_testmod_test_read")
+SEC("?fentry/bpf_testmod:bpf_testmod_test_read")
int BPF_PROG(handle_fentry_explicit,
struct file *file, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buf, loff_t off, size_t len)
{
- fentry_explicit_read_sz = len;
+ sz = len;
return 0;
}
-__u32 fentry_explicit_manual_read_sz = 0;
-
-SEC("fentry")
+SEC("?fentry")
int BPF_PROG(handle_fentry_explicit_manual,
struct file *file, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buf, loff_t off, size_t len)
{
- fentry_explicit_manual_read_sz = len;
+ sz = len;
return 0;
}
-__u32 fexit_read_sz = 0;
-int fexit_ret = 0;
+int retval = 0;
-SEC("fexit/bpf_testmod_test_read")
+SEC("?fexit/bpf_testmod_test_read")
int BPF_PROG(handle_fexit,
struct file *file, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buf, loff_t off, size_t len,
int ret)
{
- fexit_read_sz = len;
- fexit_ret = ret;
+ sz = len;
+ retval = ret;
return 0;
}
-SEC("fexit/bpf_testmod_return_ptr")
+SEC("?fexit/bpf_testmod_return_ptr")
int BPF_PROG(handle_fexit_ret, int arg, struct file *ret)
{
long buf = 0;
@@ -122,18 +109,16 @@ int BPF_PROG(handle_fexit_ret, int arg, struct file *ret)
return 0;
}
-__u32 fmod_ret_read_sz = 0;
-
-SEC("fmod_ret/bpf_testmod_test_read")
+SEC("?fmod_ret/bpf_testmod_test_read")
int BPF_PROG(handle_fmod_ret,
struct file *file, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buf, loff_t off, size_t len)
{
- fmod_ret_read_sz = len;
+ sz = len;
return 0; /* don't override the exit code */
}
-SEC("kprobe.multi/bpf_testmod_test_read")
+SEC("?kprobe.multi/bpf_testmod_test_read")
int BPF_PROG(kprobe_multi)
{
return 0;
diff --git a/tools/testing/selftests/bpf/progs/test_probe_user.c b/tools/testing/selftests/bpf/progs/test_probe_user.c
index a8e501af9604..4bc86c7654b1 100644
--- a/tools/testing/selftests/bpf/progs/test_probe_user.c
+++ b/tools/testing/selftests/bpf/progs/test_probe_user.c
@@ -5,13 +5,22 @@
#include <bpf/bpf_core_read.h>
#include "bpf_misc.h"
-static struct sockaddr_in old;
+struct test_pro_bss {
+ struct sockaddr_in old;
+ __u32 test_pid;
+};
+
+struct test_pro_bss bss;
static int handle_sys_connect_common(struct sockaddr_in *uservaddr)
{
struct sockaddr_in new;
+ __u32 cur = bpf_get_current_pid_tgid() >> 32;
+
+ if (bss.test_pid && cur != bss.test_pid)
+ return 0;
- bpf_probe_read_user(&old, sizeof(old), uservaddr);
+ bpf_probe_read_user(&bss.old, sizeof(bss.old), uservaddr);
__builtin_memset(&new, 0xab, sizeof(new));
bpf_probe_write_user(uservaddr, &new, sizeof(new));
diff --git a/tools/testing/selftests/bpf/progs/test_ringbuf_map_key.c b/tools/testing/selftests/bpf/progs/test_ringbuf_map_key.c
index 21bb7da90ea5..0efafa927a3d 100644
--- a/tools/testing/selftests/bpf/progs/test_ringbuf_map_key.c
+++ b/tools/testing/selftests/bpf/progs/test_ringbuf_map_key.c
@@ -35,7 +35,7 @@ SEC("fentry/" SYS_PREFIX "sys_getpgid")
int test_ringbuf_mem_map_key(void *ctx)
{
int cur_pid = bpf_get_current_pid_tgid() >> 32;
- struct sample *sample, sample_copy;
+ struct sample *sample;
int *lookup_val;
if (cur_pid != pid)
@@ -55,16 +55,11 @@ int test_ringbuf_mem_map_key(void *ctx)
lookup_val = (int *)bpf_map_lookup_elem(&hash_map, sample);
__sink(lookup_val);
- /* workaround - memcpy is necessary so that verifier doesn't
- * complain with:
- * verifier internal error: more than one arg with ref_obj_id R3
- * when trying to do bpf_map_update_elem(&hash_map, sample, &sample->seq, BPF_ANY);
- *
+ /*
* Since bpf_map_lookup_elem above uses 'sample' as key, test using
* sample field as value below
*/
- __builtin_memcpy(&sample_copy, sample, sizeof(struct sample));
- bpf_map_update_elem(&hash_map, &sample_copy, &sample->seq, BPF_ANY);
+ bpf_map_update_elem(&hash_map, sample, &sample->seq, BPF_ANY);
bpf_ringbuf_submit(sample, 0);
return 0;
diff --git a/tools/testing/selftests/bpf/progs/test_signed_loader.c b/tools/testing/selftests/bpf/progs/test_signed_loader.c
new file mode 100644
index 000000000000..50451a69b99a
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_signed_loader.c
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+
+/*
+ * Minimal, map-less program. Driven through libbpf's gen_loader (gen_hash)
+ * by prog_tests/signed_loader.c so the generated light-skeleton loader can be
+ * exercised against good and tampered metadata, which the kernel now verifies
+ * at load time via the insns||metadata signature. A socket filter needs no
+ * load-time attach resolution, and having no maps keeps the generated loader's
+ * ctx trivial (0 maps, 1 prog).
+ */
+SEC("socket")
+int probe(void *ctx)
+{
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_signed_loader_data.c b/tools/testing/selftests/bpf/progs/test_signed_loader_data.c
new file mode 100644
index 000000000000..43e2074d0042
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_signed_loader_data.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+
+/*
+ * A single initialized global, so the generated loader has one internal
+ * (.data) map that it seeds with an initial value while loading.
+ * prog_tests/signed_loader.c uses this to check that a signed loader
+ * keeps the attested contents and ignores a ctx-supplied initial_value:
+ * the host cannot re-seed a signed program's maps through the loader ctx.
+ */
+__u64 magic = 0x5eed1234abad1deaULL;
+
+SEC("socket")
+int probe(void *ctx)
+{
+ return (int)magic;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_signed_loader_lsm.c b/tools/testing/selftests/bpf/progs/test_signed_loader_lsm.c
new file mode 100644
index 000000000000..575a9b7910c8
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_signed_loader_lsm.c
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+__u32 monitored_tid;
+
+int sig_keyring_serial;
+int sig_keyring_type;
+int sig_verdict;
+int seen;
+
+SEC("lsm/bpf_prog_load")
+int BPF_PROG(inspect_prog_load, struct bpf_prog *prog, union bpf_attr *attr,
+ struct bpf_token *token, bool kernel)
+{
+ __u32 tid = bpf_get_current_pid_tgid() & 0xffffffff;
+
+ if (!monitored_tid || tid != monitored_tid)
+ return 0;
+
+ seen++;
+ sig_keyring_serial = prog->aux->sig.keyring_serial;
+ sig_keyring_type = prog->aux->sig.keyring_type;
+ sig_verdict = prog->aux->sig.verdict;
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/test_signed_loader_map.c b/tools/testing/selftests/bpf/progs/test_signed_loader_map.c
new file mode 100644
index 000000000000..4478ce6f1fd9
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_signed_loader_map.c
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+
+/*
+ * One explicit array map and no global variables, so the generated loader
+ * has exactly one map to create (no .rodata/.bss). prog_tests/signed_loader.c
+ * uses this to check that a signed loader ignores ctx-supplied max_entries:
+ * the map must keep its attested size (4), not whatever the host puts in
+ * the loader ctx.
+ */
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 4);
+ __type(key, __u32);
+ __type(value, __u64);
+} amap SEC(".maps");
+
+SEC("socket")
+int probe(void *ctx)
+{
+ __u32 key = 0;
+ __u64 *val = bpf_map_lookup_elem(&amap, &key);
+
+ return val ? (int)*val : 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_sleepable_tracepoints.c b/tools/testing/selftests/bpf/progs/test_sleepable_tracepoints.c
new file mode 100644
index 000000000000..254f7fd895d9
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_sleepable_tracepoints.c
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <asm/unistd.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_core_read.h>
+#include <bpf/bpf_helpers.h>
+
+char _license[] SEC("license") = "GPL";
+
+int target_pid;
+int prog_triggered;
+long err;
+char copied_byte;
+
+static int copy_getcwd_arg(char *ubuf)
+{
+ err = bpf_copy_from_user(&copied_byte, sizeof(copied_byte), ubuf);
+ if (err)
+ return err;
+
+ prog_triggered = 1;
+ return 0;
+}
+
+SEC("tp_btf.s/sys_enter")
+int BPF_PROG(handle_sys_enter_tp_btf, struct pt_regs *regs, long id)
+{
+ if ((bpf_get_current_pid_tgid() >> 32) != target_pid ||
+ id != __NR_getcwd)
+ return 0;
+
+ return copy_getcwd_arg((void *)PT_REGS_PARM1_SYSCALL(regs));
+}
+
+SEC("raw_tp.s/sys_enter")
+int BPF_PROG(handle_sys_enter_raw_tp, struct pt_regs *regs, long id)
+{
+ if ((bpf_get_current_pid_tgid() >> 32) != target_pid ||
+ id != __NR_getcwd)
+ return 0;
+
+ return copy_getcwd_arg((void *)PT_REGS_PARM1_CORE_SYSCALL(regs));
+}
+
+SEC("tp.s/syscalls/sys_enter_getcwd")
+int handle_sys_enter_tp(struct syscall_trace_enter *args)
+{
+ if ((bpf_get_current_pid_tgid() >> 32) != target_pid)
+ return 0;
+
+ return copy_getcwd_arg((void *)args->args[0]);
+}
+
+SEC("tp.s/syscalls/sys_exit_getcwd")
+int handle_sys_exit_tp(struct syscall_trace_exit *args)
+{
+ struct pt_regs *regs;
+
+ if ((bpf_get_current_pid_tgid() >> 32) != target_pid)
+ return 0;
+
+ regs = (struct pt_regs *)bpf_task_pt_regs(bpf_get_current_task_btf());
+ return copy_getcwd_arg((void *)PT_REGS_PARM1_CORE_SYSCALL(regs));
+}
+
+SEC("raw_tp.s")
+int BPF_PROG(handle_raw_tp_bare, struct pt_regs *regs, long id)
+{
+ return 0;
+}
+
+SEC("tp.s")
+int handle_tp_bare(void *ctx)
+{
+ return 0;
+}
+
+SEC("tracepoint.s/syscalls/sys_enter_getcwd")
+int handle_sys_enter_tp_alias(struct syscall_trace_enter *args)
+{
+ return 0;
+}
+
+SEC("raw_tracepoint.s/sys_enter")
+int BPF_PROG(handle_sys_enter_raw_tp_alias, struct pt_regs *regs, long id)
+{
+ return 0;
+}
+
+SEC("raw_tp.s/sys_enter")
+int BPF_PROG(handle_test_run, struct pt_regs *regs, long id)
+{
+ if ((__u64)regs == 0x1234ULL && (__u64)id == 0x5678ULL)
+ return (__u64)regs + (__u64)id;
+
+ return 0;
+}
+
+SEC("raw_tp.s/sched_switch")
+int BPF_PROG(handle_raw_tp_non_faultable, bool preempt,
+ struct task_struct *prev, struct task_struct *next)
+{
+ return 0;
+}
+
+SEC("tp.s/sched/sched_switch")
+int handle_tp_non_syscall(void *ctx)
+{
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/test_sleepable_tracepoints_fail.c b/tools/testing/selftests/bpf/progs/test_sleepable_tracepoints_fail.c
new file mode 100644
index 000000000000..1a0748a9520b
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_sleepable_tracepoints_fail.c
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+/* Sleepable program on a non-faultable tracepoint should fail to load */
+SEC("tp_btf.s/sched_switch")
+__failure __msg("Sleepable program cannot attach to non-faultable tracepoint")
+int BPF_PROG(handle_sched_switch, bool preempt,
+ struct task_struct *prev, struct task_struct *next)
+{
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/test_sockmap_kern.h b/tools/testing/selftests/bpf/progs/test_sockmap_kern.h
index f48f85f1bd70..284a2f2e50cf 100644
--- a/tools/testing/selftests/bpf/progs/test_sockmap_kern.h
+++ b/tools/testing/selftests/bpf/progs/test_sockmap_kern.h
@@ -85,13 +85,6 @@ struct {
__type(value, int);
} sock_skb_opts SEC(".maps");
-struct {
- __uint(type, TEST_MAP_TYPE);
- __uint(max_entries, 20);
- __uint(key_size, sizeof(int));
- __uint(value_size, sizeof(int));
-} tls_sock_map SEC(".maps");
-
SEC("sk_skb/stream_parser")
int bpf_prog1(struct __sk_buff *skb)
{
@@ -135,55 +128,6 @@ int bpf_prog2(struct __sk_buff *skb)
}
-static inline void bpf_write_pass(struct __sk_buff *skb, int offset)
-{
- int err = bpf_skb_pull_data(skb, 6 + offset);
- void *data_end;
- char *c;
-
- if (err)
- return;
-
- c = (char *)(long)skb->data;
- data_end = (void *)(long)skb->data_end;
-
- if (c + 5 + offset < data_end)
- memcpy(c + offset, "PASS", 4);
-}
-
-SEC("sk_skb/stream_verdict")
-int bpf_prog3(struct __sk_buff *skb)
-{
- int err, *f, ret = SK_PASS;
- const int one = 1;
-
- f = bpf_map_lookup_elem(&sock_skb_opts, &one);
- if (f && *f) {
- __u64 flags = 0;
-
- ret = 0;
- flags = *f;
-
- err = bpf_skb_adjust_room(skb, -13, 0, 0);
- if (err)
- return SK_DROP;
- err = bpf_skb_adjust_room(skb, 4, 0, 0);
- if (err)
- return SK_DROP;
- bpf_write_pass(skb, 0);
-#ifdef SOCKMAP
- return bpf_sk_redirect_map(skb, &tls_sock_map, ret, flags);
-#else
- return bpf_sk_redirect_hash(skb, &tls_sock_map, &ret, flags);
-#endif
- }
- err = bpf_skb_adjust_room(skb, 4, 0, 0);
- if (err)
- return SK_DROP;
- bpf_write_pass(skb, 13);
- return ret;
-}
-
SEC("sockops")
int bpf_sockmap(struct bpf_sock_ops *skops)
{
diff --git a/tools/testing/selftests/bpf/progs/test_sockmap_ktls.c b/tools/testing/selftests/bpf/progs/test_sockmap_ktls.c
deleted file mode 100644
index 83df4919c224..000000000000
--- a/tools/testing/selftests/bpf/progs/test_sockmap_ktls.c
+++ /dev/null
@@ -1,40 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-#include <linux/bpf.h>
-#include <bpf/bpf_helpers.h>
-#include <bpf/bpf_endian.h>
-
-int cork_byte;
-int push_start;
-int push_end;
-int apply_bytes;
-int pop_start;
-int pop_end;
-
-struct {
- __uint(type, BPF_MAP_TYPE_SOCKMAP);
- __uint(max_entries, 20);
- __type(key, int);
- __type(value, int);
-} sock_map SEC(".maps");
-
-SEC("sk_msg")
-int prog_sk_policy(struct sk_msg_md *msg)
-{
- if (cork_byte > 0)
- bpf_msg_cork_bytes(msg, cork_byte);
- if (push_start > 0 && push_end > 0)
- bpf_msg_push_data(msg, push_start, push_end, 0);
- if (pop_start >= 0 && pop_end > 0)
- bpf_msg_pop_data(msg, pop_start, pop_end, 0);
-
- return SK_PASS;
-}
-
-SEC("sk_msg")
-int prog_sk_policy_redir(struct sk_msg_md *msg)
-{
- int two = 2;
-
- bpf_msg_apply_bytes(msg, apply_bytes);
- return bpf_msg_redirect_map(msg, &sock_map, two, 0);
-}
diff --git a/tools/testing/selftests/bpf/progs/test_sockmap_msg_pop_data.c b/tools/testing/selftests/bpf/progs/test_sockmap_msg_pop_data.c
new file mode 100644
index 000000000000..301e65b95256
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_sockmap_msg_pop_data.c
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+
+struct {
+ __uint(type, BPF_MAP_TYPE_SOCKMAP);
+ __uint(max_entries, 1);
+ __type(key, int);
+ __type(value, int);
+} sock_map SEC(".maps");
+
+#define POP_START 0x48a3
+#define POP_LEN 0xfffffffd
+
+long pop_data_ret = 1;
+
+SEC("sk_msg")
+int prog_msg_pop_data(struct sk_msg_md *msg)
+{
+ if (msg->size <= POP_START)
+ return SK_PASS;
+
+ pop_data_ret = bpf_msg_pop_data(msg, POP_START, POP_LEN, 0);
+ return SK_PASS;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_sockmap_pass_prog.c b/tools/testing/selftests/bpf/progs/test_sockmap_pass_prog.c
index 69aacc96db36..ef9edca184ea 100644
--- a/tools/testing/selftests/bpf/progs/test_sockmap_pass_prog.c
+++ b/tools/testing/selftests/bpf/progs/test_sockmap_pass_prog.c
@@ -44,4 +44,18 @@ int prog_skb_parser(struct __sk_buff *skb)
return SK_PASS;
}
+SEC("sk_skb/stream_verdict")
+int prog_skb_verdict_ingress(struct __sk_buff *skb)
+{
+ int one = 1;
+
+ return bpf_sk_redirect_map(skb, &sock_map_rx, one, BPF_F_INGRESS);
+}
+
+SEC("sk_skb/stream_parser")
+int prog_skb_verdict_ingress_strp(struct __sk_buff *skb)
+{
+ return skb->len;
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_sockmap_strp.c b/tools/testing/selftests/bpf/progs/test_sockmap_strp.c
index dde3d5bec515..fe88fa6d40bc 100644
--- a/tools/testing/selftests/bpf/progs/test_sockmap_strp.c
+++ b/tools/testing/selftests/bpf/progs/test_sockmap_strp.c
@@ -50,4 +50,11 @@ int prog_skb_parser_partial(struct __sk_buff *skb)
return 10;
}
+SEC("sk_skb/stream_parser")
+int prog_skb_parser_resize(struct __sk_buff *skb)
+{
+ bpf_skb_change_tail(skb, skb->len, 0);
+ return skb->len;
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_sockmap_update.c b/tools/testing/selftests/bpf/progs/test_sockmap_update.c
deleted file mode 100644
index 6d64ea536e3d..000000000000
--- a/tools/testing/selftests/bpf/progs/test_sockmap_update.c
+++ /dev/null
@@ -1,48 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-// Copyright (c) 2020 Cloudflare
-#include "vmlinux.h"
-#include <bpf/bpf_helpers.h>
-
-struct {
- __uint(type, BPF_MAP_TYPE_SOCKMAP);
- __uint(max_entries, 1);
- __type(key, __u32);
- __type(value, __u64);
-} src SEC(".maps");
-
-struct {
- __uint(type, BPF_MAP_TYPE_SOCKMAP);
- __uint(max_entries, 1);
- __type(key, __u32);
- __type(value, __u64);
-} dst_sock_map SEC(".maps");
-
-struct {
- __uint(type, BPF_MAP_TYPE_SOCKHASH);
- __uint(max_entries, 1);
- __type(key, __u32);
- __type(value, __u64);
-} dst_sock_hash SEC(".maps");
-
-SEC("tc")
-int copy_sock_map(void *ctx)
-{
- struct bpf_sock *sk;
- bool failed = false;
- __u32 key = 0;
-
- sk = bpf_map_lookup_elem(&src, &key);
- if (!sk)
- return SK_DROP;
-
- if (bpf_map_update_elem(&dst_sock_map, &key, sk, 0))
- failed = true;
-
- if (bpf_map_update_elem(&dst_sock_hash, &key, sk, 0))
- failed = true;
-
- bpf_sk_release(sk);
- return failed ? SK_DROP : SK_PASS;
-}
-
-char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c b/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c
index f678ee6bd7ea..55282f20fa32 100644
--- a/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c
+++ b/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c
@@ -14,17 +14,18 @@ struct array_map {
__type(key, int);
__type(value, struct foo);
__uint(max_entries, 1);
-} array_map SEC(".maps");
+} array_map SEC(".maps"), array_map_b SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
- __uint(max_entries, 1);
+ __uint(max_entries, 2);
__type(key, int);
__type(value, int);
__array(values, struct array_map);
} map_of_maps SEC(".maps") = {
.values = {
[0] = &array_map,
+ [1] = &array_map_b,
},
};
@@ -314,4 +315,66 @@ int lock_global_sleepable_subprog_indirect(struct __sk_buff *ctx)
return ret;
}
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 2);
+ __type(key, int);
+ __type(value, struct foo);
+} callback_array_map SEC(".maps");
+
+struct callback_ctx {
+ struct foo *value;
+};
+
+static long lock_different_value(struct bpf_map *map, int *key,
+ struct foo *value, struct callback_ctx *ctx)
+{
+ bpf_spin_lock(&value->lock);
+ bpf_spin_unlock(&ctx->value->lock);
+ return 0;
+}
+
+static long nest_lock_different_value(struct bpf_map *map, int *key,
+ struct foo *value, void *data)
+{
+ struct callback_ctx ctx = { .value = value };
+
+ bpf_for_each_map_elem(&callback_array_map, lock_different_value, &ctx, 0);
+ return 0;
+}
+
+SEC("?tc")
+int callback_value_lock_identity(void *ctx)
+{
+ bpf_for_each_map_elem(&callback_array_map, nest_lock_different_value, NULL, 0);
+ return 0;
+}
+
+static long nest_lock_different_inner_value(struct bpf_map *map, int *key,
+ struct foo *value, void *data)
+{
+ struct callback_ctx ctx = { .value = value };
+ int inner_key = 1;
+ void *inner_map;
+
+ inner_map = bpf_map_lookup_elem(&map_of_maps, &inner_key);
+ if (!inner_map)
+ return 0;
+ bpf_for_each_map_elem(inner_map, lock_different_value, &ctx, 0);
+ return 0;
+}
+
+SEC("?tc")
+int callback_inner_map_value_lock_identity(void *ctx)
+{
+ int inner_key = 0;
+ void *inner_map;
+
+ inner_map = bpf_map_lookup_elem(&map_of_maps, &inner_key);
+ if (!inner_map)
+ return 0;
+ bpf_for_each_map_elem(inner_map, nest_lock_different_inner_value, NULL, 0);
+ return 0;
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_tc_change_tail_pmtu.c b/tools/testing/selftests/bpf/progs/test_tc_change_tail_pmtu.c
new file mode 100644
index 000000000000..5c4c07545bc9
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_tc_change_tail_pmtu.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include <linux/bpf.h>
+#include <linux/icmp.h>
+#include <linux/if_ether.h>
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+#define ICMP_SAMPLE_LEN (sizeof(struct iphdr) + 8)
+#define ICMP_HDRS_LEN (sizeof(struct iphdr) + sizeof(struct icmphdr))
+
+__be16 server_port = 0;
+__u16 pmtu = 0;
+
+long change_tail_ret = 1;
+long adjust_room_ret = 0;
+bool icmp_sent = false;
+bool icmp_err = false;
+
+static __always_inline __sum16 csum_fold(__wsum csum)
+{
+ csum = (csum & 0xffff) + (csum >> 16);
+ csum = (csum & 0xffff) + (csum >> 16);
+
+ return (__sum16)~csum;
+}
+
+SEC("tc/egress")
+int change_tail_icmp(struct __sk_buff *skb)
+{
+ __u8 smac[ETH_ALEN], dmac[ETH_ALEN];
+ void *data, *data_end;
+ struct icmphdr *icmp;
+ struct ethhdr *eth;
+ struct tcphdr *tcp;
+ __be32 saddr, daddr;
+ struct iphdr *ip;
+ __wsum csum;
+
+ if (icmp_sent || icmp_err)
+ return TCX_PASS;
+
+ data = (void *)(long)skb->data;
+ data_end = (void *)(long)skb->data_end;
+
+ eth = data;
+ if ((void *)(eth + 1) > data_end)
+ return TCX_PASS;
+ if (eth->h_proto != bpf_htons(ETH_P_IP))
+ return TCX_PASS;
+
+ ip = (void *)(eth + 1);
+ if ((void *)(ip + 1) > data_end)
+ return TCX_PASS;
+ if (ip->ihl != 5 || ip->protocol != IPPROTO_TCP)
+ return TCX_PASS;
+
+ tcp = (void *)(ip + 1);
+ if ((void *)(tcp + 1) > data_end)
+ return TCX_PASS;
+ if (tcp->dest != server_port)
+ return TCX_PASS;
+ if (bpf_ntohs(ip->tot_len) <= sizeof(*ip) + tcp->doff * 4)
+ return TCX_PASS;
+
+ __builtin_memcpy(smac, eth->h_source, ETH_ALEN);
+ __builtin_memcpy(dmac, eth->h_dest, ETH_ALEN);
+ saddr = ip->saddr;
+ daddr = ip->daddr;
+
+ change_tail_ret = bpf_skb_change_tail(skb, ETH_HLEN + ICMP_SAMPLE_LEN, 0);
+ if (change_tail_ret) {
+ icmp_err = true;
+ return TCX_PASS;
+ }
+
+ adjust_room_ret = bpf_skb_adjust_room(skb, ICMP_HDRS_LEN,
+ BPF_ADJ_ROOM_MAC,
+ BPF_F_ADJ_ROOM_NO_CSUM_RESET);
+ if (adjust_room_ret) {
+ icmp_err = true;
+ return TCX_DROP;
+ }
+
+ data = (void *)(long)skb->data;
+ data_end = (void *)(long)skb->data_end;
+
+ eth = data;
+ ip = (void *)(eth + 1);
+ icmp = (void *)(ip + 1);
+ if ((void *)icmp + sizeof(*icmp) + ICMP_SAMPLE_LEN > data_end) {
+ icmp_err = true;
+ return TCX_DROP;
+ }
+
+ __builtin_memcpy(eth->h_dest, smac, ETH_ALEN);
+ __builtin_memcpy(eth->h_source, dmac, ETH_ALEN);
+
+ __builtin_memset(icmp, 0, sizeof(*icmp));
+ icmp->type = ICMP_DEST_UNREACH;
+ icmp->code = ICMP_FRAG_NEEDED;
+ icmp->un.frag.mtu = bpf_htons(pmtu);
+
+ __builtin_memset(ip, 0, sizeof(*ip));
+ ip->version = 4;
+ ip->ihl = 5;
+ ip->ttl = 64;
+ ip->protocol = IPPROTO_ICMP;
+ ip->tot_len = bpf_htons(ICMP_HDRS_LEN + ICMP_SAMPLE_LEN);
+ ip->saddr = daddr;
+ ip->daddr = saddr;
+
+ csum = bpf_csum_diff(NULL, 0, (__be32 *)icmp,
+ sizeof(*icmp) + ICMP_SAMPLE_LEN, 0);
+ icmp->checksum = csum_fold(csum);
+ csum = bpf_csum_diff(NULL, 0, (__be32 *)ip, sizeof(*ip), 0);
+ ip->check = csum_fold(csum);
+ icmp_sent = true;
+ return bpf_redirect(skb->ifindex, BPF_F_INGRESS);
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_tc_peer.c b/tools/testing/selftests/bpf/progs/test_tc_peer.c
index 365eacb5dc34..cfb9ef7f467c 100644
--- a/tools/testing/selftests/bpf/progs/test_tc_peer.c
+++ b/tools/testing/selftests/bpf/progs/test_tc_peer.c
@@ -35,6 +35,28 @@ int tc_src(struct __sk_buff *skb)
}
SEC("tc")
+int tc_dst_ing(struct __sk_buff *skb)
+{
+ if (!skb->mark) {
+ skb->mark = 0x1;
+ return bpf_redirect_peer(IFINDEX_SRC, BPF_F_EGRESS);
+ }
+
+ return bpf_redirect(IFINDEX_DST, 0);
+}
+
+SEC("tc")
+int tc_src_ing(struct __sk_buff *skb)
+{
+ if (!skb->mark) {
+ skb->mark = 0x1;
+ return bpf_redirect_peer(IFINDEX_DST, BPF_F_EGRESS);
+ }
+
+ return bpf_redirect(IFINDEX_SRC, 0);
+}
+
+SEC("tc")
int tc_dst_l3(struct __sk_buff *skb)
{
return bpf_redirect(IFINDEX_SRC, 0);
diff --git a/tools/testing/selftests/bpf/progs/test_tc_qevent.c b/tools/testing/selftests/bpf/progs/test_tc_qevent.c
new file mode 100644
index 000000000000..1529c111f4aa
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_tc_qevent.c
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+
+int redirect_ifindex = 1;
+__u64 verdict_calls = 0;
+__u64 helper_calls = 0;
+
+SEC("tc")
+int qevent_redirect_verdict(struct __sk_buff *skb)
+{
+ __sync_fetch_and_add(&verdict_calls, 1);
+ return TCX_REDIRECT;
+}
+
+SEC("tc")
+int qevent_redirect_helper(struct __sk_buff *skb)
+{
+ __sync_fetch_and_add(&helper_calls, 1);
+ return bpf_redirect(redirect_ifindex, 0);
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_tc_tunnel.c b/tools/testing/selftests/bpf/progs/test_tc_tunnel.c
index 7330c61b5730..853bca962910 100644
--- a/tools/testing/selftests/bpf/progs/test_tc_tunnel.c
+++ b/tools/testing/selftests/bpf/progs/test_tc_tunnel.c
@@ -6,6 +6,7 @@
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
+#include <bpf/bpf_core_read.h>
#include "bpf_tracing_net.h"
#include "bpf_compiler.h"
@@ -23,7 +24,12 @@ static const int cfg_udp_src = 20000;
(((__u64)len & BPF_ADJ_ROOM_ENCAP_L2_MASK) \
<< BPF_ADJ_ROOM_ENCAP_L2_SHIFT)
-#define L2_PAD_SZ (sizeof(struct vxlanhdr) + ETH_HLEN)
+struct vxlanhdr___local {
+ __be32 vx_flags;
+ __be32 vx_vni;
+};
+
+#define L2_PAD_SZ (sizeof(struct vxlanhdr___local) + ETH_HLEN)
#define UDP_PORT 5555
#define MPLS_OVER_UDP_PORT 6635
@@ -32,6 +38,22 @@ static const int cfg_udp_src = 20000;
#define EXTPROTO_VXLAN 0x1
+#define SKB_GSO_UDP_TUNNEL_MASK (SKB_GSO_UDP_TUNNEL | \
+ SKB_GSO_UDP_TUNNEL_CSUM)
+
+#define SKB_GSO_TUNNEL_MASK (SKB_GSO_UDP_TUNNEL_MASK | \
+ SKB_GSO_GRE | \
+ SKB_GSO_GRE_CSUM | \
+ SKB_GSO_IPXIP4 | \
+ SKB_GSO_IPXIP6 | \
+ SKB_GSO_ESP)
+
+#define BPF_F_ADJ_ROOM_DECAP_L4_MASK (BPF_F_ADJ_ROOM_DECAP_L4_UDP | \
+ BPF_F_ADJ_ROOM_DECAP_L4_GRE)
+
+#define BPF_F_ADJ_ROOM_DECAP_IPXIP_MASK (BPF_F_ADJ_ROOM_DECAP_IPXIP4 | \
+ BPF_F_ADJ_ROOM_DECAP_IPXIP6)
+
#define VXLAN_FLAGS bpf_htonl(1<<27)
#define VNI_ID 1
#define VXLAN_VNI bpf_htonl(VNI_ID << 8)
@@ -154,7 +176,7 @@ static __always_inline int __encap_ipv4(struct __sk_buff *skb, __u8 encap_proto,
l2_len = ETH_HLEN;
if (ext_proto & EXTPROTO_VXLAN) {
udp_dst = VXLAN_UDP_PORT;
- l2_len += sizeof(struct vxlanhdr);
+ l2_len += sizeof(struct vxlanhdr___local);
} else
udp_dst = ETH_OVER_UDP_PORT;
break;
@@ -195,12 +217,12 @@ static __always_inline int __encap_ipv4(struct __sk_buff *skb, __u8 encap_proto,
flags |= BPF_F_ADJ_ROOM_ENCAP_L2_ETH;
if (ext_proto & EXTPROTO_VXLAN) {
- struct vxlanhdr *vxlan_hdr = (struct vxlanhdr *)l2_hdr;
+ struct vxlanhdr___local *vxlan_hdr = (struct vxlanhdr___local *)l2_hdr;
vxlan_hdr->vx_flags = VXLAN_FLAGS;
vxlan_hdr->vx_vni = VXLAN_VNI;
- l2_hdr += sizeof(struct vxlanhdr);
+ l2_hdr += sizeof(struct vxlanhdr___local);
}
if (bpf_skb_load_bytes(skb, 0, l2_hdr, ETH_HLEN))
@@ -285,7 +307,7 @@ static __always_inline int __encap_ipv6(struct __sk_buff *skb, __u8 encap_proto,
l2_len = ETH_HLEN;
if (ext_proto & EXTPROTO_VXLAN) {
udp_dst = VXLAN_UDP_PORT;
- l2_len += sizeof(struct vxlanhdr);
+ l2_len += sizeof(struct vxlanhdr___local);
} else
udp_dst = ETH_OVER_UDP_PORT;
break;
@@ -325,12 +347,12 @@ static __always_inline int __encap_ipv6(struct __sk_buff *skb, __u8 encap_proto,
flags |= BPF_F_ADJ_ROOM_ENCAP_L2_ETH;
if (ext_proto & EXTPROTO_VXLAN) {
- struct vxlanhdr *vxlan_hdr = (struct vxlanhdr *)l2_hdr;
+ struct vxlanhdr___local *vxlan_hdr = (struct vxlanhdr___local *)l2_hdr;
vxlan_hdr->vx_flags = VXLAN_FLAGS;
vxlan_hdr->vx_vni = VXLAN_VNI;
- l2_hdr += sizeof(struct vxlanhdr);
+ l2_hdr += sizeof(struct vxlanhdr___local);
}
if (bpf_skb_load_bytes(skb, 0, l2_hdr, ETH_HLEN))
@@ -584,9 +606,12 @@ int __encap_ip6vxlan_eth(struct __sk_buff *skb)
return TC_ACT_OK;
}
-static int decap_internal(struct __sk_buff *skb, int off, int len, char proto)
+static int decap_internal(struct __sk_buff *skb, int off, int len, char proto,
+ __u64 ipxip_flag)
{
__u64 flags = BPF_F_ADJ_ROOM_FIXED_GSO;
+ struct sk_buff *kskb;
+ struct skb_shared_info *shinfo;
struct ipv6_opt_hdr ip6_opt_hdr;
struct gre_hdr greh;
struct udphdr udph;
@@ -594,10 +619,12 @@ static int decap_internal(struct __sk_buff *skb, int off, int len, char proto)
switch (proto) {
case IPPROTO_IPIP:
- flags |= BPF_F_ADJ_ROOM_DECAP_L3_IPV4;
+ flags |= BPF_F_ADJ_ROOM_DECAP_L3_IPV4 |
+ ipxip_flag;
break;
case IPPROTO_IPV6:
- flags |= BPF_F_ADJ_ROOM_DECAP_L3_IPV6;
+ flags |= BPF_F_ADJ_ROOM_DECAP_L3_IPV6 |
+ ipxip_flag;
break;
case NEXTHDR_DEST:
if (bpf_skb_load_bytes(skb, off + len, &ip6_opt_hdr,
@@ -605,10 +632,12 @@ static int decap_internal(struct __sk_buff *skb, int off, int len, char proto)
return TC_ACT_OK;
switch (ip6_opt_hdr.nexthdr) {
case IPPROTO_IPIP:
- flags |= BPF_F_ADJ_ROOM_DECAP_L3_IPV4;
+ flags |= BPF_F_ADJ_ROOM_DECAP_L3_IPV4 |
+ ipxip_flag;
break;
case IPPROTO_IPV6:
- flags |= BPF_F_ADJ_ROOM_DECAP_L3_IPV6;
+ flags |= BPF_F_ADJ_ROOM_DECAP_L3_IPV6 |
+ ipxip_flag;
break;
default:
return TC_ACT_OK;
@@ -616,6 +645,11 @@ static int decap_internal(struct __sk_buff *skb, int off, int len, char proto)
break;
case IPPROTO_GRE:
olen += sizeof(struct gre_hdr);
+ if (!bpf_core_enum_value_exists(enum bpf_adj_room_flags,
+ BPF_F_ADJ_ROOM_DECAP_L4_GRE))
+ return TC_ACT_SHOT;
+ flags |= BPF_F_ADJ_ROOM_DECAP_L4_GRE;
+
if (bpf_skb_load_bytes(skb, off + len, &greh, sizeof(greh)) < 0)
return TC_ACT_OK;
switch (bpf_ntohs(greh.protocol)) {
@@ -629,6 +663,10 @@ static int decap_internal(struct __sk_buff *skb, int off, int len, char proto)
break;
case IPPROTO_UDP:
olen += sizeof(struct udphdr);
+ if (!bpf_core_enum_value_exists(enum bpf_adj_room_flags,
+ BPF_F_ADJ_ROOM_DECAP_L4_UDP))
+ return TC_ACT_SHOT;
+ flags |= BPF_F_ADJ_ROOM_DECAP_L4_UDP;
if (bpf_skb_load_bytes(skb, off + len, &udph, sizeof(udph)) < 0)
return TC_ACT_OK;
switch (bpf_ntohs(udph.dest)) {
@@ -639,7 +677,7 @@ static int decap_internal(struct __sk_buff *skb, int off, int len, char proto)
olen += ETH_HLEN;
break;
case VXLAN_UDP_PORT:
- olen += ETH_HLEN + sizeof(struct vxlanhdr);
+ olen += ETH_HLEN + sizeof(struct vxlanhdr___local);
break;
}
break;
@@ -650,6 +688,40 @@ static int decap_internal(struct __sk_buff *skb, int off, int len, char proto)
if (bpf_skb_adjust_room(skb, -olen, BPF_ADJ_ROOM_MAC, flags))
return TC_ACT_SHOT;
+ kskb = bpf_cast_to_kern_ctx(skb);
+ shinfo = bpf_core_cast(kskb->head + kskb->end, struct skb_shared_info);
+ if (shinfo->gso_size) {
+ if ((flags & BPF_F_ADJ_ROOM_DECAP_L4_UDP) &&
+ (shinfo->gso_type & SKB_GSO_UDP_TUNNEL_MASK))
+ return TC_ACT_SHOT;
+
+ if ((flags & BPF_F_ADJ_ROOM_DECAP_L4_GRE) &&
+ (shinfo->gso_type & (SKB_GSO_GRE | SKB_GSO_GRE_CSUM)))
+ return TC_ACT_SHOT;
+
+ if ((flags & BPF_F_ADJ_ROOM_DECAP_IPXIP4) &&
+ (shinfo->gso_type & SKB_GSO_IPXIP4))
+ return TC_ACT_SHOT;
+
+ if ((flags & BPF_F_ADJ_ROOM_DECAP_IPXIP6) &&
+ (shinfo->gso_type & SKB_GSO_IPXIP6))
+ return TC_ACT_SHOT;
+
+ if (flags & (BPF_F_ADJ_ROOM_DECAP_L4_MASK |
+ BPF_F_ADJ_ROOM_DECAP_IPXIP_MASK)) {
+ if ((shinfo->gso_type & SKB_GSO_TUNNEL_MASK) &&
+ !kskb->encapsulation)
+ return TC_ACT_SHOT;
+ if (!(shinfo->gso_type & SKB_GSO_TUNNEL_MASK) &&
+ kskb->encapsulation)
+ return TC_ACT_SHOT;
+ }
+ } else if ((flags & (BPF_F_ADJ_ROOM_DECAP_L4_MASK |
+ BPF_F_ADJ_ROOM_DECAP_IPXIP_MASK)) &&
+ kskb->encapsulation) {
+ return TC_ACT_SHOT;
+ }
+
return TC_ACT_OK;
}
@@ -657,6 +729,10 @@ static int decap_ipv4(struct __sk_buff *skb)
{
struct iphdr iph_outer;
+ if (!bpf_core_enum_value_exists(enum bpf_adj_room_flags,
+ BPF_F_ADJ_ROOM_DECAP_IPXIP4))
+ return TC_ACT_SHOT;
+
if (bpf_skb_load_bytes(skb, ETH_HLEN, &iph_outer,
sizeof(iph_outer)) < 0)
return TC_ACT_OK;
@@ -665,19 +741,25 @@ static int decap_ipv4(struct __sk_buff *skb)
return TC_ACT_OK;
return decap_internal(skb, ETH_HLEN, sizeof(iph_outer),
- iph_outer.protocol);
+ iph_outer.protocol,
+ BPF_F_ADJ_ROOM_DECAP_IPXIP4);
}
static int decap_ipv6(struct __sk_buff *skb)
{
struct ipv6hdr iph_outer;
+ if (!bpf_core_enum_value_exists(enum bpf_adj_room_flags,
+ BPF_F_ADJ_ROOM_DECAP_IPXIP6))
+ return TC_ACT_SHOT;
+
if (bpf_skb_load_bytes(skb, ETH_HLEN, &iph_outer,
sizeof(iph_outer)) < 0)
return TC_ACT_OK;
return decap_internal(skb, ETH_HLEN, sizeof(iph_outer),
- iph_outer.nexthdr);
+ iph_outer.nexthdr,
+ BPF_F_ADJ_ROOM_DECAP_IPXIP6);
}
SEC("tc")
diff --git a/tools/testing/selftests/bpf/progs/test_trampoline_count.c b/tools/testing/selftests/bpf/progs/test_trampoline_count.c
index 7765720da7d5..02f52806b1b2 100644
--- a/tools/testing/selftests/bpf/progs/test_trampoline_count.c
+++ b/tools/testing/selftests/bpf/progs/test_trampoline_count.c
@@ -3,20 +3,20 @@
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
-SEC("fentry/bpf_modify_return_test")
-int BPF_PROG(fentry_test, int a, int *b)
+SEC("fentry/bpf_testmod_trampoline_count_test")
+int BPF_PROG(fentry_test)
{
return 0;
}
-SEC("fmod_ret/bpf_modify_return_test")
-int BPF_PROG(fmod_ret_test, int a, int *b, int ret)
+SEC("fmod_ret/bpf_testmod_trampoline_count_test")
+int BPF_PROG(fmod_ret_test, int ret)
{
return 0;
}
-SEC("fexit/bpf_modify_return_test")
-int BPF_PROG(fexit_test, int a, int *b, int ret)
+SEC("fexit/bpf_testmod_trampoline_count_test")
+int BPF_PROG(fexit_test, int ret)
{
return 0;
}
diff --git a/tools/testing/selftests/bpf/progs/test_tunnel_kern.c b/tools/testing/selftests/bpf/progs/test_tunnel_kern.c
index 32127f1cd687..30f1de458669 100644
--- a/tools/testing/selftests/bpf/progs/test_tunnel_kern.c
+++ b/tools/testing/selftests/bpf/progs/test_tunnel_kern.c
@@ -6,6 +6,7 @@
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*/
+#define BPF_NO_KFUNC_PROTOTYPES
#include "vmlinux.h"
#include <bpf/bpf_core_read.h>
#include <bpf/bpf_helpers.h>
@@ -36,12 +37,10 @@ enum bpf_fou_encap_type___local {
FOU_BPF_ENCAP_GUE___local,
};
-struct bpf_fou_encap;
-
int bpf_skb_set_fou_encap(struct __sk_buff *skb_ctx,
- struct bpf_fou_encap *encap, int type) __ksym;
+ struct bpf_fou_encap___local *encap, int type) __ksym;
int bpf_skb_get_fou_encap(struct __sk_buff *skb_ctx,
- struct bpf_fou_encap *encap) __ksym;
+ struct bpf_fou_encap___local *encap) __ksym;
struct xfrm_state *
bpf_xdp_get_xfrm_state(struct xdp_md *ctx, struct bpf_xfrm_state_opts *opts,
u32 opts__sz) __ksym;
@@ -781,7 +780,7 @@ int ipip_gue_set_tunnel(struct __sk_buff *skb)
encap.sport = 0;
encap.dport = bpf_htons(5555);
- ret = bpf_skb_set_fou_encap(skb, (struct bpf_fou_encap *)&encap,
+ ret = bpf_skb_set_fou_encap(skb, &encap,
bpf_core_enum_value(enum bpf_fou_encap_type___local,
FOU_BPF_ENCAP_GUE___local));
if (ret < 0) {
@@ -820,7 +819,7 @@ int ipip_fou_set_tunnel(struct __sk_buff *skb)
encap.sport = 0;
encap.dport = bpf_htons(5555);
- ret = bpf_skb_set_fou_encap(skb, (struct bpf_fou_encap *)&encap,
+ ret = bpf_skb_set_fou_encap(skb, &encap,
FOU_BPF_ENCAP_FOU___local);
if (ret < 0) {
log_err(ret);
@@ -843,7 +842,7 @@ int ipip_encap_get_tunnel(struct __sk_buff *skb)
return TC_ACT_SHOT;
}
- ret = bpf_skb_get_fou_encap(skb, (struct bpf_fou_encap *)&encap);
+ ret = bpf_skb_get_fou_encap(skb, &encap);
if (ret < 0) {
log_err(ret);
return TC_ACT_SHOT;
diff --git a/tools/testing/selftests/bpf/progs/test_usdt.c b/tools/testing/selftests/bpf/progs/test_usdt.c
index a78c87537b07..0ee78fb050a1 100644
--- a/tools/testing/selftests/bpf/progs/test_usdt.c
+++ b/tools/testing/selftests/bpf/progs/test_usdt.c
@@ -138,4 +138,41 @@ int usdt_sib(struct pt_regs *ctx)
return 0;
}
+#ifdef __TARGET_ARCH_x86
+int executed;
+unsigned long expected_ip;
+
+SEC("usdt")
+int usdt_executed(struct pt_regs *ctx)
+{
+ if (expected_ip == ctx->ip)
+ executed++;
+ return 0;
+}
+
+int arg_total;
+int arg_bad;
+long arg_last[3];
+long expected_arg[3];
+int expected_pid;
+
+SEC("usdt")
+int BPF_USDT(usdt_check_arg, long arg1, long arg2, long arg3)
+{
+ if (expected_pid != (bpf_get_current_pid_tgid() >> 32))
+ return 0;
+
+ __sync_fetch_and_add(&arg_total, 1);
+ arg_last[0] = arg1;
+ arg_last[1] = arg2;
+ arg_last[2] = arg3;
+
+ if (arg1 != expected_arg[0] ||
+ arg2 != expected_arg[1] ||
+ arg3 != expected_arg[2])
+ __sync_fetch_and_add(&arg_bad, 1);
+
+ return 0;
+}
+#endif
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_vmlinux.c b/tools/testing/selftests/bpf/progs/test_vmlinux.c
index 78b23934d9f8..eea556940df6 100644
--- a/tools/testing/selftests/bpf/progs/test_vmlinux.c
+++ b/tools/testing/selftests/bpf/progs/test_vmlinux.c
@@ -69,7 +69,7 @@ int BPF_PROG(handle__tp_btf, struct pt_regs *regs, long id)
return 0;
}
-SEC("kprobe/hrtimer_start_range_ns")
+SEC("kprobe")
int BPF_KPROBE(handle__kprobe, struct hrtimer *timer, ktime_t tim, u64 delta_ns,
const enum hrtimer_mode mode)
{
@@ -78,7 +78,7 @@ int BPF_KPROBE(handle__kprobe, struct hrtimer *timer, ktime_t tim, u64 delta_ns,
return 0;
}
-SEC("fentry/hrtimer_start_range_ns")
+SEC("fentry")
int BPF_PROG(handle__fentry, struct hrtimer *timer, ktime_t tim, u64 delta_ns,
const enum hrtimer_mode mode)
{
diff --git a/tools/testing/selftests/bpf/progs/test_wakeup_source.c b/tools/testing/selftests/bpf/progs/test_wakeup_source.c
new file mode 100644
index 000000000000..fd2fb6aebd82
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_wakeup_source.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright 2026 Google LLC */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_core_read.h>
+#include "bpf_experimental.h"
+#include "bpf_misc.h"
+#include "wakeup_source.h"
+
+#define MAX_LOOP_ITER 1000
+#define RB_SIZE (16384 * 4)
+
+struct {
+ __uint(type, BPF_MAP_TYPE_RINGBUF);
+ __uint(max_entries, RB_SIZE);
+} rb SEC(".maps");
+
+struct bpf_ws_lock;
+struct bpf_ws_lock *bpf_wakeup_sources_read_lock(void) __ksym;
+void bpf_wakeup_sources_read_unlock(struct bpf_ws_lock *lock) __ksym;
+void *bpf_wakeup_sources_get_head(void) __ksym;
+
+SEC("syscall")
+__success __retval(0)
+int iterate_wakeupsources(void *ctx)
+{
+ struct list_head *head = bpf_wakeup_sources_get_head();
+ struct list_head *pos = head;
+ struct bpf_ws_lock *lock;
+ int i;
+
+ lock = bpf_wakeup_sources_read_lock();
+ if (!lock)
+ return 0;
+
+ bpf_for(i, 0, MAX_LOOP_ITER) {
+ if (bpf_core_read(&pos, sizeof(pos), &pos->next) || !pos || pos == head)
+ break;
+
+ struct wakeup_event_t *e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
+
+ if (!e)
+ break;
+
+ struct wakeup_source *ws = bpf_core_cast(
+ (void *)pos - bpf_core_field_offset(struct wakeup_source, entry),
+ struct wakeup_source);
+ s64 active_time = 0;
+ bool active = BPF_CORE_READ_BITFIELD(ws, active);
+ bool autosleep_enable = BPF_CORE_READ_BITFIELD(ws, autosleep_enabled);
+ s64 last_time = ws->last_time;
+ s64 max_time = ws->max_time;
+ s64 prevent_sleep_time = ws->prevent_sleep_time;
+ s64 total_time = ws->total_time;
+
+ if (active) {
+ s64 curr_time = bpf_ktime_get_ns();
+ s64 prevent_time = ws->start_prevent_time;
+
+ if (curr_time > last_time)
+ active_time = curr_time - last_time;
+
+ total_time += active_time;
+ if (active_time > max_time)
+ max_time = active_time;
+ if (autosleep_enable && curr_time > prevent_time)
+ prevent_sleep_time += curr_time - prevent_time;
+ }
+
+ e->active_count = ws->active_count;
+ e->active_time_ns = active_time;
+ e->event_count = ws->event_count;
+ e->expire_count = ws->expire_count;
+ e->last_time_ns = last_time;
+ e->max_time_ns = max_time;
+ e->prevent_sleep_time_ns = prevent_sleep_time;
+ e->total_time_ns = total_time;
+ e->wakeup_count = ws->wakeup_count;
+
+ if (bpf_probe_read_kernel_str(
+ e->name, WAKEUP_NAME_LEN, ws->name) < 0)
+ e->name[0] = '\0';
+
+ bpf_ringbuf_submit(e, 0);
+ }
+
+ bpf_wakeup_sources_read_unlock(lock);
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_xdp_meta.c b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
index 0a0f371a2dec..08b03be0b891 100644
--- a/tools/testing/selftests/bpf/progs/test_xdp_meta.c
+++ b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
@@ -1,12 +1,12 @@
-#include <stdbool.h>
-#include <linux/bpf.h>
-#include <linux/errno.h>
-#include <linux/if_ether.h>
-#include <linux/pkt_cls.h>
+// SPDX-License-Identifier: GPL-2.0
+#include <vmlinux.h>
#include <bpf/bpf_endian.h>
#include <bpf/bpf_helpers.h>
+#include <errno.h>
+
#include "bpf_kfuncs.h"
+#include "bpf_tracing_net.h"
#define META_SIZE 32
@@ -21,10 +21,6 @@
bool test_pass;
-static const __u8 smac_want[ETH_ALEN] = {
- 0x12, 0x34, 0xDE, 0xAD, 0xBE, 0xEF,
-};
-
static const __u8 meta_want[META_SIZE] = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
@@ -32,17 +28,12 @@ static const __u8 meta_want[META_SIZE] = {
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
};
-static bool check_smac(const struct ethhdr *eth)
-{
- return !__builtin_memcmp(eth->h_source, smac_want, ETH_ALEN);
-}
-
static bool check_metadata(const char *file, int line, __u8 *meta_have)
{
if (!__builtin_memcmp(meta_have, meta_want, META_SIZE))
return true;
- bpf_stream_printk(BPF_STREAM_STDERR,
+ bpf_stream_printk(BPF_STDERR,
"FAIL:%s:%d: metadata mismatch\n"
" have:\n %pI6\n %pI6\n"
" want:\n %pI6\n %pI6\n",
@@ -280,18 +271,47 @@ fail:
return TC_ACT_SHOT;
}
+/* Test packets carry test metadata pattern as payload. */
+static bool is_test_packet_xdp(struct xdp_md *ctx)
+{
+ __u8 meta_have[META_SIZE];
+ __u32 len;
+
+ len = bpf_xdp_get_buff_len(ctx);
+ if (len < META_SIZE)
+ return false;
+ if (bpf_xdp_load_bytes(ctx, len - META_SIZE, meta_have, META_SIZE))
+ return false;
+ if (__builtin_memcmp(meta_have, meta_want, META_SIZE))
+ return false;
+
+ return true;
+}
+
+/* Test packets carry test metadata pattern as payload. */
+static bool is_test_packet_tc(struct __sk_buff *ctx)
+{
+ __u8 meta_have[META_SIZE];
+
+ if (ctx->len < META_SIZE)
+ return false;
+ if (bpf_skb_load_bytes(ctx, ctx->len - META_SIZE, meta_have, META_SIZE))
+ return false;
+ if (__builtin_memcmp(meta_have, meta_want, META_SIZE))
+ return false;
+
+ return true;
+}
+
/* Reserve and clear space for metadata but don't populate it */
SEC("xdp")
int ing_xdp_zalloc_meta(struct xdp_md *ctx)
{
- struct ethhdr *eth = ctx_ptr(ctx, data);
__u8 *meta;
int ret;
/* Drop any non-test packets */
- if (eth + 1 > ctx_ptr(ctx, data_end))
- return XDP_DROP;
- if (!check_smac(eth))
+ if (!is_test_packet_xdp(ctx))
return XDP_DROP;
ret = bpf_xdp_adjust_meta(ctx, -META_SIZE);
@@ -310,33 +330,24 @@ int ing_xdp_zalloc_meta(struct xdp_md *ctx)
SEC("xdp")
int ing_xdp(struct xdp_md *ctx)
{
- __u8 *data, *data_meta, *data_end, *payload;
- struct ethhdr *eth;
+ __u8 *data, *data_meta;
int ret;
+ /* Drop any non-test packets */
+ if (!is_test_packet_xdp(ctx))
+ return XDP_DROP;
+
ret = bpf_xdp_adjust_meta(ctx, -META_SIZE);
if (ret < 0)
return XDP_DROP;
data_meta = ctx_ptr(ctx, data_meta);
- data_end = ctx_ptr(ctx, data_end);
data = ctx_ptr(ctx, data);
- eth = (struct ethhdr *)data;
- payload = data + sizeof(struct ethhdr);
-
- if (payload + META_SIZE > data_end ||
- data_meta + META_SIZE > data)
- return XDP_DROP;
-
- /* The Linux networking stack may send other packets on the test
- * interface that interfere with the test. Just drop them.
- * The test packets can be recognized by their source MAC address.
- */
- if (!check_smac(eth))
+ if (data_meta + META_SIZE > data)
return XDP_DROP;
- __builtin_memcpy(data_meta, payload, META_SIZE);
+ __builtin_memcpy(data_meta, meta_want, META_SIZE);
return XDP_PASS;
}
@@ -353,7 +364,7 @@ int clone_data_meta_survives_data_write(struct __sk_buff *ctx)
if (eth + 1 > ctx_ptr(ctx, data_end))
goto out;
/* Ignore non-test packets */
- if (!check_smac(eth))
+ if (!is_test_packet_tc(ctx))
goto out;
if (meta_have + META_SIZE > eth)
@@ -383,7 +394,7 @@ int clone_data_meta_survives_meta_write(struct __sk_buff *ctx)
if (eth + 1 > ctx_ptr(ctx, data_end))
goto out;
/* Ignore non-test packets */
- if (!check_smac(eth))
+ if (!is_test_packet_tc(ctx))
goto out;
if (meta_have + META_SIZE > eth)
@@ -416,7 +427,7 @@ int clone_meta_dynptr_survives_data_slice_write(struct __sk_buff *ctx)
if (!eth)
goto out;
/* Ignore non-test packets */
- if (!check_smac(eth))
+ if (!is_test_packet_tc(ctx))
goto out;
bpf_dynptr_from_skb_meta(ctx, 0, &meta);
@@ -436,16 +447,11 @@ out:
SEC("tc")
int clone_meta_dynptr_survives_meta_slice_write(struct __sk_buff *ctx)
{
- struct bpf_dynptr data, meta;
- const struct ethhdr *eth;
+ struct bpf_dynptr meta;
__u8 *meta_have;
- bpf_dynptr_from_skb(ctx, 0, &data);
- eth = bpf_dynptr_slice(&data, 0, NULL, sizeof(*eth));
- if (!eth)
- goto out;
/* Ignore non-test packets */
- if (!check_smac(eth))
+ if (!is_test_packet_tc(ctx))
goto out;
bpf_dynptr_from_skb_meta(ctx, 0, &meta);
@@ -471,15 +477,10 @@ int clone_meta_dynptr_rw_before_data_dynptr_write(struct __sk_buff *ctx)
{
struct bpf_dynptr data, meta;
__u8 meta_have[META_SIZE];
- const struct ethhdr *eth;
int err;
- bpf_dynptr_from_skb(ctx, 0, &data);
- eth = bpf_dynptr_slice(&data, 0, NULL, sizeof(*eth));
- if (!eth)
- goto out;
/* Ignore non-test packets */
- if (!check_smac(eth))
+ if (!is_test_packet_tc(ctx))
goto out;
/* Expect read-write metadata before unclone */
@@ -492,6 +493,7 @@ int clone_meta_dynptr_rw_before_data_dynptr_write(struct __sk_buff *ctx)
goto out;
/* Helper write to payload will unclone the packet */
+ bpf_dynptr_from_skb(ctx, 0, &data);
bpf_dynptr_write(&data, offsetof(struct ethhdr, h_proto), "x", 1, 0);
err = bpf_dynptr_read(meta_have, META_SIZE, &meta, 0, 0);
@@ -511,17 +513,12 @@ out:
SEC("tc")
int clone_meta_dynptr_rw_before_meta_dynptr_write(struct __sk_buff *ctx)
{
- struct bpf_dynptr data, meta;
+ struct bpf_dynptr meta;
__u8 meta_have[META_SIZE];
- const struct ethhdr *eth;
int err;
- bpf_dynptr_from_skb(ctx, 0, &data);
- eth = bpf_dynptr_slice(&data, 0, NULL, sizeof(*eth));
- if (!eth)
- goto out;
/* Ignore non-test packets */
- if (!check_smac(eth))
+ if (!is_test_packet_tc(ctx))
goto out;
/* Expect read-write metadata before unclone */
@@ -545,6 +542,28 @@ out:
return TC_ACT_SHOT;
}
+SEC("lwt_xmit")
+int dummy_lwt_xmit(struct __sk_buff *ctx)
+{
+ if (bpf_skb_change_head(ctx, sizeof(struct ipv6hdr), 0))
+ return BPF_DROP;
+
+ return BPF_OK;
+}
+
+SEC("tc")
+int tc_is_meta_empty(struct __sk_buff *ctx)
+{
+ if (!is_test_packet_tc(ctx))
+ return TC_ACT_OK;
+
+ if (ctx->data_meta != ctx->data)
+ return TC_ACT_OK;
+
+ test_pass = true;
+ return TC_ACT_OK;
+}
+
SEC("tc")
int helper_skb_vlan_push_pop(struct __sk_buff *ctx)
{
diff --git a/tools/testing/selftests/bpf/progs/timer.c b/tools/testing/selftests/bpf/progs/timer.c
index 4c677c001258..d6d5fefcd9b1 100644
--- a/tools/testing/selftests/bpf/progs/timer.c
+++ b/tools/testing/selftests/bpf/progs/timer.c
@@ -1,13 +1,17 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2021 Facebook */
-#include <linux/bpf.h>
-#include <time.h>
+
+#include <vmlinux.h>
#include <stdbool.h>
#include <errno.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
+#define CLOCK_MONOTONIC 1
+#define CLOCK_BOOTTIME 7
+
char _license[] SEC("license") = "GPL";
+
struct hmap_elem {
int counter;
struct bpf_timer timer;
@@ -59,10 +63,14 @@ __u64 bss_data;
__u64 abs_data;
__u64 err;
__u64 ok;
+__u64 test_hits;
+__u64 update_hits;
+__u64 cancel_hits;
__u64 callback_check = 52;
__u64 callback2_check = 52;
__u64 pinned_callback_check;
__s32 pinned_cpu;
+bool async_cancel = 0;
#define ARRAY 1
#define HTAB 2
@@ -164,6 +172,29 @@ int BPF_PROG2(test1, int, a)
return 0;
}
+static int timer_error(void *map, int *key, struct bpf_timer *timer)
+{
+ err = 42;
+ return 0;
+}
+
+SEC("syscall")
+int test_async_cancel_succeed(void *ctx)
+{
+ struct bpf_timer *arr_timer;
+ int array_key = ARRAY;
+
+ arr_timer = bpf_map_lookup_elem(&array, &array_key);
+ if (!arr_timer)
+ return 0;
+ bpf_timer_init(arr_timer, &array, CLOCK_MONOTONIC);
+ bpf_timer_set_callback(arr_timer, timer_error);
+ bpf_timer_start(arr_timer, 100000 /* 100us */, 0);
+ bpf_timer_cancel_async(arr_timer);
+ ok = 7;
+ return 0;
+}
+
/* callback for prealloc and non-prealloca hashtab timers */
static int timer_cb2(void *map, int *key, struct hmap_elem *val)
{
@@ -399,27 +430,88 @@ static int race_timer_callback(void *race_array, int *race_key, struct bpf_timer
return 0;
}
-SEC("syscall")
-int race(void *ctx)
+/* Callback that updates its own map element */
+static int update_self_callback(void *map, int *key, struct bpf_timer *timer)
+{
+ struct elem init = {};
+
+ bpf_map_update_elem(map, key, &init, BPF_ANY);
+ __sync_fetch_and_add(&update_hits, 1);
+ return 0;
+}
+
+/* Callback that cancels itself using async cancel */
+static int cancel_self_callback(void *map, int *key, struct bpf_timer *timer)
+{
+ bpf_timer_cancel_async(timer);
+ __sync_fetch_and_add(&cancel_hits, 1);
+ return 0;
+}
+
+enum test_mode {
+ TEST_RACE_SYNC,
+ TEST_RACE_ASYNC,
+ TEST_UPDATE,
+ TEST_CANCEL,
+};
+
+static __always_inline int test_common(enum test_mode mode)
{
struct bpf_timer *timer;
- int err, race_key = 0;
struct elem init;
+ int ret, key = 0;
__builtin_memset(&init, 0, sizeof(struct elem));
- bpf_map_update_elem(&race_array, &race_key, &init, BPF_ANY);
- timer = bpf_map_lookup_elem(&race_array, &race_key);
+ bpf_map_update_elem(&race_array, &key, &init, BPF_ANY);
+ timer = bpf_map_lookup_elem(&race_array, &key);
if (!timer)
- return 1;
+ return 0;
- err = bpf_timer_init(timer, &race_array, CLOCK_MONOTONIC);
- if (err && err != -EBUSY)
- return 1;
+ ret = bpf_timer_init(timer, &race_array, CLOCK_MONOTONIC);
+ if (ret && ret != -EBUSY)
+ return 0;
+
+ if (mode == TEST_RACE_SYNC || mode == TEST_RACE_ASYNC)
+ bpf_timer_set_callback(timer, race_timer_callback);
+ else if (mode == TEST_UPDATE)
+ bpf_timer_set_callback(timer, update_self_callback);
+ else
+ bpf_timer_set_callback(timer, cancel_self_callback);
- bpf_timer_set_callback(timer, race_timer_callback);
bpf_timer_start(timer, 0, 0);
- bpf_timer_cancel(timer);
+
+ if (mode == TEST_RACE_ASYNC)
+ bpf_timer_cancel_async(timer);
+ else if (mode == TEST_RACE_SYNC)
+ bpf_timer_cancel(timer);
return 0;
}
+
+SEC("syscall")
+int race(void *ctx)
+{
+ return test_common(async_cancel ? TEST_RACE_ASYNC : TEST_RACE_SYNC);
+}
+
+SEC("perf_event")
+int nmi_race(void *ctx)
+{
+ __sync_fetch_and_add(&test_hits, 1);
+ return test_common(TEST_RACE_ASYNC);
+}
+
+SEC("perf_event")
+int nmi_update(void *ctx)
+{
+ __sync_fetch_and_add(&test_hits, 1);
+ return test_common(TEST_UPDATE);
+}
+
+SEC("perf_event")
+int nmi_cancel(void *ctx)
+{
+ __sync_fetch_and_add(&test_hits, 1);
+ return test_common(TEST_CANCEL);
+}
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/timer_start_deadlock.c b/tools/testing/selftests/bpf/progs/timer_start_deadlock.c
new file mode 100644
index 000000000000..afabd15bdac4
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/timer_start_deadlock.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+#define CLOCK_MONOTONIC 1
+
+char _license[] SEC("license") = "GPL";
+
+struct elem {
+ struct bpf_timer timer;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, int);
+ __type(value, struct elem);
+} timer_map SEC(".maps");
+
+volatile int in_timer_start;
+volatile int tp_called;
+
+static int timer_cb(void *map, int *key, struct elem *value)
+{
+ return 0;
+}
+
+SEC("tp_btf/hrtimer_start")
+int BPF_PROG(tp_hrtimer_start, struct hrtimer *hrtimer, enum hrtimer_mode mode, bool was_armed)
+{
+ struct bpf_timer *timer;
+ int key = 0;
+
+ if (!in_timer_start || !was_armed)
+ return 0;
+
+ tp_called = 1;
+ timer = bpf_map_lookup_elem(&timer_map, &key);
+
+ /*
+ * Call bpf_timer_start() from the tracepoint within hrtimer logic
+ * on the same timer to make sure it doesn't deadlock.
+ */
+ bpf_timer_start(timer, 1000000000, 0);
+ return 0;
+}
+
+SEC("syscall")
+int start_timer(void *ctx)
+{
+ struct bpf_timer *timer;
+ int key = 0;
+
+ timer = bpf_map_lookup_elem(&timer_map, &key);
+ /* claude may complain here that there is no NULL check. Ignoring it. */
+ bpf_timer_init(timer, &timer_map, CLOCK_MONOTONIC);
+ bpf_timer_set_callback(timer, timer_cb);
+
+ /*
+ * call hrtimer_start() twice, so that 2nd call does
+ * trace_hrtimer_start(was_armed=1) tracepoint.
+ */
+ in_timer_start = 1;
+ bpf_timer_start(timer, 1000000000, 0);
+ bpf_timer_start(timer, 1000000000, 0);
+ in_timer_start = 0;
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/timer_start_delete_race.c b/tools/testing/selftests/bpf/progs/timer_start_delete_race.c
new file mode 100644
index 000000000000..2ff6357f33f9
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/timer_start_delete_race.c
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <linux/bpf.h>
+#include <time.h>
+#include <bpf/bpf_helpers.h>
+
+#define ITER_CNT 2000
+
+struct map_value {
+ struct bpf_timer timer;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, int);
+ __type(value, struct map_value);
+ __uint(max_entries, 1);
+} timer_map SEC(".maps");
+
+long cb_cnt;
+
+/*
+ * Timer callback that accesses the map value.
+ * If the race bug exists and this runs on freed memory,
+ * KASAN should detect it.
+ */
+static int timer_cb(void *map, int *key, struct map_value *value)
+{
+ __sync_fetch_and_add(&cb_cnt, 1);
+ return 0;
+}
+
+SEC("syscall")
+int start_timer(void *ctx)
+{
+ struct map_value *value;
+ int i;
+
+ for (i = 0; i < ITER_CNT; i++) {
+ int key = 0;
+
+ value = bpf_map_lookup_elem(&timer_map, &key);
+ /* hey claude, no if (!value) here. The verifier got smarter */
+
+ bpf_timer_init(&value->timer, &timer_map, CLOCK_MONOTONIC);
+ bpf_timer_set_callback(&value->timer, timer_cb);
+ bpf_timer_start(&value->timer, 100000000, 0);
+ }
+ return 0;
+}
+
+SEC("syscall")
+int delete_elem(void *ctx)
+{
+ int i;
+
+ for (i = 0; i < ITER_CNT; i++) {
+ int key = 0;
+
+ bpf_map_delete_elem(&timer_map, &key);
+ }
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/token_kallsyms.c b/tools/testing/selftests/bpf/progs/token_kallsyms.c
new file mode 100644
index 000000000000..c9f9344f3eb2
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/token_kallsyms.c
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+
+char _license[] SEC("license") = "GPL";
+
+__weak
+int token_ksym_subprog(void)
+{
+ return 0;
+}
+
+SEC("xdp")
+int xdp_main(struct xdp_md *xdp)
+{
+ return token_ksym_subprog();
+}
diff --git a/tools/testing/selftests/bpf/progs/trace_printk.c b/tools/testing/selftests/bpf/progs/trace_printk.c
index 6695478c2b25..f4c538ec3ebd 100644
--- a/tools/testing/selftests/bpf/progs/trace_printk.c
+++ b/tools/testing/selftests/bpf/progs/trace_printk.c
@@ -10,13 +10,23 @@ char _license[] SEC("license") = "GPL";
int trace_printk_ret = 0;
int trace_printk_ran = 0;
+int trace_printk_invalid_spec_ret = 0;
+int trace_printk_utf8_ret = 0;
+int trace_printk_utf8_ran = 0;
const char fmt[] = "Testing,testing %d\n";
+static const char utf8_fmt[] = "中文,测试 %d\n";
+/* Non-ASCII bytes after '%' must still be rejected. */
+static const char invalid_spec_fmt[] = "%\x80\n";
SEC("fentry/" SYS_PREFIX "sys_nanosleep")
int sys_enter(void *ctx)
{
trace_printk_ret = bpf_trace_printk(fmt, sizeof(fmt),
++trace_printk_ran);
+ trace_printk_utf8_ret = bpf_trace_printk(utf8_fmt, sizeof(utf8_fmt),
+ ++trace_printk_utf8_ran);
+ trace_printk_invalid_spec_ret = bpf_trace_printk(invalid_spec_fmt,
+ sizeof(invalid_spec_fmt));
return 0;
}
diff --git a/tools/testing/selftests/bpf/progs/tracing_failure.c b/tools/testing/selftests/bpf/progs/tracing_failure.c
index 65e485c4468c..f7a095767679 100644
--- a/tools/testing/selftests/bpf/progs/tracing_failure.c
+++ b/tools/testing/selftests/bpf/progs/tracing_failure.c
@@ -30,3 +30,9 @@ int BPF_PROG(fexit_noreturns)
{
return 0;
}
+
+SEC("?fexit/bpf_testmod_test_int128_ret")
+int BPF_PROG(fexit_int128_ret)
+{
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/tracing_multi_attach.c b/tools/testing/selftests/bpf/progs/tracing_multi_attach.c
new file mode 100644
index 000000000000..332d0a423a43
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tracing_multi_attach.c
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+__hidden extern int tracing_multi_arg_check(__u64 *ctx, __u64 *test_result, bool is_return);
+
+__u64 test_result_fentry = 0;
+__u64 test_result_fexit = 0;
+
+SEC("fentry.multi/bpf_fentry_test*")
+int BPF_PROG(test_fentry)
+{
+ tracing_multi_arg_check(ctx, &test_result_fentry, false);
+ return 0;
+}
+
+SEC("fexit.multi/bpf_fentry_test*")
+int BPF_PROG(test_fexit)
+{
+ tracing_multi_arg_check(ctx, &test_result_fexit, true);
+ return 0;
+}
+
+SEC("fentry.multi.s/bpf_fentry_test1")
+int BPF_PROG(test_fentry_s)
+{
+ tracing_multi_arg_check(ctx, &test_result_fentry, false);
+ return 0;
+}
+
+SEC("fexit.multi.s/bpf_fentry_test1")
+int BPF_PROG(test_fexit_s)
+{
+ tracing_multi_arg_check(ctx, &test_result_fexit, true);
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/tracing_multi_attach_module.c b/tools/testing/selftests/bpf/progs/tracing_multi_attach_module.c
new file mode 100644
index 000000000000..b3374f2db450
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tracing_multi_attach_module.c
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+__hidden extern int tracing_multi_arg_check(__u64 *ctx, __u64 *test_result, bool is_return);
+
+__u64 test_result_fentry = 0;
+__u64 test_result_fexit = 0;
+
+SEC("fentry.multi/bpf_testmod:bpf_testmod_fentry_test*")
+int BPF_PROG(test_fentry)
+{
+ tracing_multi_arg_check(ctx, &test_result_fentry, false);
+ return 0;
+}
+
+SEC("fexit.multi/bpf_testmod:bpf_testmod_fentry_test*")
+int BPF_PROG(test_fexit)
+{
+ tracing_multi_arg_check(ctx, &test_result_fexit, true);
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/tracing_multi_bench.c b/tools/testing/selftests/bpf/progs/tracing_multi_bench.c
new file mode 100644
index 000000000000..beae946cb8c4
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tracing_multi_bench.c
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+SEC("fentry.multi")
+int BPF_PROG(bench)
+{
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/tracing_multi_check.c b/tools/testing/selftests/bpf/progs/tracing_multi_check.c
new file mode 100644
index 000000000000..b2959ba71179
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tracing_multi_check.c
@@ -0,0 +1,214 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+int pid = 0;
+bool test_cookies = false;
+
+/* bpf_fentry_test1 is exported as kfunc via vmlinux.h */
+extern const void bpf_fentry_test2 __ksym;
+extern const void bpf_fentry_test3 __ksym;
+extern const void bpf_fentry_test4 __ksym;
+extern const void bpf_fentry_test5 __ksym;
+extern const void bpf_fentry_test6 __ksym;
+extern const void bpf_fentry_test7 __ksym;
+extern const void bpf_fentry_test8 __ksym;
+extern const void bpf_fentry_test9 __ksym;
+extern const void bpf_fentry_test10 __ksym;
+
+extern const void bpf_testmod_fentry_test1 __ksym;
+extern const void bpf_testmod_fentry_test2 __ksym;
+extern const void bpf_testmod_fentry_test3 __ksym;
+extern const void bpf_testmod_fentry_test7 __ksym;
+extern const void bpf_testmod_fentry_test11 __ksym;
+
+int tracing_multi_arg_check(__u64 *ctx, __u64 *test_result, bool is_return)
+{
+ void *ip = (void *) bpf_get_func_ip(ctx);
+ __u64 value = 0, ret = 0, cookie = 0;
+ long err = 0;
+
+ if (bpf_get_current_pid_tgid() >> 32 != pid)
+ return 1;
+
+ if (is_return)
+ err |= bpf_get_func_ret(ctx, &ret);
+ if (test_cookies)
+ cookie = bpf_get_attach_cookie(ctx);
+
+ if (ip == &bpf_fentry_test1) {
+ int a;
+
+ err |= bpf_get_func_arg(ctx, 0, &value);
+ a = (int) value;
+
+ err |= is_return ? ret != 2 : 0;
+ err |= test_cookies ? cookie != 8 : 0;
+
+ *test_result += err == 0 && a == 1;
+ } else if (ip == &bpf_fentry_test2) {
+ __u64 b;
+ int a;
+
+ err |= bpf_get_func_arg(ctx, 0, &value);
+ a = (int) value;
+ err |= bpf_get_func_arg(ctx, 1, &value);
+ b = value;
+
+ err |= is_return ? ret != 5 : 0;
+ err |= test_cookies ? cookie != 9 : 0;
+
+ *test_result += err == 0 && a == 2 && b == 3;
+ } else if (ip == &bpf_fentry_test3) {
+ __u64 c;
+ char a;
+ int b;
+
+ err |= bpf_get_func_arg(ctx, 0, &value);
+ a = (char) value;
+ err |= bpf_get_func_arg(ctx, 1, &value);
+ b = (int) value;
+ err |= bpf_get_func_arg(ctx, 2, &value);
+ c = value;
+
+ err |= is_return ? ret != 15 : 0;
+ err |= test_cookies ? cookie != 7 : 0;
+
+ *test_result += err == 0 && a == 4 && b == 5 && c == 6;
+ } else if (ip == &bpf_fentry_test4) {
+ void *a;
+ char b;
+ int c;
+ __u64 d;
+
+ err |= bpf_get_func_arg(ctx, 0, &value);
+ a = (void *) value;
+ err |= bpf_get_func_arg(ctx, 1, &value);
+ b = (char) value;
+ err |= bpf_get_func_arg(ctx, 2, &value);
+ c = (int) value;
+ err |= bpf_get_func_arg(ctx, 3, &value);
+ d = value;
+
+ err |= is_return ? ret != 34 : 0;
+ err |= test_cookies ? cookie != 5 : 0;
+
+ *test_result += err == 0 && a == (void *) 7 && b == 8 && c == 9 && d == 10;
+ } else if (ip == &bpf_fentry_test5) {
+ __u64 a;
+ void *b;
+ short c;
+ int d;
+ __u64 e;
+
+ err |= bpf_get_func_arg(ctx, 0, &value);
+ a = value;
+ err |= bpf_get_func_arg(ctx, 1, &value);
+ b = (void *) value;
+ err |= bpf_get_func_arg(ctx, 2, &value);
+ c = (short) value;
+ err |= bpf_get_func_arg(ctx, 3, &value);
+ d = (int) value;
+ err |= bpf_get_func_arg(ctx, 4, &value);
+ e = value;
+
+ err |= is_return ? ret != 65 : 0;
+ err |= test_cookies ? cookie != 4 : 0;
+
+ *test_result += err == 0 && a == 11 && b == (void *) 12 && c == 13 && d == 14 && e == 15;
+ } else if (ip == &bpf_fentry_test6) {
+ __u64 a;
+ void *b;
+ short c;
+ int d;
+ void *e;
+ __u64 f;
+
+ err |= bpf_get_func_arg(ctx, 0, &value);
+ a = value;
+ err |= bpf_get_func_arg(ctx, 1, &value);
+ b = (void *) value;
+ err |= bpf_get_func_arg(ctx, 2, &value);
+ c = (short) value;
+ err |= bpf_get_func_arg(ctx, 3, &value);
+ d = (int) value;
+ err |= bpf_get_func_arg(ctx, 4, &value);
+ e = (void *) value;
+ err |= bpf_get_func_arg(ctx, 5, &value);
+ f = value;
+
+ err |= is_return ? ret != 111 : 0;
+ err |= test_cookies ? cookie != 2 : 0;
+
+ *test_result += err == 0 && a == 16 && b == (void *) 17 && c == 18 && d == 19 && e == (void *) 20 && f == 21;
+ } else if (ip == &bpf_fentry_test7) {
+ err |= is_return ? ret != 0 : 0;
+ err |= test_cookies ? cookie != 3 : 0;
+
+ *test_result += err == 0 ? 1 : 0;
+ } else if (ip == &bpf_fentry_test8) {
+ err |= is_return ? ret != 0 : 0;
+ err |= test_cookies ? cookie != 1 : 0;
+
+ *test_result += err == 0 ? 1 : 0;
+ } else if (ip == &bpf_fentry_test9) {
+ err |= is_return ? ret != 0 : 0;
+ err |= test_cookies ? cookie != 10 : 0;
+
+ *test_result += err == 0 ? 1 : 0;
+ } else if (ip == &bpf_fentry_test10) {
+ err |= is_return ? ret != 0 : 0;
+ err |= test_cookies ? cookie != 6 : 0;
+
+ *test_result += err == 0 ? 1 : 0;
+ } else if (ip == &bpf_testmod_fentry_test1) {
+ int a;
+
+ err |= bpf_get_func_arg(ctx, 0, &value);
+ a = (int) value;
+
+ err |= is_return ? ret != 2 : 0;
+
+ *test_result += err == 0 && a == 1;
+ } else if (ip == &bpf_testmod_fentry_test2) {
+ int a;
+ __u64 b;
+
+ err |= bpf_get_func_arg(ctx, 0, &value);
+ a = (int) value;
+ err |= bpf_get_func_arg(ctx, 1, &value);
+ b = (__u64) value;
+
+ err |= is_return ? ret != 5 : 0;
+
+ *test_result += err == 0 && a == 2 && b == 3;
+ } else if (ip == &bpf_testmod_fentry_test3) {
+ char a;
+ int b;
+ __u64 c;
+
+ err |= bpf_get_func_arg(ctx, 0, &value);
+ a = (char) value;
+ err |= bpf_get_func_arg(ctx, 1, &value);
+ b = (int) value;
+ err |= bpf_get_func_arg(ctx, 2, &value);
+ c = (__u64) value;
+
+ err |= is_return ? ret != 15 : 0;
+
+ *test_result += err == 0 && a == 4 && b == 5 && c == 6;
+ } else if (ip == &bpf_testmod_fentry_test7) {
+ err |= is_return ? ret != 133 : 0;
+
+ *test_result += err == 0;
+ } else if (ip == &bpf_testmod_fentry_test11) {
+ err |= is_return ? ret != 231 : 0;
+
+ *test_result += err == 0;
+ }
+
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/tracing_multi_fail.c b/tools/testing/selftests/bpf/progs/tracing_multi_fail.c
new file mode 100644
index 000000000000..7f0375f4213d
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tracing_multi_fail.c
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+SEC("fentry.multi")
+int BPF_PROG(test_fentry)
+{
+ return 0;
+}
+
+SEC("fentry.multi.s")
+int BPF_PROG(test_fentry_s)
+{
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/tracing_multi_intersect_attach.c b/tools/testing/selftests/bpf/progs/tracing_multi_intersect_attach.c
new file mode 100644
index 000000000000..5b0af8f4c62f
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tracing_multi_intersect_attach.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+__hidden extern int tracing_multi_arg_check(__u64 *ctx, __u64 *test_result, bool is_return);
+
+__u64 test_result_fentry_1 = 0;
+__u64 test_result_fentry_2 = 0;
+__u64 test_result_fexit_1 = 0;
+__u64 test_result_fexit_2 = 0;
+__u64 test_result_fentry = 0;
+
+SEC("fentry/bpf_fentry_test1")
+int BPF_PROG(fentry)
+{
+ tracing_multi_arg_check(ctx, &test_result_fentry, false);
+ return 0;
+}
+
+SEC("fentry.multi")
+int BPF_PROG(fentry_1)
+{
+ tracing_multi_arg_check(ctx, &test_result_fentry_1, false);
+ return 0;
+}
+
+SEC("fentry.multi")
+int BPF_PROG(fentry_2)
+{
+ tracing_multi_arg_check(ctx, &test_result_fentry_2, false);
+ return 0;
+}
+
+SEC("fexit.multi")
+int BPF_PROG(fexit_1)
+{
+ tracing_multi_arg_check(ctx, &test_result_fexit_1, true);
+ return 0;
+}
+
+SEC("fexit.multi")
+int BPF_PROG(fexit_2)
+{
+ tracing_multi_arg_check(ctx, &test_result_fexit_2, true);
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/tracing_multi_rollback.c b/tools/testing/selftests/bpf/progs/tracing_multi_rollback.c
new file mode 100644
index 000000000000..a49d1d841f3a
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tracing_multi_rollback.c
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+int pid = 0;
+
+__u64 test_result_fentry = 0;
+__u64 test_result_fexit = 0;
+
+SEC("?fentry.multi")
+int BPF_PROG(test_fentry)
+{
+ if (bpf_get_current_pid_tgid() >> 32 != pid)
+ return 0;
+
+ test_result_fentry++;
+ return 0;
+}
+
+SEC("?fexit.multi")
+int BPF_PROG(test_fexit)
+{
+ if (bpf_get_current_pid_tgid() >> 32 != pid)
+ return 0;
+
+ test_result_fexit++;
+ return 0;
+}
+
+SEC("?fentry/bpf_fentry_test1")
+int BPF_PROG(extra)
+{
+ return 0;
+}
+
+SEC("?fentry/bpf_fentry_test10")
+int BPF_PROG(filler)
+{
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/tracing_multi_session_attach.c b/tools/testing/selftests/bpf/progs/tracing_multi_session_attach.c
new file mode 100644
index 000000000000..7c9a46016ccd
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tracing_multi_session_attach.c
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+__hidden extern int tracing_multi_arg_check(__u64 *ctx, __u64 *test_result, bool is_return);
+
+__u64 test_result_fentry = 0;
+__u64 test_result_fexit = 0;
+
+SEC("fsession.multi/bpf_fentry_test*")
+int BPF_PROG(test_session_1)
+{
+ volatile __u64 *cookie = bpf_session_cookie(ctx);
+
+ if (bpf_session_is_return(ctx)) {
+ if (tracing_multi_arg_check(ctx, &test_result_fexit, true))
+ return 0;
+ /* extra count for test_result_fexit cookie */
+ test_result_fexit += *cookie == 0xbeafbeafbeafbeaf;
+ } else {
+ if (tracing_multi_arg_check(ctx, &test_result_fentry, false))
+ return 0;
+ *cookie = 0xbeafbeafbeafbeaf;
+ }
+ return 0;
+}
+
+SEC("fsession.multi.s/bpf_fentry_test1")
+int BPF_PROG(test_fsession_s)
+{
+ volatile __u64 *cookie = bpf_session_cookie(ctx);
+
+ if (bpf_session_is_return(ctx)) {
+ if (tracing_multi_arg_check(ctx, &test_result_fexit, true))
+ return 0;
+ /* extra count for test_result_fexit cookie */
+ test_result_fexit += *cookie == 0xbeafbeafbeafbeaf;
+ } else {
+ if (tracing_multi_arg_check(ctx, &test_result_fentry, false))
+ return 0;
+ *cookie = 0xbeafbeafbeafbeaf;
+ }
+ return 0;
+}
+
+SEC("fsession.multi/bpf_testmod:bpf_testmod_fentry_test*")
+int BPF_PROG(test_session_2)
+{
+ volatile __u64 *cookie = bpf_session_cookie(ctx);
+
+ if (bpf_session_is_return(ctx)) {
+ if (tracing_multi_arg_check(ctx, &test_result_fexit, true))
+ return 0;
+ /* extra count for test_result_fexit cookie */
+ test_result_fexit += *cookie == 0xbeafbeafbeafbeaf;
+ } else {
+ if (tracing_multi_arg_check(ctx, &test_result_fentry, false))
+ return 0;
+ *cookie = 0xbeafbeafbeafbeaf;
+ }
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/tracing_multi_verifier.c b/tools/testing/selftests/bpf/progs/tracing_multi_verifier.c
new file mode 100644
index 000000000000..7b6ed41bf452
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tracing_multi_verifier.c
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+SEC("fentry.multi/bpf_fentry_test1")
+__failure
+__msg("func 'bpf_multi_func' doesn't have 1-th argument")
+int BPF_PROG(fentry_direct_access, int a)
+{
+ return a;
+}
+
+SEC("fexit.multi/bpf_fentry_test3")
+__failure
+__msg("invalid bpf_context access off=24 size=8")
+int BPF_PROG(fexit_direct_access, char a, int b, __u64 c, int ret)
+{
+ return ret;
+}
+
+SEC("fsession.multi/bpf_fentry_test4")
+__failure
+__msg("invalid bpf_context access off=16 size=8")
+int BPF_PROG(fsession_direct_access, void *a, char b, int c, __u64 d, int ret)
+{
+ return c;
+}
diff --git a/tools/testing/selftests/bpf/progs/tracing_struct_int128.c b/tools/testing/selftests/bpf/progs/tracing_struct_int128.c
new file mode 100644
index 000000000000..4638dfec1f38
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tracing_struct_int128.c
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <vmlinux.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_helpers.h>
+
+long t_b, t_c, t_ret;
+
+SEC("fexit/bpf_testmod_test_int128_arg")
+int test_int128_arg_fexit(unsigned long long *ctx)
+{
+ t_b = (int)ctx[2];
+ t_c = (long)ctx[3];
+ t_ret = (long)ctx[4];
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/trigger_bench.c b/tools/testing/selftests/bpf/progs/trigger_bench.c
index 2898b3749d07..3225b4aee8ff 100644
--- a/tools/testing/selftests/bpf/progs/trigger_bench.c
+++ b/tools/testing/selftests/bpf/progs/trigger_bench.c
@@ -1,10 +1,11 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2020 Facebook
-#include <linux/bpf.h>
+#include "vmlinux.h"
#include <asm/unistd.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include "bpf_misc.h"
+#include "bpf/usdt.bpf.h"
char _license[] SEC("license") = "GPL";
@@ -25,6 +26,34 @@ static __always_inline void inc_counter(void)
__sync_add_and_fetch(&hits[cpu & CPU_MASK].value, 1);
}
+volatile const int stacktrace;
+
+typedef __u64 stack_trace_t[128];
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, __u32);
+ __type(value, stack_trace_t);
+} stack_heap SEC(".maps");
+
+static __always_inline void do_stacktrace(void *ctx)
+{
+ if (!stacktrace)
+ return;
+
+ __u64 *ptr = bpf_map_lookup_elem(&stack_heap, &(__u32){0});
+
+ if (ptr)
+ bpf_get_stack(ctx, ptr, sizeof(stack_trace_t), 0);
+}
+
+static __always_inline void handle(void *ctx)
+{
+ inc_counter();
+ do_stacktrace(ctx);
+}
+
SEC("?uprobe")
int bench_trigger_uprobe(void *ctx)
{
@@ -81,21 +110,21 @@ int trigger_driver_kfunc(void *ctx)
SEC("?kprobe/bpf_get_numa_node_id")
int bench_trigger_kprobe(void *ctx)
{
- inc_counter();
+ handle(ctx);
return 0;
}
SEC("?kretprobe/bpf_get_numa_node_id")
int bench_trigger_kretprobe(void *ctx)
{
- inc_counter();
+ handle(ctx);
return 0;
}
SEC("?kprobe.multi/bpf_get_numa_node_id")
int bench_trigger_kprobe_multi(void *ctx)
{
- inc_counter();
+ handle(ctx);
return 0;
}
@@ -108,7 +137,7 @@ int bench_kprobe_multi_empty(void *ctx)
SEC("?kretprobe.multi/bpf_get_numa_node_id")
int bench_trigger_kretprobe_multi(void *ctx)
{
- inc_counter();
+ handle(ctx);
return 0;
}
@@ -121,34 +150,41 @@ int bench_kretprobe_multi_empty(void *ctx)
SEC("?fentry/bpf_get_numa_node_id")
int bench_trigger_fentry(void *ctx)
{
- inc_counter();
+ handle(ctx);
return 0;
}
SEC("?fexit/bpf_get_numa_node_id")
int bench_trigger_fexit(void *ctx)
{
- inc_counter();
+ handle(ctx);
return 0;
}
SEC("?fmod_ret/bpf_modify_return_test_tp")
int bench_trigger_fmodret(void *ctx)
{
- inc_counter();
+ handle(ctx);
return -22;
}
SEC("?tp/bpf_test_run/bpf_trigger_tp")
int bench_trigger_tp(void *ctx)
{
- inc_counter();
+ handle(ctx);
return 0;
}
SEC("?raw_tp/bpf_trigger_tp")
int bench_trigger_rawtp(void *ctx)
{
+ handle(ctx);
+ return 0;
+}
+
+SEC("?usdt")
+int bench_trigger_usdt(void *ctx)
+{
inc_counter();
return 0;
}
diff --git a/tools/testing/selftests/bpf/progs/uninit_stack.c b/tools/testing/selftests/bpf/progs/uninit_stack.c
index 046a204c8fc6..5db02323c89c 100644
--- a/tools/testing/selftests/bpf/progs/uninit_stack.c
+++ b/tools/testing/selftests/bpf/progs/uninit_stack.c
@@ -76,6 +76,7 @@ __naked int helper_uninit_to_misc(void *ctx)
* thus showing the stack state, matched by __msg(). \
*/ \
call %[dummy]; \
+ r1 = *(u64*)(r10 - 104); \
r0 = 0; \
exit; \
"
diff --git a/tools/testing/selftests/bpf/progs/uprobe_multi_session.c b/tools/testing/selftests/bpf/progs/uprobe_multi_session.c
index 30bff90b68dc..6e46bb00ff58 100644
--- a/tools/testing/selftests/bpf/progs/uprobe_multi_session.c
+++ b/tools/testing/selftests/bpf/progs/uprobe_multi_session.c
@@ -1,9 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
-#include <linux/bpf.h>
+#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <stdbool.h>
-#include "bpf_kfuncs.h"
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
@@ -51,7 +50,7 @@ static int uprobe_multi_check(void *ctx, bool is_return)
SEC("uprobe.session//proc/self/exe:uprobe_multi_func_*")
int uprobe(struct pt_regs *ctx)
{
- return uprobe_multi_check(ctx, bpf_session_is_return());
+ return uprobe_multi_check(ctx, bpf_session_is_return(ctx));
}
static __always_inline bool verify_sleepable_user_copy(void)
@@ -67,5 +66,5 @@ int uprobe_sleepable(struct pt_regs *ctx)
{
if (verify_sleepable_user_copy())
uprobe_multi_sleep_result++;
- return uprobe_multi_check(ctx, bpf_session_is_return());
+ return uprobe_multi_check(ctx, bpf_session_is_return(ctx));
}
diff --git a/tools/testing/selftests/bpf/progs/uprobe_multi_session_cookie.c b/tools/testing/selftests/bpf/progs/uprobe_multi_session_cookie.c
index 5befdf944dc6..b5db196614a9 100644
--- a/tools/testing/selftests/bpf/progs/uprobe_multi_session_cookie.c
+++ b/tools/testing/selftests/bpf/progs/uprobe_multi_session_cookie.c
@@ -1,9 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
-#include <linux/bpf.h>
+#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <stdbool.h>
-#include "bpf_kfuncs.h"
char _license[] SEC("license") = "GPL";
@@ -13,16 +12,16 @@ __u64 test_uprobe_1_result = 0;
__u64 test_uprobe_2_result = 0;
__u64 test_uprobe_3_result = 0;
-static int check_cookie(__u64 val, __u64 *result)
+static int check_cookie(struct pt_regs *ctx, __u64 val, __u64 *result)
{
__u64 *cookie;
if (bpf_get_current_pid_tgid() >> 32 != pid)
return 1;
- cookie = bpf_session_cookie();
+ cookie = bpf_session_cookie(ctx);
- if (bpf_session_is_return())
+ if (bpf_session_is_return(ctx))
*result = *cookie == val ? val : 0;
else
*cookie = val;
@@ -32,17 +31,17 @@ static int check_cookie(__u64 val, __u64 *result)
SEC("uprobe.session//proc/self/exe:uprobe_multi_func_1")
int uprobe_1(struct pt_regs *ctx)
{
- return check_cookie(1, &test_uprobe_1_result);
+ return check_cookie(ctx, 1, &test_uprobe_1_result);
}
SEC("uprobe.session//proc/self/exe:uprobe_multi_func_2")
int uprobe_2(struct pt_regs *ctx)
{
- return check_cookie(2, &test_uprobe_2_result);
+ return check_cookie(ctx, 2, &test_uprobe_2_result);
}
SEC("uprobe.session//proc/self/exe:uprobe_multi_func_3")
int uprobe_3(struct pt_regs *ctx)
{
- return check_cookie(3, &test_uprobe_3_result);
+ return check_cookie(ctx, 3, &test_uprobe_3_result);
}
diff --git a/tools/testing/selftests/bpf/progs/uprobe_multi_session_recursive.c b/tools/testing/selftests/bpf/progs/uprobe_multi_session_recursive.c
index 8fbcd69fae22..3ce309248a04 100644
--- a/tools/testing/selftests/bpf/progs/uprobe_multi_session_recursive.c
+++ b/tools/testing/selftests/bpf/progs/uprobe_multi_session_recursive.c
@@ -1,9 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
-#include <linux/bpf.h>
+#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <stdbool.h>
-#include "bpf_kfuncs.h"
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
@@ -16,11 +15,11 @@ int idx_return = 0;
__u64 test_uprobe_cookie_entry[6];
__u64 test_uprobe_cookie_return[3];
-static int check_cookie(void)
+static int check_cookie(struct pt_regs *ctx)
{
- __u64 *cookie = bpf_session_cookie();
+ __u64 *cookie = bpf_session_cookie(ctx);
- if (bpf_session_is_return()) {
+ if (bpf_session_is_return(ctx)) {
if (idx_return >= ARRAY_SIZE(test_uprobe_cookie_return))
return 1;
test_uprobe_cookie_return[idx_return++] = *cookie;
@@ -40,5 +39,5 @@ int uprobe_recursive(struct pt_regs *ctx)
if (bpf_get_current_pid_tgid() >> 32 != pid)
return 1;
- return check_cookie();
+ return check_cookie(ctx);
}
diff --git a/tools/testing/selftests/bpf/progs/user_ringbuf_fail.c b/tools/testing/selftests/bpf/progs/user_ringbuf_fail.c
index 54de0389f878..c0d0422b8030 100644
--- a/tools/testing/selftests/bpf/progs/user_ringbuf_fail.c
+++ b/tools/testing/selftests/bpf/progs/user_ringbuf_fail.c
@@ -146,7 +146,7 @@ try_discard_dynptr(struct bpf_dynptr *dynptr, void *context)
* not be able to read past the end of the pointer.
*/
SEC("?raw_tp")
-__failure __msg("cannot release unowned const bpf_dynptr")
+__failure __msg("CONST_PTR_TO_DYNPTR cannot be released")
int user_ringbuf_callback_discard_dynptr(void *ctx)
{
bpf_user_ringbuf_drain(&user_ringbuf, try_discard_dynptr, NULL, 0);
@@ -166,7 +166,7 @@ try_submit_dynptr(struct bpf_dynptr *dynptr, void *context)
* not be able to read past the end of the pointer.
*/
SEC("?raw_tp")
-__failure __msg("cannot release unowned const bpf_dynptr")
+__failure __msg("CONST_PTR_TO_DYNPTR cannot be released")
int user_ringbuf_callback_submit_dynptr(void *ctx)
{
bpf_user_ringbuf_drain(&user_ringbuf, try_submit_dynptr, NULL, 0);
diff --git a/tools/testing/selftests/bpf/progs/verifier_align.c b/tools/testing/selftests/bpf/progs/verifier_align.c
new file mode 100644
index 000000000000..3e52686515ca
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_align.c
@@ -0,0 +1,581 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+/* Converted from tools/testing/selftests/bpf/prog_tests/align.c */
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+/* Four tests of known constants. These aren't staggeringly
+ * interesting since we track exact values now.
+ */
+
+SEC("tc")
+__success __log_level(2)
+__flag(BPF_F_ANY_ALIGNMENT)
+__msg("0: R1=ctx() R10=fp0")
+__msg("0: {{.*}} R3=2")
+__msg("1: {{.*}} R3=4")
+__msg("2: {{.*}} R3=8")
+__msg("3: {{.*}} R3=16")
+__msg("4: {{.*}} R3=32")
+__naked void mov(void)
+{
+ asm volatile (" \
+ r3 = 2; \
+ r3 = 4; \
+ r3 = 8; \
+ r3 = 16; \
+ r3 = 32; \
+ r0 = 0; \
+ exit; \
+" ::: __clobber_all);
+}
+
+SEC("tc")
+__success __log_level(2)
+__flag(BPF_F_ANY_ALIGNMENT)
+__msg("0: R1=ctx() R10=fp0")
+__msg("0: {{.*}}R3=1")
+__msg("1: {{.*}}R3=2")
+__msg("2: {{.*}}R3=4")
+__msg("3: {{.*}}R3=8")
+__msg("4: {{.*}}R3=16")
+__msg("5: {{.*}}R3=1")
+__msg("6: {{.*}}R4=32")
+__msg("7: {{.*}}R4=16")
+__msg("8: {{.*}}R4=8")
+__msg("9: {{.*}}R4=4")
+__msg("10: {{.*}}R4=2")
+__naked void shift(void)
+{
+ asm volatile (" \
+ r3 = 1; \
+ r3 <<= 1; \
+ r3 <<= 1; \
+ r3 <<= 1; \
+ r3 <<= 1; \
+ r3 >>= 4; \
+ r4 = 32; \
+ r4 >>= 1; \
+ r4 >>= 1; \
+ r4 >>= 1; \
+ r4 >>= 1; \
+ r0 = 0; \
+ exit; \
+" ::: __clobber_all);
+}
+
+SEC("tc")
+__success __log_level(2)
+__flag(BPF_F_ANY_ALIGNMENT)
+__msg("0: R1=ctx() R10=fp0")
+__msg("0: {{.*}}R3=4")
+__msg("1: {{.*}}R3=8")
+__msg("2: {{.*}}R3=10")
+__msg("3: {{.*}}R4=8")
+__msg("4: {{.*}}R4=12")
+__msg("5: {{.*}}R4=14")
+__naked void addsub(void)
+{
+ asm volatile (" \
+ r3 = 4; \
+ r3 += 4; \
+ r3 += 2; \
+ r4 = 8; \
+ r4 += 4; \
+ r4 += 2; \
+ r0 = 0; \
+ exit; \
+" ::: __clobber_all);
+}
+
+SEC("tc")
+__success __log_level(2)
+__flag(BPF_F_ANY_ALIGNMENT)
+__msg("0: R1=ctx() R10=fp0")
+__msg("0: {{.*}}R3=7")
+__msg("1: {{.*}}R3=7")
+__msg("2: {{.*}}R3=14")
+__msg("3: {{.*}}R3=56")
+__naked void mul(void)
+{
+ asm volatile (" \
+ r3 = 7; \
+ r3 *= 1; \
+ r3 *= 2; \
+ r3 *= 4; \
+ r0 = 0; \
+ exit; \
+" ::: __clobber_all);
+}
+
+/* Tests using unknown values */
+
+#define PREP_PKT_POINTERS \
+ "r2 = *(u32*)(r1 + %[__sk_buff_data]);" \
+ "r3 = *(u32*)(r1 + %[__sk_buff_data_end]);"
+
+#define __LOAD_UNKNOWN(DST_REG, LBL) \
+ "r2 = *(u32*)(r1 + %[__sk_buff_data]);" \
+ "r3 = *(u32*)(r1 + %[__sk_buff_data_end]);" \
+ "r0 = r2;" \
+ "r0 += 8;" \
+ "if r3 >= r0 goto " LBL ";" \
+ "exit;" \
+LBL ":" \
+ DST_REG " = *(u8*)(r2 + 0);"
+
+#define LOAD_UNKNOWN(DST_REG) __LOAD_UNKNOWN(DST_REG, "l99_%=")
+
+SEC("tc")
+__success __log_level(2)
+__flag(BPF_F_ANY_ALIGNMENT)
+__msg("6: {{.*}} R2=pkt(r=8)")
+__msg("6: {{.*}} R3={{[^)]*}}var_off=(0x0; 0xff)")
+__msg("7: {{.*}} R3={{[^)]*}}var_off=(0x0; 0x1fe)")
+__msg("8: {{.*}} R3={{[^)]*}}var_off=(0x0; 0x3fc)")
+__msg("9: {{.*}} R3={{[^)]*}}var_off=(0x0; 0x7f8)")
+__msg("10: {{.*}} R3={{[^)]*}}var_off=(0x0; 0xff0)")
+__msg("12: {{.*}} R3=pkt_end()")
+__msg("17: {{.*}} R4={{[^)]*}}var_off=(0x0; 0xff)")
+__msg("18: {{.*}} R4={{[^)]*}}var_off=(0x0; 0x1fe0)")
+__msg("19: {{.*}} R4={{[^)]*}}var_off=(0x0; 0xff0)")
+__msg("20: {{.*}} R4={{[^)]*}}var_off=(0x0; 0x7f8)")
+__msg("21: {{.*}} R4={{[^)]*}}var_off=(0x0; 0x3fc)")
+__msg("22: {{.*}} R4={{[^)]*}}var_off=(0x0; 0x1fe)")
+__naked void unknown_shift(void)
+{
+ asm volatile (" \
+ " __LOAD_UNKNOWN("r3", "l99_%=") " \
+ r3 <<= 1; \
+ r3 <<= 1; \
+ r3 <<= 1; \
+ r3 <<= 1; \
+ " __LOAD_UNKNOWN("r4", "l98_%=") " \
+ r4 <<= 5; \
+ r4 >>= 1; \
+ r4 >>= 1; \
+ r4 >>= 1; \
+ r4 >>= 1; \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+ __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end))
+ : __clobber_all);
+}
+
+SEC("tc")
+__success __log_level(2)
+__flag(BPF_F_ANY_ALIGNMENT)
+__msg("6: {{.*}} R3={{[^)]*}}var_off=(0x0; 0xff)")
+__msg("7: {{.*}} R4={{[^)]*}}var_off=(0x0; 0xff)")
+__msg("8: {{.*}} R4={{[^)]*}}var_off=(0x0; 0xff)")
+__msg("9: {{.*}} R4={{[^)]*}}var_off=(0x0; 0xff)")
+__msg("10: {{.*}} R4={{[^)]*}}var_off=(0x0; 0x1fe)")
+__msg("11: {{.*}} R4={{[^)]*}}var_off=(0x0; 0xff)")
+__msg("12: {{.*}} R4={{[^)]*}}var_off=(0x0; 0x3fc)")
+__msg("13: {{.*}} R4={{[^)]*}}var_off=(0x0; 0xff)")
+__msg("14: {{.*}} R4={{[^)]*}}var_off=(0x0; 0x7f8)")
+__msg("15: {{.*}} R4={{[^)]*}}var_off=(0x0; 0xff0)")
+__naked void unknown_mul(void)
+{
+ asm volatile (" \
+ " LOAD_UNKNOWN("r3") " \
+ r4 = r3; \
+ r4 *= 1; \
+ r4 = r3; \
+ r4 *= 2; \
+ r4 = r3; \
+ r4 *= 4; \
+ r4 = r3; \
+ r4 *= 8; \
+ r4 *= 2; \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+ __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end))
+ : __clobber_all);
+}
+
+SEC("tc")
+__success __log_level(2)
+__msg("2: {{.*}} R5=pkt(r=0)")
+__msg("4: {{.*}} R5=pkt(r=0,imm=14)")
+__msg("5: {{.*}} R4=pkt(r=0,imm=14)")
+__msg("9: {{.*}} R5=pkt(r=18,imm=14)")
+__msg("10: {{.*}} R4={{[^)]*}}var_off=(0x0; 0xff){{.*}} R5=pkt(r=18,imm=14)")
+__msg("13: {{.*}} R4={{[^)]*}}var_off=(0x0; 0xffff)")
+__msg("14: {{.*}} R4={{[^)]*}}var_off=(0x0; 0xffff)")
+__naked void packet_const_offset(void)
+{
+ asm volatile (" \
+ " PREP_PKT_POINTERS " \
+ r5 = r2; \
+ r0 = 0; \
+ /* Skip over ethernet header. */ \
+ r5 += 14; \
+ r4 = r5; \
+ r4 += 4; \
+ if r3 >= r4 goto l0_%=; \
+ exit; \
+l0_%=: r4 = *(u8*)(r5 + 0); \
+ r4 = *(u8*)(r5 + 1); \
+ r4 = *(u8*)(r5 + 2); \
+ r4 = *(u8*)(r5 + 3); \
+ r4 = *(u16*)(r5 + 0); \
+ r4 = *(u16*)(r5 + 2); \
+ r4 = *(u32*)(r5 + 0); \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+ __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end))
+ : __clobber_all);
+}
+
+SEC("tc")
+__success __log_level(2)
+__flag(BPF_F_ANY_ALIGNMENT)
+/* Calculated offset in R6 has unknown value, but known
+ * alignment of 4.
+ */
+__msg("6: {{.*}} R2=pkt(r=8)")
+__msg("7: {{.*}} R6={{[^)]*}}var_off=(0x0; 0x3fc)")
+/* Offset is added to packet pointer R5, resulting in
+ * known fixed offset, and variable offset from R6.
+ */
+__msg("11: {{.*}} R5=pkt(id=1,{{[^)]*}},var_off=(0x2; 0x7fc)")
+/* At the time the word size load is performed from R5,
+ * it's total offset is NET_IP_ALIGN + reg->off (0) +
+ * reg->aux_off (14) which is 16. Then the variable
+ * offset is considered using reg->aux_off_align which
+ * is 4 and meets the load's requirements.
+ */
+__msg("15: {{.*}} R5={{[^)]*}}var_off=(0x2; 0x7fc)")
+/* Variable offset is added to R5 packet pointer,
+ * resulting in auxiliary alignment of 4. To avoid BPF
+ * verifier's precision backtracking logging
+ * interfering we also have a no-op R4 = R5
+ * instruction to validate R5 state. We also check
+ * that R4 is what it should be in such case.
+ */
+__msg("18: {{.*}} R4={{[^)]*}}var_off=(0x0; 0x3fc){{.*}} R5={{[^)]*}}var_off=(0x0; 0x3fc)")
+/* Constant offset is added to R5, resulting in
+ * reg->off of 14.
+ */
+__msg("19: {{.*}} R5=pkt(id=2,{{[^)]*}}var_off=(0x2; 0x7fc)")
+/* At the time the word size load is performed from R5,
+ * its total fixed offset is NET_IP_ALIGN + reg->off
+ * (14) which is 16. Then the variable offset is 4-byte
+ * aligned, so the total offset is 4-byte aligned and
+ * meets the load's requirements.
+ */
+__msg("24: {{.*}} R5={{[^)]*}}var_off=(0x2; 0x7fc)")
+/* Constant offset is added to R5 packet pointer,
+ * resulting in reg->off value of 14.
+ */
+__msg("26: {{.*}} R5=pkt(r=8,imm=14)")
+/* Variable offset is added to R5, resulting in a
+ * variable offset of (4n). See comment for insn #18
+ * for R4 = R5 trick.
+ */
+__msg("28: {{.*}} R4={{[^)]*}}var_off=(0x2; 0x7fc){{.*}} R5={{[^)]*}}var_off=(0x2; 0x7fc)")
+/* Constant is added to R5 again, setting reg->off to 18. */
+__msg("29: {{.*}} R5=pkt(id=3,{{[^)]*}}var_off=(0x2; 0x7fc)")
+/* And once more we add a variable; resulting {{[^)]*}}var_off
+ * is still (4n), fixed offset is not changed.
+ * Also, we create a new reg->id.
+ */
+__msg("31: {{.*}} R4={{[^)]*}}var_off=(0x2; 0xffc){{.*}} R5={{[^)]*}}var_off=(0x2; 0xffc)")
+/* At the time the word size load is performed from R5,
+ * its total fixed offset is NET_IP_ALIGN + reg->off (18)
+ * which is 20. Then the variable offset is (4n), so
+ * the total offset is 4-byte aligned and meets the
+ * load's requirements.
+ */
+__msg("35: {{.*}} R5={{[^)]*}}var_off=(0x2; 0xffc)")
+__naked void packet_variable_offset(void)
+{
+ asm volatile (" \
+ " LOAD_UNKNOWN("r6") " \
+ r6 <<= 2; \
+ /* First, add a constant to the R5 packet pointer,\
+ * then a variable with a known alignment. \
+ */ \
+ r5 = r2; \
+ r5 += 14; \
+ r5 += r6; \
+ r4 = r5; \
+ r4 += 4; \
+ if r3 >= r4 goto l0_%=; \
+ exit; \
+l0_%=: r4 = *(u32*)(r5 + 0); \
+ /* Now, test in the other direction. Adding first\
+ * the variable offset to R5, then the constant.\
+ */ \
+ r5 = r2; \
+ r5 += r6; \
+ r4 = r5; \
+ r5 += 14; \
+ r4 = r5; \
+ r4 += 4; \
+ if r3 >= r4 goto l1_%=; \
+ exit; \
+l1_%=: r4 = *(u32*)(r5 + 0); \
+ /* Test multiple accumulations of unknown values\
+ * into a packet pointer. \
+ */ \
+ r5 = r2; \
+ r5 += 14; \
+ r5 += r6; \
+ r4 = r5; \
+ r5 += 4; \
+ r5 += r6; \
+ r4 = r5; \
+ r4 += 4; \
+ if r3 >= r4 goto l2_%=; \
+ exit; \
+l2_%=: r4 = *(u32*)(r5 + 0); \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+ __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end))
+ : __clobber_all);
+}
+
+SEC("tc")
+__success __log_level(2)
+__flag(BPF_F_ANY_ALIGNMENT)
+/* Calculated offset in R6 has unknown value, but known
+ * alignment of 4.
+ */
+__msg("6: {{.*}} R2=pkt(r=8)")
+__msg("7: {{.*}} R6={{[^)]*}}var_off=(0x0; 0x3fc)")
+/* Adding 14 makes R6 be (4n+2) */
+__msg("8: {{.*}} R6={{[^)]*}}var_off=(0x2; 0x7fc)")
+/* Packet pointer has (4n+2) offset */
+__msg("11: {{.*}} R5={{[^)]*}}var_off=(0x2; 0x7fc)")
+__msg("12: {{.*}} R4={{[^)]*}}var_off=(0x2; 0x7fc)")
+/* At the time the word size load is performed from R5,
+ * its total fixed offset is NET_IP_ALIGN + reg->off (0)
+ * which is 2. Then the variable offset is (4n+2), so
+ * the total offset is 4-byte aligned and meets the
+ * load's requirements.
+ */
+__msg("15: {{.*}} R5={{[^)]*}}var_off=(0x2; 0x7fc)")
+/* Newly read value in R6 was shifted left by 2, so has
+ * known alignment of 4.
+ */
+__msg("17: {{.*}} R6={{[^)]*}}var_off=(0x0; 0x3fc)")
+/* Added (4n) to packet pointer's (4n+2) {{[^)]*}}var_off, giving
+ * another (4n+2).
+ */
+__msg("19: {{.*}} R5={{[^)]*}}var_off=(0x2; 0xffc)")
+__msg("20: {{.*}} R4={{[^)]*}}var_off=(0x2; 0xffc)")
+/* At the time the word size load is performed from R5,
+ * its total fixed offset is NET_IP_ALIGN + reg->off (0)
+ * which is 2. Then the variable offset is (4n+2), so
+ * the total offset is 4-byte aligned and meets the
+ * load's requirements.
+ */
+__msg("23: {{.*}} R5={{[^)]*}}var_off=(0x2; 0xffc)")
+__naked void packet_variable_offset_2(void)
+{
+ asm volatile (" \
+ /* Create an unknown offset, (4n+2)-aligned */ \
+ " LOAD_UNKNOWN("r6") " \
+ r6 <<= 2; \
+ r6 += 14; \
+ /* Add it to the packet pointer */ \
+ r5 = r2; \
+ r5 += r6; \
+ /* Check bounds and perform a read */ \
+ r4 = r5; \
+ r4 += 4; \
+ if r3 >= r4 goto l0_%=; \
+ exit; \
+l0_%=: r6 = *(u32*)(r5 + 0); \
+ /* Make a (4n) offset from the value we just read */\
+ r6 &= 0xff; \
+ r6 <<= 2; \
+ /* Add it to the packet pointer */ \
+ r5 += r6; \
+ /* Check bounds and perform a read */ \
+ r4 = r5; \
+ r4 += 4; \
+ if r3 >= r4 goto l1_%=; \
+ exit; \
+l1_%=: r6 = *(u32*)(r5 + 0); \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+ __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end))
+ : __clobber_all);
+}
+
+SEC("tc")
+__failure __log_level(2)
+__msg("3: {{.*}} R5=pkt_end()")
+/* (ptr - ptr) << 2 == unknown, (4n) */
+__msg("5: {{.*}} R5={{[^)]*}}var_off=(0x0; 0xfffffffffffffffc)")
+/* (4n) + 14 == (4n+2). We blow our bounds, because
+ * the add could overflow.
+ */
+__msg("6: {{.*}} R5={{[^)]*}}var_off=(0x2; 0xfffffffffffffffc)")
+/* Checked s>=0 */
+__msg("9: {{.*}} R5={{[^)]*}}var_off=(0x2; 0x7ffffffffffffffc)")
+/* packet pointer + nonnegative (4n+2) */
+__msg("11: {{.*}} R4={{[^)]*}}var_off=(0x2; 0x7ffffffffffffffc){{.*}} R6={{[^)]*}}var_off=(0x2; 0x7ffffffffffffffc)")
+__msg("12: (07) r4 += 4")
+/* packet smax bound overflow */
+__msg("pkt pointer offset -9223372036854775808 is not allowed")
+__naked void dubious_pointer_arithmetic(void)
+{
+ asm volatile (" \
+ " PREP_PKT_POINTERS " \
+ r0 = 0; \
+ /* (ptr - ptr) << 2 */ \
+ r5 = r3; \
+ r5 -= r2; \
+ r5 <<= 2; \
+ /* We have a (4n) value. Let's make a packet offset\
+ * out of it. First add 14, to make it a (4n+2)\
+ */ \
+ r5 += 14; \
+ /* Then make sure it's nonnegative */ \
+ if r5 s>= 0 goto l0_%=; \
+ exit; \
+l0_%=: /* Add it to packet pointer */ \
+ r6 = r2; \
+ r6 += r5; \
+ /* Check bounds and perform a read */ \
+ r4 = r6; \
+ r4 += 4; \
+ if r3 >= r4 goto l1_%=; \
+ exit; \
+l1_%=: r4 = *(u32*)(r6 + 0); \
+ exit; \
+" :
+ : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+ __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end))
+ : __clobber_all);
+}
+
+SEC("tc")
+__success __log_level(2)
+__flag(BPF_F_ANY_ALIGNMENT)
+/* Calculated offset in R6 has unknown value, but known
+ * alignment of 4.
+ */
+__msg("6: {{.*}} R2=pkt(r=8)")
+__msg("8: {{.*}} R6={{[^)]*}}var_off=(0x0; 0x3fc)")
+/* Adding 14 makes R6 be (4n+2) */
+__msg("9: {{.*}} R6={{[^)]*}}var_off=(0x2; 0x7fc)")
+/* New unknown value in R7 is (4n) */
+__msg("10: {{.*}} R7={{[^)]*}}var_off=(0x0; 0x3fc)")
+/* Subtracting it from R6 blows our unsigned bounds */
+__msg("11: {{.*}} R6={{[^)]*}}var_off=(0x2; 0xfffffffffffffffc)")
+/* Checked s>= 0 */
+__msg("14: {{.*}} R6={{[^)]*}}var_off=(0x2; 0x7fc)")
+/* At the time the word size load is performed from R5,
+ * its total fixed offset is NET_IP_ALIGN + reg->off (0)
+ * which is 2. Then the variable offset is (4n+2), so
+ * the total offset is 4-byte aligned and meets the
+ * load's requirements.
+ */
+__msg("20: {{.*}} R5={{[^)]*}}var_off=(0x2; 0x7fc)")
+__naked void variable_subtraction(void)
+{
+ asm volatile (" \
+ /* Create an unknown offset, (4n+2)-aligned */ \
+ " LOAD_UNKNOWN("r6") " \
+ r7 = r6; \
+ r6 <<= 2; \
+ r6 += 14; \
+ /* Create another unknown, (4n)-aligned, and subtract\
+ * it from the first one \
+ */ \
+ r7 <<= 2; \
+ r6 -= r7; \
+ /* Bounds-check the result */ \
+ if r6 s>= 0 goto l0_%=; \
+ exit; \
+l0_%=: /* Add it to the packet pointer */ \
+ r5 = r2; \
+ r5 += r6; \
+ /* Check bounds and perform a read */ \
+ r4 = r5; \
+ r4 += 4; \
+ if r3 >= r4 goto l1_%=; \
+ exit; \
+l1_%=: r6 = *(u32*)(r5 + 0); \
+ exit; \
+" :
+ : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+ __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end))
+ : __clobber_all);
+}
+
+SEC("tc")
+__success __log_level(2)
+__flag(BPF_F_ANY_ALIGNMENT)
+/* Calculated offset in R6 has unknown value, but known
+ * alignment of 4.
+ */
+__msg("6: {{.*}} R2=pkt(r=8)")
+__msg("9: {{.*}} R6={{[^)]*}}var_off=(0x0; 0x3c)")
+/* Adding 14 makes R6 be (4n+2) */
+__msg("10: {{.*}} R6={{[^)]*}}var_off=(0x2; 0x7c)")
+/* Subtracting from packet pointer overflows ubounds */
+__msg("13: R5={{[^)]*}}var_off=(0xffffffffffffff82; 0x7c)")
+/* New unknown value in R7 is (4n), >= 76 */
+__msg("14: {{.*}} R7={{[^)]*}}var_off=(0x0; 0x7fc)")
+/* Adding it to packet pointer gives nice bounds again */
+__msg("16: {{.*}} R5={{[^)]*}}var_off=(0x2; 0x7fc)")
+/* At the time the word size load is performed from R5,
+ * its total fixed offset is NET_IP_ALIGN + reg->off (0)
+ * which is 2. Then the variable offset is (4n+2), so
+ * the total offset is 4-byte aligned and meets the
+ * load's requirements.
+ */
+__msg("20: {{.*}} R5={{[^)]*}}var_off=(0x2; 0x7fc)")
+__naked void pointer_variable_subtraction(void)
+{
+ asm volatile (" \
+ /* Create an unknown offset, (4n+2)-aligned and bounded\
+ * to [14,74] \
+ */ \
+ " LOAD_UNKNOWN("r6") " \
+ r7 = r6; \
+ r6 &= 0xf; \
+ r6 <<= 2; \
+ r6 += 14; \
+ /* Subtract it from the packet pointer */ \
+ r5 = r2; \
+ r5 -= r6; \
+ /* Create another unknown, (4n)-aligned and >= 74.\
+ * That in fact means >= 76, since 74 mod 4 == 2\
+ */ \
+ r7 <<= 2; \
+ r7 += 76; \
+ /* Add it to the packet pointer */ \
+ r5 += r7; \
+ /* Check bounds and perform a read */ \
+ r4 = r5; \
+ r4 += 4; \
+ if r3 >= r4 goto l0_%=; \
+ exit; \
+l0_%=: r6 = *(u32*)(r5 + 0); \
+ exit; \
+" :
+ : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+ __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end))
+ : __clobber_all);
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_arena.c b/tools/testing/selftests/bpf/progs/verifier_arena.c
index 7f4827eede3c..3e33766547c0 100644
--- a/tools/testing/selftests/bpf/progs/verifier_arena.c
+++ b/tools/testing/selftests/bpf/progs/verifier_arena.c
@@ -8,19 +8,54 @@
#include <bpf/bpf_tracing.h>
#include "bpf_misc.h"
#include "bpf_experimental.h"
-#include "bpf_arena_common.h"
+#include <bpf_arena_common.h>
+
+#define private(name) SEC(".bss." #name) __hidden __attribute__((aligned(8)))
+
+#ifdef __TARGET_ARCH_arm64
+#define ARENA_VM_START ((1ull << 32) | (~0u - __PAGE_SIZE * 2 + 1))
+#else
+#define ARENA_VM_START ((1ull << 44) | (~0u - __PAGE_SIZE * 2 + 1))
+#endif
struct {
__uint(type, BPF_MAP_TYPE_ARENA);
__uint(map_flags, BPF_F_MMAPABLE);
__uint(max_entries, 2); /* arena of two pages close to 32-bit boundary*/
-#ifdef __TARGET_ARCH_arm64
- __ulong(map_extra, (1ull << 32) | (~0u - __PAGE_SIZE * 2 + 1)); /* start of mmap() region */
-#else
- __ulong(map_extra, (1ull << 44) | (~0u - __PAGE_SIZE * 2 + 1)); /* start of mmap() region */
-#endif
+ __ulong(map_extra, ARENA_VM_START); /* start of mmap() region */
} arena SEC(".maps");
+SEC("socket")
+__success __retval(0)
+int basic_alloc1_nosleep(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ volatile int __arena *page1, *page2, *no_page;
+
+ page1 = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!page1)
+ return 1;
+ *page1 = 1;
+ page2 = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!page2)
+ return 2;
+ *page2 = 2;
+ no_page = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (no_page)
+ return 3;
+ if (*page1 != 1)
+ return 4;
+ if (*page2 != 2)
+ return 5;
+ bpf_arena_free_pages(&arena, (void __arena *)page2, 1);
+ if (*page1 != 1)
+ return 6;
+ if (*page2 != 0 && *page2 != 2) /* use-after-free should return 0 or the stored value */
+ return 7;
+#endif
+ return 0;
+}
+
SEC("syscall")
__success __retval(0)
int basic_alloc1(void *ctx)
@@ -62,6 +97,72 @@ int basic_alloc1(void *ctx)
SEC("syscall")
__success __retval(0)
+int free_scalar_below_arena(void *ctx)
+{
+ void __arena *page1, *page2, *page3;
+ __u64 bad_addr = ARENA_VM_START - __PAGE_SIZE;
+
+ page1 = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!page1)
+ return 1;
+
+ page2 = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!page2)
+ return 2;
+
+ page3 = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (page3)
+ return 3;
+
+ bpf_arena_free_pages(&arena, (void __arena *)bad_addr, 1);
+
+ page3 = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (page3)
+ return 4;
+
+ return 0;
+}
+
+SEC("socket")
+__success __retval(0)
+int basic_alloc2_nosleep(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ volatile char __arena *page1, *page2, *page3, *page4;
+
+ page1 = bpf_arena_alloc_pages(&arena, NULL, 2, NUMA_NO_NODE, 0);
+ if (!page1)
+ return 1;
+ page2 = page1 + __PAGE_SIZE;
+ page3 = page1 + __PAGE_SIZE * 2;
+ page4 = page1 - __PAGE_SIZE;
+ *page1 = 1;
+ *page2 = 2;
+ *page3 = 3;
+ *page4 = 4;
+ if (*page1 != 1)
+ return 1;
+ if (*page2 != 2)
+ return 2;
+ if (*page3 != 0)
+ return 3;
+ if (*page4 != 0)
+ return 4;
+ bpf_arena_free_pages(&arena, (void __arena *)page1, 2);
+ if (*page1 != 0 && *page1 != 1)
+ return 5;
+ if (*page2 != 0 && *page2 != 2)
+ return 6;
+ if (*page3 != 0)
+ return 7;
+ if (*page4 != 0)
+ return 8;
+#endif
+ return 0;
+}
+
+SEC("syscall")
+__success __retval(0)
int basic_alloc2(void *ctx)
{
#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
@@ -102,6 +203,19 @@ struct bpf_arena___l {
struct bpf_map map;
} __attribute__((preserve_access_index));
+SEC("socket")
+__success __retval(0) __log_level(2)
+int basic_alloc3_nosleep(void *ctx)
+{
+ struct bpf_arena___l *ar = (struct bpf_arena___l *)&arena;
+ volatile char __arena *pages;
+
+ pages = bpf_arena_alloc_pages(&ar->map, NULL, ar->map.max_entries, NUMA_NO_NODE, 0);
+ if (!pages)
+ return 1;
+ return 0;
+}
+
SEC("syscall")
__success __retval(0) __log_level(2)
int basic_alloc3(void *ctx)
@@ -115,6 +229,38 @@ int basic_alloc3(void *ctx)
return 0;
}
+SEC("socket")
+__success __retval(0)
+int basic_reserve1_nosleep(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ char __arena *page;
+ int ret;
+
+ page = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!page)
+ return 1;
+
+ page += __PAGE_SIZE;
+
+ /* Reserve the second page */
+ ret = bpf_arena_reserve_pages(&arena, page, 1);
+ if (ret)
+ return 2;
+
+ /* Try to explicitly allocate the reserved page. */
+ page = bpf_arena_alloc_pages(&arena, page, 1, NUMA_NO_NODE, 0);
+ if (page)
+ return 3;
+
+ /* Try to implicitly allocate the page (since there's only 2 of them). */
+ page = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (page)
+ return 4;
+#endif
+ return 0;
+}
+
SEC("syscall")
__success __retval(0)
int basic_reserve1(void *ctx)
@@ -147,6 +293,26 @@ int basic_reserve1(void *ctx)
return 0;
}
+SEC("socket")
+__success __retval(0)
+int basic_reserve2_nosleep(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ char __arena *page;
+ int ret;
+
+ page = arena_base(&arena);
+ ret = bpf_arena_reserve_pages(&arena, page, 1);
+ if (ret)
+ return 1;
+
+ page = bpf_arena_alloc_pages(&arena, page, 1, NUMA_NO_NODE, 0);
+ if ((u64)page)
+ return 2;
+#endif
+ return 0;
+}
+
SEC("syscall")
__success __retval(0)
int basic_reserve2(void *ctx)
@@ -168,6 +334,27 @@ int basic_reserve2(void *ctx)
}
/* Reserve the same page twice, should return -EBUSY. */
+SEC("socket")
+__success __retval(0)
+int reserve_twice_nosleep(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ char __arena *page;
+ int ret;
+
+ page = arena_base(&arena);
+
+ ret = bpf_arena_reserve_pages(&arena, page, 1);
+ if (ret)
+ return 1;
+
+ ret = bpf_arena_reserve_pages(&arena, page, 1);
+ if (ret != -EBUSY)
+ return 2;
+#endif
+ return 0;
+}
+
SEC("syscall")
__success __retval(0)
int reserve_twice(void *ctx)
@@ -190,6 +377,36 @@ int reserve_twice(void *ctx)
}
/* Try to reserve past the end of the arena. */
+SEC("socket")
+__success __retval(0)
+int reserve_invalid_region_nosleep(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ char __arena *page;
+ int ret;
+
+ /* Try a NULL pointer. */
+ ret = bpf_arena_reserve_pages(&arena, NULL, 3);
+ if (ret != -EINVAL)
+ return 1;
+
+ page = arena_base(&arena);
+
+ ret = bpf_arena_reserve_pages(&arena, page, 3);
+ if (ret != -EINVAL)
+ return 2;
+
+ ret = bpf_arena_reserve_pages(&arena, page, 4096);
+ if (ret != -EINVAL)
+ return 3;
+
+ ret = bpf_arena_reserve_pages(&arena, page, (1ULL << 32) - 1);
+ if (ret != -EINVAL)
+ return 4;
+#endif
+ return 0;
+}
+
SEC("syscall")
__success __retval(0)
int reserve_invalid_region(void *ctx)
@@ -254,4 +471,316 @@ int iter_maps3(struct bpf_iter__bpf_map *ctx)
return 0;
}
+private(ARENA_TESTS) struct bpf_spin_lock arena_bpf_test_lock;
+
+/* Use the arena kfunc API while under a BPF lock. */
+SEC("syscall")
+__success __retval(0)
+int arena_kfuncs_under_bpf_lock(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ char __arena *page;
+ int ret;
+
+ bpf_spin_lock(&arena_bpf_test_lock);
+
+ /* Get a separate region of the arena. */
+ page = arena_base(&arena);
+ ret = bpf_arena_reserve_pages(&arena, page, 1);
+ if (ret) {
+ bpf_spin_unlock(&arena_bpf_test_lock);
+ return 1;
+ }
+
+ bpf_arena_free_pages(&arena, page, 1);
+
+ page = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!page) {
+ bpf_spin_unlock(&arena_bpf_test_lock);
+ return 2;
+ }
+
+ bpf_arena_free_pages(&arena, page, 1);
+
+ bpf_spin_unlock(&arena_bpf_test_lock);
+#endif
+
+ return 0;
+}
+
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+
+/*
+ * Test that scalar += PTR_TO_ARENA correctly upgrades the
+ * destination register to a PTR_TO_ARENA.
+ */
+SEC("syscall")
+__success __retval(0)
+int scalar_add_arena_ptr(void *ctx)
+{
+ int __arena *scalar, *arena_ptr;
+
+ volatile char __arena *base = arena_base(&arena);
+
+ asm volatile (
+ "%[arena_ptr] = 8192;"
+ "%[arena_ptr] = addr_space_cast(%[arena_ptr], 0x0, 0x1);"
+ "%[scalar] = 12;"
+ "%[scalar] += %[arena_ptr];"
+ : [scalar] "=r"(scalar),
+ [arena_ptr] "=&r"(arena_ptr)
+ : "r"(base)
+ :
+ );
+ return 0;
+}
+
+/*
+ * Tests that PTR_TO_ARENA + PTR_TO_ARENA is allowed.
+ */
+SEC("syscall")
+__success __retval(0)
+int arena_ptr_add_arena_ptr(void *ctx)
+{
+ int __arena *arena_ptr2, *arena_ptr1;
+
+ /* Needed for the verifier to link the arena to the subprog. */
+ volatile char __arena *base = arena_base(&arena);
+
+ asm volatile (
+ "%[arena_ptr1] = 8192;"
+ "%[arena_ptr1] = addr_space_cast(%[arena_ptr1], 0x0, 0x1);"
+ "%[arena_ptr2] = 4096;"
+ "%[arena_ptr2] = addr_space_cast(%[arena_ptr2], 0x0, 0x1);"
+ "%[arena_ptr2] += %[arena_ptr1];"
+ : [arena_ptr2] "=r"(arena_ptr2),
+ [arena_ptr1] "=&r"(arena_ptr1)
+ : "r"(base)
+ :
+ );
+ return 0;
+}
+
+SEC("syscall")
+__failure __msg("same insn cannot be used with and without arena pointer")
+int mixed_arena_scalar_alu64_scalar_first(void *ctx)
+{
+ volatile register __u64 reg asm("r3");
+ __u32 pick_arena = bpf_get_prandom_u32();
+
+ reg = 1ULL << 32;
+
+ if (pick_arena) {
+ asm volatile (
+ "r9 = %[arena] ll;"
+ "%[reg] = 0;"
+ "%[reg] = addr_space_cast(%[reg], 0x0, 0x1);"
+ : [reg] "=r"(reg)
+ : __imm_addr(arena)
+ : "r9"
+ );
+ }
+
+ reg += 1;
+
+ return 0;
+}
+
+SEC("syscall")
+__failure __msg("same insn cannot be used with and without arena pointer")
+int mixed_arena_scalar_alu64_arena_first(void *ctx)
+{
+ volatile register __u64 reg asm("r3");
+ __u32 pick_scalar = bpf_get_prandom_u32();
+
+ asm volatile (
+ "r9 = %[arena] ll;"
+ "%[reg] = 0;"
+ "%[reg] = addr_space_cast(%[reg], 0x0, 0x1);"
+ : [reg] "=r"(reg)
+ : __imm_addr(arena)
+ : "r9"
+ );
+
+ if (pick_scalar)
+ reg = 1ULL << 32;
+
+ reg += 1;
+
+ return 0;
+}
+
+SEC("syscall")
+__success __retval(0)
+int scalar_xor_arena_ptr(void *ctx)
+{
+ int __arena *scalar, *arena_ptr;
+
+ volatile char __arena *base = arena_base(&arena);
+
+ asm volatile (
+ "%[arena_ptr] = 8192;"
+ "%[arena_ptr] = addr_space_cast(%[arena_ptr], 0x0, 0x1);"
+ "%[scalar] = 12;"
+ "%[scalar] ^= %[arena_ptr];"
+ : [scalar] "=r"(scalar),
+ [arena_ptr] "=&r"(arena_ptr)
+ : "r"(base)
+ :
+ );
+ return 0;
+}
+
+/*
+ * Tests that PTR_TO_ARENA and non-arena pointers can be added.
+ */
+SEC("syscall")
+__success __retval(0)
+int arena_ptr_add_to_non_arena_ptr(void *ctx)
+{
+ register int __arena *arena_ptr asm("r3");
+ register void *dst asm("r4");
+
+ volatile char __arena *base = arena_base(&arena);
+
+ asm volatile (
+ "%[arena_ptr] = 8192;"
+ "%[arena_ptr] = addr_space_cast(%[arena_ptr], 0x0, 0x1);"
+ "%[dst] = %[ctx];"
+ "%[dst] += %[arena_ptr];"
+ : [arena_ptr] "=&r"(arena_ptr),
+ [dst] "=&r"(dst)
+ : [ctx] "r"(ctx), "r"(base)
+ :
+ );
+
+ (void)ctx;
+
+ return 0;
+}
+
+SEC("syscall")
+__success __retval(0)
+int non_arena_ptr_add_to_arena_ptr(void *ctx)
+{
+ register int __arena *arena_ptr asm("r3");
+ register void *src asm("r4");
+
+ volatile char __arena *base = arena_base(&arena);
+
+ asm volatile (
+ "%[arena_ptr] = 8192;"
+ "%[arena_ptr] = addr_space_cast(%[arena_ptr], 0x0, 0x1);"
+ "%[src] = %[ctx];"
+ "%[arena_ptr] += %[src];"
+ : [arena_ptr] "=&r"(arena_ptr),
+ [src] "=&r"(src)
+ : [ctx] "r"(ctx), "r"(base)
+ :
+ );
+
+ (void)ctx;
+
+ return 0;
+}
+
+SEC("socket")
+__description("arena and stack atomic at the same instruction")
+__failure __msg("same insn cannot be used with different pointers")
+__arch_x86_64
+__load_if_JITed()
+__naked void mixed_arena_stack_atomic(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r6 = r10; \
+ r6 += -8; \
+ r9 = 0; \
+ *(u64 *)(r6 + 0) = r9; \
+ r7 = 8192; \
+ r7 = addr_space_cast(r7, 0, 1); \
+ call %[bpf_get_prandom_u32]; \
+ if w0 != 0 goto 1f; \
+ r8 = r6; \
+ goto 2f; \
+1: r8 = r7; \
+2: r9 = 1; \
+ lock *(u64 *)(r8 + 0) += r9; \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+#endif /* defined(__BPF_FEATURE_ADDR_SPACE_CAST) */
+
+static __noinline
+u32 __arena *check_arena_arg_nonglobal(u32 __arena *arg)
+{
+ volatile u32 val = *arg;
+
+ *arg = val + 1;
+
+ return arg;
+}
+
+__weak
+u32 __arena *check_arena_arg_global(u32 __arena *arg)
+{
+ volatile u32 val = *arg;
+
+ *arg = val + 1;
+
+ return arg;
+}
+
+__weak
+u32 volatile __arena *check_arena_arg_quals1(u32 volatile __arena *arg1, u32 __arena volatile *arg2)
+{
+ *arg1 = *arg1 + 1;
+ *arg2 = *arg1 + 1;
+
+ return arg2;
+}
+
+__weak
+u32 __arena volatile *check_arena_arg_quals2(u32 volatile __arena *arg1, u32 __arena volatile *arg2)
+{
+ *arg1 = *arg1 + 1;
+ *arg2 = *arg2 + 1;
+
+ return arg2;
+}
+
+SEC("syscall")
+__success __retval(0)
+int check_arena_arg_ret(void *ctx)
+{
+ u32 __arena *page = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ u32 __arena *arg = page;
+ u32 __arena volatile *arg1;
+ u32 __arena volatile *ret1;
+ u32 volatile __arena *arg2;
+ u32 volatile __arena *ret2;
+
+ if (!arg)
+ return 1;
+
+ /* Make sure we use {arg, ret}{1, 2}. */
+
+ arg = check_arena_arg_nonglobal(page);
+ arg = check_arena_arg_global(arg);
+
+ arg1 = arg2 = page;
+ ret1 = check_arena_arg_quals1(arg1, arg2);
+ ret2 = check_arena_arg_quals2(arg1, arg2);
+
+ if (!(*ret1 ||*ret2))
+ return -EINVAL;
+
+ return 0;
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_arena_globals1.c b/tools/testing/selftests/bpf/progs/verifier_arena_globals1.c
new file mode 100644
index 000000000000..45d364b0bc85
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_arena_globals1.c
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
+
+#define BPF_NO_KFUNC_PROTOTYPES
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_experimental.h"
+#include <bpf_arena_common.h>
+#include "bpf_misc.h"
+
+#define ARENA_PAGES (1UL<< (32 - __builtin_ffs(__PAGE_SIZE) + 1))
+#define GLOBAL_PAGES (16)
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARENA);
+ __uint(map_flags, BPF_F_MMAPABLE);
+ __uint(max_entries, ARENA_PAGES);
+#ifdef __TARGET_ARCH_arm64
+ __ulong(map_extra, (1ull << 32) | (~0u - __PAGE_SIZE * ARENA_PAGES + 1));
+#else
+ __ulong(map_extra, (1ull << 44) | (~0u - __PAGE_SIZE * ARENA_PAGES + 1));
+#endif
+} arena SEC(".maps");
+
+/*
+ * Global data, to be placed at the end of the arena.
+ */
+volatile char __arena global_data[GLOBAL_PAGES][PAGE_SIZE];
+
+SEC("syscall")
+__success __retval(0)
+int check_reserve1(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ const u8 magic = 0x5a;
+ __u8 __arena *guard, *globals;
+ volatile char __arena *ptr;
+ int i;
+ int ret;
+
+ guard = (void __arena *)arena_base(&arena);
+ globals = (void __arena *)(arena_base(&arena) + (ARENA_PAGES - GLOBAL_PAGES) * PAGE_SIZE);
+
+ /* Reserve the region we've offset the globals by. */
+ ret = bpf_arena_reserve_pages(&arena, guard, ARENA_PAGES - GLOBAL_PAGES);
+ if (ret)
+ return 1;
+
+ /* Make sure the globals are in the expected offset. */
+ ret = bpf_arena_reserve_pages(&arena, globals, 1);
+ if (!ret)
+ return 2;
+
+ /* Verify globals are properly mapped in by libbpf. */
+ for (i = 0; i < GLOBAL_PAGES; i++) {
+ ptr = &global_data[i][PAGE_SIZE / 2];
+
+ *ptr = magic;
+ if (*ptr != magic)
+ return i + 3;
+ }
+#endif
+ return 0;
+}
+
+/*
+ * Relocation check by reading directly into the global data w/o using symbols.
+ */
+SEC("syscall")
+__success __retval(0)
+int check_relocation(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ const u8 magic = 0xfa;
+ u8 __arena *ptr;
+
+ global_data[GLOBAL_PAGES - 1][PAGE_SIZE / 2] = magic;
+ ptr = (u8 __arena *)((u64)(ARENA_PAGES * PAGE_SIZE - PAGE_SIZE / 2));
+ if (*ptr != magic)
+ return 1;
+
+#endif
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_arena_globals2.c b/tools/testing/selftests/bpf/progs/verifier_arena_globals2.c
new file mode 100644
index 000000000000..b51594dbc005
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_arena_globals2.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
+
+#define BPF_NO_KFUNC_PROTOTYPES
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+#include "bpf_experimental.h"
+#include <bpf_arena_common.h>
+
+#define ARENA_PAGES (32)
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARENA);
+ __uint(map_flags, BPF_F_MMAPABLE);
+ __uint(max_entries, ARENA_PAGES);
+#ifdef __TARGET_ARCH_arm64
+ __ulong(map_extra, (1ull << 32) | (~0u - __PAGE_SIZE * ARENA_PAGES + 1));
+#else
+ __ulong(map_extra, (1ull << 44) | (~0u - __PAGE_SIZE * ARENA_PAGES + 1));
+#endif
+} arena SEC(".maps");
+
+/*
+ * Fill the entire arena with global data.
+ * The offset into the arena should be 0.
+ */
+char __arena global_data[ARENA_PAGES][PAGE_SIZE];
+
+SEC("syscall")
+__success __retval(0)
+int check_reserve2(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ void __arena *guard;
+ int ret;
+
+ guard = (void __arena *)arena_base(&arena);
+
+ /* Make sure the data at offset 0 case is properly handled. */
+ ret = bpf_arena_reserve_pages(&arena, guard, 1);
+ if (!ret)
+ return 1;
+#endif
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_arena_large.c b/tools/testing/selftests/bpf/progs/verifier_arena_large.c
index f19e15400b3e..6ab8730d4878 100644
--- a/tools/testing/selftests/bpf/progs/verifier_arena_large.c
+++ b/tools/testing/selftests/bpf/progs/verifier_arena_large.c
@@ -7,7 +7,7 @@
#include <bpf/bpf_tracing.h>
#include "bpf_misc.h"
#include "bpf_experimental.h"
-#include "bpf_arena_common.h"
+#include <bpf_arena_common.h>
#define ARENA_SIZE (1ull << 32)
@@ -23,18 +23,31 @@ int big_alloc1(void *ctx)
{
#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
volatile char __arena *page1, *page2, *no_page, *page3;
- void __arena *base;
+ u64 base;
- page1 = base = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ base = (u64)arena_base(&arena);
+
+ page1 = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
if (!page1)
return 1;
+
+ if ((u64)page1 != base)
+ return 15;
+
*page1 = 1;
- page2 = bpf_arena_alloc_pages(&arena, base + ARENA_SIZE - PAGE_SIZE * 2,
+ page2 = bpf_arena_alloc_pages(&arena, (void __arena *)(ARENA_SIZE - 2 * PAGE_SIZE),
1, NUMA_NO_NODE, 0);
if (!page2)
return 2;
*page2 = 2;
- no_page = bpf_arena_alloc_pages(&arena, base + ARENA_SIZE - PAGE_SIZE,
+
+ /* Test for the guard region at the end of the arena. */
+ no_page = bpf_arena_alloc_pages(&arena, (void __arena *)ARENA_SIZE - PAGE_SIZE,
+ 1, NUMA_NO_NODE, 0);
+ if (no_page)
+ return 16;
+
+ no_page = bpf_arena_alloc_pages(&arena, (void __arena *)ARENA_SIZE,
1, NUMA_NO_NODE, 0);
if (no_page)
return 3;
@@ -270,5 +283,34 @@ int big_alloc2(void *ctx)
return 9;
return 0;
}
+
+SEC("socket")
+__success __retval(0)
+int big_alloc3(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ char __arena *pages;
+ u64 i;
+
+ /*
+ * Allocate 2051 pages in one go to check how kmalloc_nolock() handles large requests.
+ * Since kmalloc_nolock() can allocate up to 1024 struct page * at a time, this call should
+ * result in three batches: two batches of 1024 pages each, followed by a final batch of 3
+ * pages.
+ */
+ pages = bpf_arena_alloc_pages(&arena, NULL, 2051, NUMA_NO_NODE, 0);
+ if (!pages)
+ return 0;
+
+ bpf_for(i, 0, 2051)
+ pages[i * PAGE_SIZE] = 123;
+ bpf_for(i, 0, 2051)
+ if (pages[i * PAGE_SIZE] != 123)
+ return i;
+
+ bpf_arena_free_pages(&arena, pages, 2051);
+#endif
+ return 0;
+}
#endif
char _license[] SEC("license") = "GPL";
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 7efa9521105e..1b653bfb63eb 100644
--- a/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c
+++ b/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c
@@ -9,6 +9,11 @@
char _license[] SEC("license") = "GPL";
+struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym;
+void bpf_task_release(struct task_struct *p) __ksym;
+void bpf_rcu_read_lock(void) __ksym;
+void bpf_rcu_read_unlock(void) __ksym;
+
/* Timer tests */
struct timer_elem {
@@ -31,7 +36,7 @@ static int timer_cb(void *map, int *key, struct bpf_timer *timer)
}
SEC("fentry/bpf_fentry_test1")
-__failure __msg("helper call might sleep in a non-sleepable prog")
+__failure __msg("sleepable helper bpf_copy_from_user#{{[0-9]+}} in non-sleepable prog")
int timer_non_sleepable_prog(void *ctx)
{
struct timer_elem *val;
@@ -47,7 +52,7 @@ int timer_non_sleepable_prog(void *ctx)
}
SEC("lsm.s/file_open")
-__failure __msg("helper call might sleep in a non-sleepable prog")
+__failure __msg("sleepable helper bpf_copy_from_user#{{[0-9]+}} in non-sleepable prog")
int timer_sleepable_prog(void *ctx)
{
struct timer_elem *val;
@@ -62,10 +67,109 @@ 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 {
struct bpf_wq w;
+ struct task_struct __kptr *task;
};
struct {
@@ -96,7 +200,7 @@ int wq_non_sleepable_prog(void *ctx)
if (bpf_wq_init(&val->w, &wq_map, 0) != 0)
return 0;
- if (bpf_wq_set_callback_impl(&val->w, wq_cb, 0, NULL) != 0)
+ if (bpf_wq_set_callback(&val->w, wq_cb, 0) != 0)
return 0;
return 0;
}
@@ -114,11 +218,111 @@ int wq_sleepable_prog(void *ctx)
if (bpf_wq_init(&val->w, &wq_map, 0) != 0)
return 0;
- if (bpf_wq_set_callback_impl(&val->w, wq_cb, 0, NULL) != 0)
+ if (bpf_wq_set_callback(&val->w, wq_cb, 0) != 0)
return 0;
return 0;
}
+__noinline int wq_global_acquire(void)
+{
+ struct task_struct *task, *acquired;
+ struct wq_elem *val;
+ int key = 0;
+
+ val = bpf_map_lookup_elem(&wq_map, &key);
+ if (!val)
+ return 0;
+
+ task = val->task;
+ if (!task)
+ return 0;
+
+ acquired = bpf_task_acquire(task);
+ if (acquired)
+ bpf_task_release(acquired);
+ return 0;
+}
+
+static int wq_global_rcu_cb(void *map, int *key, void *value)
+{
+ wq_global_acquire();
+ return 0;
+}
+
+SEC("fentry/bpf_fentry_test1")
+__failure __msg("R1 must be a rcu pointer")
+int wq_global_rcu_prog(void *ctx)
+{
+ struct wq_elem *val;
+ int key = 0;
+
+ val = bpf_map_lookup_elem(&wq_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_wq_init(&val->w, &wq_map, 0);
+ bpf_wq_set_callback(&val->w, wq_global_rcu_cb, 0);
+ return 0;
+}
+
+static int wq_global_rcu_lock_cb(void *map, int *key, void *value)
+{
+ bpf_rcu_read_lock();
+ wq_global_acquire();
+ bpf_rcu_read_unlock();
+ return 0;
+}
+
+SEC("fentry/bpf_fentry_test1")
+__success
+int wq_global_rcu_lock_prog(void *ctx)
+{
+ struct wq_elem *val;
+ int key = 0;
+
+ /* Verify the same global subprog in non-sleepable and protected contexts. */
+ wq_global_acquire();
+
+ val = bpf_map_lookup_elem(&wq_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_wq_init(&val->w, &wq_map, 0);
+ bpf_wq_set_callback(&val->w, wq_global_rcu_lock_cb, 0);
+ return 0;
+}
+
+__weak __noinline int wq_global_no_rcu(void)
+{
+ return 0;
+}
+
+static int wq_global_no_rcu_cb(void *map, int *key, void *value)
+{
+ wq_global_no_rcu();
+ return 0;
+}
+
+SEC("fentry/bpf_fentry_test1")
+__success __log_level(4)
+__msg("subprog {{[0-9]+}} (wq_global_no_rcu) global insns_self 4 insns_total 4 stack 0")
+int wq_global_no_rcu_prog(void *ctx)
+{
+ struct wq_elem *val;
+ int key = 0;
+
+ /* Verify the same global in non-sleepable and unprotected contexts. */
+ wq_global_no_rcu();
+
+ val = bpf_map_lookup_elem(&wq_map, &key);
+ if (!val)
+ return 0;
+
+ bpf_wq_init(&val->w, &wq_map, 0);
+ bpf_wq_set_callback(&val->w, wq_global_no_rcu_cb, 0);
+ return 0;
+}
+
/* Task work tests */
struct task_work_elem {
@@ -156,7 +360,7 @@ int task_work_non_sleepable_prog(void *ctx)
if (!task)
return 0;
- bpf_task_work_schedule_resume_impl(task, &val->tw, &task_work_map, task_work_cb, NULL);
+ bpf_task_work_schedule_resume(task, &val->tw, &task_work_map, task_work_cb);
return 0;
}
@@ -176,6 +380,6 @@ int task_work_sleepable_prog(void *ctx)
if (!task)
return 0;
- bpf_task_work_schedule_resume_impl(task, &val->tw, &task_work_map, task_work_cb, NULL);
+ bpf_task_work_schedule_resume(task, &val->tw, &task_work_map, task_work_cb);
return 0;
}
diff --git a/tools/testing/selftests/bpf/progs/verifier_basic_stack.c b/tools/testing/selftests/bpf/progs/verifier_basic_stack.c
index fb62e09f2114..0eb495ce85c1 100644
--- a/tools/testing/selftests/bpf/progs/verifier_basic_stack.c
+++ b/tools/testing/selftests/bpf/progs/verifier_basic_stack.c
@@ -27,7 +27,8 @@ __naked void stack_out_of_bounds(void)
SEC("socket")
__description("uninitialized stack1")
-__success __log_level(4) __msg("stack depth 8")
+__success __log_level(4)
+__msg("subprog 0 (uninitialized_stack1) main {{.*}} stack 8")
__failure_unpriv __msg_unpriv("invalid read from stack")
__naked void uninitialized_stack1(void)
{
@@ -45,7 +46,8 @@ __naked void uninitialized_stack1(void)
SEC("socket")
__description("uninitialized stack2")
-__success __log_level(4) __msg("stack depth 8")
+__success __log_level(4)
+__msg("subprog 0 (uninitialized_stack2) main insns_self {{[0-9]+}} insns_total {{[0-9]+}} stack 8")
__failure_unpriv __msg_unpriv("invalid read from stack")
__naked void uninitialized_stack2(void)
{
@@ -97,4 +99,45 @@ __naked void misaligned_read_from_stack(void)
" ::: __clobber_all);
}
+SEC("socket")
+__description("stack pointer arithmetic preserves frame number")
+__failure __msg("R7 invalid mem access 'scalar'")
+__naked void stack_ptr_arith_preserves_frameno(void)
+{
+ asm volatile ("\
+ r3 = 0; \
+ *(u64 *)(r10 - 8) = r3; \
+ r1 = %[map_hash_8b] ll; \
+ r2 = r10; \
+ r2 += -8; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 != 0 goto +2; \
+ r0 = 0; \
+ exit; \
+ r1 = r0; \
+ r2 = 0; \
+ r3 = 0; \
+ call stack_ptr_arith_preserves_frameno_subprog;\
+ r0 = 0; \
+ exit; \
+ ":
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_8b)
+ : __clobber_all);
+}
+
+static __used __naked void stack_ptr_arith_preserves_frameno_subprog(void)
+{
+ asm volatile ("\
+ *(u64 *)(r10 - 8) = r1; \
+ r6 = -8; \
+ r6 += r10; \
+ *(u64 *)(r6 + 0) = r2; \
+ r7 = *(u64 *)(r10 - 8); \
+ *(u64 *)(r7 + 0) = r3; \
+ r0 = 0; \
+ exit; \
+ "::: __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_bits_iter.c b/tools/testing/selftests/bpf/progs/verifier_bits_iter.c
index 8bcddadfc4da..dd97f2027505 100644
--- a/tools/testing/selftests/bpf/progs/verifier_bits_iter.c
+++ b/tools/testing/selftests/bpf/progs/verifier_bits_iter.c
@@ -32,7 +32,7 @@ int BPF_PROG(no_destroy, struct bpf_iter_meta *meta, struct cgroup *cgrp)
SEC("iter/cgroup")
__description("uninitialized iter in ->next()")
-__failure __msg("expected an initialized iter_bits as arg #0")
+__failure __msg("expected an initialized iter_bits as R1")
int BPF_PROG(next_uninit, struct bpf_iter_meta *meta, struct cgroup *cgrp)
{
struct bpf_iter_bits it = {};
@@ -43,7 +43,7 @@ int BPF_PROG(next_uninit, struct bpf_iter_meta *meta, struct cgroup *cgrp)
SEC("iter/cgroup")
__description("uninitialized iter in ->destroy()")
-__failure __msg("expected an initialized iter_bits as arg #0")
+__failure __msg("expected an initialized iter_bits as R1")
int BPF_PROG(destroy_uninit, struct bpf_iter_meta *meta, struct cgroup *cgrp)
{
struct bpf_iter_bits it = {};
diff --git a/tools/testing/selftests/bpf/progs/verifier_bounds.c b/tools/testing/selftests/bpf/progs/verifier_bounds.c
index 411a18437d7e..df8d5309657e 100644
--- a/tools/testing/selftests/bpf/progs/verifier_bounds.c
+++ b/tools/testing/selftests/bpf/progs/verifier_bounds.c
@@ -202,7 +202,7 @@ l0_%=: /* exit */ \
SEC("tc")
__description("bounds check based on reg_off + var_off + insn_off. test1")
-__failure __msg("value_size=8 off=1073741825")
+__failure __msg("map_value pointer offset 1073741822 is not allowed")
__naked void var_off_insn_off_test1(void)
{
asm volatile (" \
@@ -1066,7 +1066,6 @@ l0_%=: r0 = 0; \
SEC("xdp")
__description("bound check with JMP_JSLT for crossing 64-bit signed boundary")
__success __retval(0)
-__flag(BPF_F_TEST_REG_INVARIANTS)
__naked void crossing_64_bit_signed_boundary_2(void)
{
asm volatile (" \
@@ -1148,7 +1147,6 @@ l0_%=: r0 = 0; \
SEC("xdp")
__description("bound check with JMP32_JSLT for crossing 32-bit signed boundary")
__success __retval(0)
-__flag(!BPF_F_TEST_REG_INVARIANTS) /* known invariants violation */
__naked void crossing_32_bit_signed_boundary_2(void)
{
asm volatile (" \
@@ -1197,7 +1195,7 @@ l0_%=: r1 = r6; \
r3 += -8; \
r5 = 0; \
/* The 4th argument of bpf_skb_store_bytes is defined as \
- * ARG_CONST_SIZE, so 0 is not allowed. The 'r4 != 0' \
+ * ARG_MEM_SIZE, so 0 is not allowed. The 'r4 != 0' \
* is providing us this exclusion of zero from initial \
* [0, 7] range. \
*/ \
@@ -1241,7 +1239,8 @@ l0_%=: r0 = 0; \
SEC("tc")
__description("multiply mixed sign bounds. test 1")
__success __log_level(2)
-__msg("r6 *= r7 {{.*}}; R6=scalar(smin=umin=0x1bc16d5cd4927ee1,smax=umax=0x1bc16d674ec80000,smax32=0x7ffffeff,umax32=0xfffffeff,var_off=(0x1bc16d4000000000; 0x3ffffffeff))")
+__msg("r6 *= r7 {{.*}}; R6=scalar(smin=umin=0x1bc16d5cd4927ee1,smax=umax=0x1bc16d674ec80000,smax32=0x7ffffeff,var_off=(0x1bc16d4000000000; 0x3ffffffeff))")
+/* cnum can't represent both [0, 0xffff_feff] and [0x8000_0000, 0x7fff_feff], so it picks one */
__naked void mult_mixed0_sign(void)
{
asm volatile (
@@ -1477,7 +1476,7 @@ __naked void sub64_full_overflow(void)
SEC("socket")
__description("64-bit subtraction, partial overflow, result in unbounded reg")
__success __log_level(2)
-__msg("3: (1f) r3 -= r2 {{.*}} R3=scalar()")
+__msg("3: (1f) r3 -= r2 {{.*}} R3=scalar(id=1-1)")
__retval(0)
__naked void sub64_partial_overflow(void)
{
@@ -1536,7 +1535,7 @@ __naked void sub32_partial_overflow(void)
SEC("socket")
__description("dead branch on jset, does not result in invariants violation error")
__success __log_level(2)
-__retval(0) __flag(BPF_F_TEST_REG_INVARIANTS)
+__retval(0)
__naked void jset_range_analysis(void)
{
asm volatile (" \
@@ -1572,7 +1571,7 @@ l0_%=: r0 = 0; \
*/
SEC("socket")
__description("bounds deduction cross sign boundary, negative overlap")
-__success __log_level(2) __flag(BPF_F_TEST_REG_INVARIANTS)
+__success __log_level(2)
__msg("7: (1f) r0 -= r6 {{.*}} R0=scalar(smin=smin32=-655,smax=smax32=-146,umin=0xfffffffffffffd71,umax=0xffffffffffffff6e,umin32=0xfffffd71,umax32=0xffffff6e,var_off=(0xfffffffffffffc00; 0x3ff))")
__retval(0)
__naked void bounds_deduct_negative_overlap(void)
@@ -1616,7 +1615,7 @@ l0_%=: r0 = 0; \
*/
SEC("socket")
__description("bounds deduction cross sign boundary, positive overlap")
-__success __log_level(2) __flag(BPF_F_TEST_REG_INVARIANTS)
+__success __log_level(2)
__msg("3: (2d) if r0 > r1 {{.*}} R0=scalar(smin=smin32=0,smax=umax=smax32=umax32=127,var_off=(0x0; 0x7f))")
__retval(0)
__naked void bounds_deduct_positive_overlap(void)
@@ -1649,8 +1648,9 @@ l0_%=: r0 = 0; \
*/
SEC("socket")
__description("bounds deduction cross sign boundary, two overlaps")
-__failure __flag(BPF_F_TEST_REG_INVARIANTS)
-__msg("3: (2d) if r0 > r1 {{.*}} R0=scalar(smin=smin32=-128,smax=smax32=127,umax=0xffffffffffffff80)")
+__failure
+__msg("3: (2d) if r0 > r1 {{.*}} R0=scalar(smin=smin32=-128,smax=smax32=127)")
+/* smin=-128 includes point 0xffffffffffffff80 */
__msg("frame pointer is read only")
__naked void bounds_deduct_two_overlaps(void)
{
@@ -1713,7 +1713,7 @@ SEC("socket")
__description("conditional jump on same register, branch taken")
__not_msg("20: (b7) r0 = 1 {{.*}} R0=1")
__success __log_level(2)
-__retval(0) __flag(BPF_F_TEST_REG_INVARIANTS)
+__retval(0)
__naked void condition_jump_on_same_register(void *ctx)
{
asm volatile(" \
@@ -1748,7 +1748,7 @@ SEC("socket")
__description("jset on same register, constant value branch taken")
__not_msg("7: (b7) r0 = 1 {{.*}} R0=1")
__success __log_level(2)
-__retval(0) __flag(BPF_F_TEST_REG_INVARIANTS)
+__retval(0)
__naked void jset_on_same_register_1(void *ctx)
{
asm volatile(" \
@@ -1770,7 +1770,7 @@ SEC("socket")
__description("jset on same register, scalar value branch taken")
__not_msg("12: (b7) r0 = 1 {{.*}} R0=1")
__success __log_level(2)
-__retval(0) __flag(BPF_F_TEST_REG_INVARIANTS)
+__retval(0)
__naked void jset_on_same_register_2(void *ctx)
{
asm volatile(" \
@@ -1800,7 +1800,6 @@ __description("jset on same register, scalar value unknown branch 1")
__msg("3: (b7) r0 = 0 {{.*}} R0=0")
__msg("5: (b7) r0 = 1 {{.*}} R0=1")
__success __log_level(2)
-__flag(BPF_F_TEST_REG_INVARIANTS)
__naked void jset_on_same_register_3(void *ctx)
{
asm volatile(" \
@@ -1822,7 +1821,6 @@ __description("jset on same register, scalar value unknown branch 2")
__msg("4: (b7) r0 = 0 {{.*}} R0=0")
__msg("6: (b7) r0 = 1 {{.*}} R0=1")
__success __log_level(2)
-__flag(BPF_F_TEST_REG_INVARIANTS)
__naked void jset_on_same_register_4(void *ctx)
{
asm volatile(" \
@@ -1845,7 +1843,6 @@ __description("jset on same register, scalar value unknown branch 3")
__msg("4: (b7) r0 = 0 {{.*}} R0=0")
__msg("6: (b7) r0 = 1 {{.*}} R0=1")
__success __log_level(2)
-__flag(BPF_F_TEST_REG_INVARIANTS)
__naked void jset_on_same_register_5(void *ctx)
{
asm volatile(" \
@@ -1863,4 +1860,479 @@ l1_%=: r0 = 1; \
: __clobber_all);
}
+/* This test covers the bounds deduction when the u64 range and the tnum
+ * overlap only at umax. After instruction 3, the ranges look as follows:
+ *
+ * 0 umin=0xe01 umax=0xf00 U64_MAX
+ * | [xxxxxxxxxxxxxx] |
+ * |----------------------------|------------------------------|
+ * | x x | tnum values
+ *
+ * The verifier can therefore deduce that the R0=0xf0=240.
+ */
+SEC("socket")
+__description("bounds refinement with single-value tnum on umax")
+__msg("3: (15) if r0 == 0xe0 {{.*}} R0=240")
+__success __log_level(2)
+__naked void bounds_refinement_tnum_umax(void *ctx)
+{
+ asm volatile(" \
+ call %[bpf_get_prandom_u32]; \
+ r0 |= 0xe0; \
+ r0 &= 0xf0; \
+ if r0 == 0xe0 goto +2; \
+ if r0 == 0xf0 goto +1; \
+ r10 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* This test covers the bounds deduction when the u64 range and the tnum
+ * overlap only at umin. After instruction 3, the ranges look as follows:
+ *
+ * 0 umin=0xe1 umax=0xf0 U64_MAX
+ * | [xxxxxxxxxxxxxx] |
+ * |----------------------------|------------------------------|
+ * | x x | tnum values
+ *
+ * The verifier can therefore deduce that the R0=0xe1=225.
+ */
+SEC("socket")
+__description("bounds refinement with single-value tnum on umin")
+__msg("3: (15) if r0 == 0xf1 {{.*}} R0=225")
+__success __log_level(2)
+__naked void bounds_refinement_tnum_umin(void *ctx)
+{
+ asm volatile(" \
+ call %[bpf_get_prandom_u32]; \
+ r0 |= 0xe1; \
+ r0 &= 0xf1; \
+ if r0 == 0xf1 goto +2; \
+ if r0 == 0xe1 goto +1; \
+ r10 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* This test covers the bounds deduction when the only possible tnum value is
+ * in the middle of the u64 range. After instruction 3, the ranges look as
+ * follows:
+ *
+ * 0 umin=0x7cf umax=0x7df U64_MAX
+ * | [xxxxxxxxxxxx] |
+ * |----------------------------|------------------------------|
+ * | x x x x x | tnum values
+ * | +--- 0x7e0
+ * +--- 0x7d0
+ *
+ * Since the lower four bits are zero, the tnum and the u64 range only overlap
+ * in R0=0x7d0=2000. Instruction 5 is therefore dead code.
+ */
+SEC("socket")
+__description("bounds refinement with single-value tnum in middle of range")
+__msg("3: (a5) if r0 < 0x7cf {{.*}} R0=2000")
+__success __log_level(2)
+__naked void bounds_refinement_tnum_middle(void *ctx)
+{
+ asm volatile(" \
+ call %[bpf_get_prandom_u32]; \
+ if r0 & 0x0f goto +4; \
+ if r0 > 0x7df goto +3; \
+ if r0 < 0x7cf goto +2; \
+ if r0 == 0x7d0 goto +1; \
+ r10 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* This test cover the negative case for the tnum/u64 overlap. Since
+ * they contain the same two values (i.e., {0, 1}), we can't deduce
+ * anything more.
+ */
+SEC("socket")
+__description("bounds refinement: several overlaps between tnum and u64")
+__msg("2: (25) if r0 > 0x1 {{.*}} R0=scalar(smin=smin32=0,smax=umax=smax32=umax32=1,var_off=(0x0; 0x1))")
+__failure __log_level(2)
+__naked void bounds_refinement_several_overlaps(void *ctx)
+{
+ asm volatile(" \
+ call %[bpf_get_prandom_u32]; \
+ if r0 < 0 goto +3; \
+ if r0 > 1 goto +2; \
+ if r0 == 1 goto +1; \
+ r10 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* This test cover the negative case for the tnum/u64 overlap. Since
+ * they overlap in the two values contained by the u64 range (i.e.,
+ * {0xf, 0x10}), we can't deduce anything more.
+ */
+SEC("socket")
+__description("bounds refinement: multiple overlaps between tnum and u64")
+__msg("2: (25) if r0 > 0x10 {{.*}} R0=scalar(smin=umin=smin32=umin32=15,smax=umax=smax32=umax32=16,var_off=(0x0; 0x1f))")
+__failure __log_level(2)
+__naked void bounds_refinement_multiple_overlaps(void *ctx)
+{
+ asm volatile(" \
+ call %[bpf_get_prandom_u32]; \
+ if r0 < 0xf goto +3; \
+ if r0 > 0x10 goto +2; \
+ if r0 == 0x10 goto +1; \
+ r10 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__success
+__naked void signed_unsigned_intersection32_case1(void *ctx)
+{
+ asm volatile(" \
+ call %[bpf_get_prandom_u32]; \
+ w0 &= 0xffffffff; \
+ if w0 < 0x3 goto 1f; /* on fall-through u32 range [3..U32_MAX] */ \
+ if w0 s> 0x1 goto 1f; /* on fall-through s32 range [S32_MIN..1] */ \
+ if w0 s< 0x0 goto 1f; /* range can be narrowed to [S32_MIN..-1] */ \
+ r10 = 0; /* thus predicting the jump. */ \
+1: exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__success
+__naked void signed_unsigned_intersection32_case2(void *ctx)
+{
+ asm volatile(" \
+ call %[bpf_get_prandom_u32]; \
+ w0 &= 0xffffffff; \
+ if w0 > 0x80000003 goto 1f; /* on fall-through u32 range [0..S32_MIN+3] */ \
+ if w0 s< -3 goto 1f; /* on fall-through s32 range [-3..S32_MAX] */ \
+ if w0 s> 5 goto 1f; /* on fall-through s32 range [-3..5] */ \
+ if w0 <= 5 goto 1f; /* range can be narrowed to [0..5] */ \
+ r10 = 0; /* thus predicting the jump */ \
+1: exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * After instruction 3, the u64 and s64 ranges look as follows:
+ * 0 umin=2 umax=0xff..ff00..03 U64_MAX
+ * | [xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx] |
+ * |----------------------------|------------------------------|
+ * |xx] [xxxxxxxxxxxxxxxxxxxxxxxxxxxx|
+ * 0 smax=2 smin=0x800..02 -1
+ *
+ * The two ranges can't be refined because they overlap in two places. Once we
+ * add an upper-bound to u64 at instruction 4, the refinement can happen. This
+ * test validates that this refinement does happen and is not overwritten by
+ * the less-precise 32bits ranges.
+ */
+SEC("socket")
+__description("bounds refinement: 64bits ranges not overwritten by 32bits ranges")
+__msg("3: (65) if r0 s> 0x2 {{.*}} R0=scalar(smin=0x8000000000000002,smax=2,smin32=umin32=2,smax32=umax32=3,var_off{{.*}}))")
+/* Can't represent both [S64_MIN+2, 2] and [2, U64_MAX - U32_MAX + 2] at the same time, picks shorter interval */
+__msg("4: (25) if r0 > 0x13 {{.*}} R0=2")
+__success __log_level(2)
+__naked void refinement_32bounds_not_overwriting_64bounds(void *ctx)
+{
+ asm volatile(" \
+ call %[bpf_get_prandom_u32]; \
+ if w0 < 2 goto +5; \
+ if w0 > 3 goto +4; \
+ if r0 s> 2 goto +3; \
+ if r0 > 19 goto +2; \
+ if r0 == 2 goto +1; \
+ r10 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("maybe_fork_scalars: OR with constant rejects OOB")
+__failure __msg("invalid access to map value")
+__naked void or_scalar_fork_rejects_oob(void)
+{
+ asm volatile (" \
+ 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_%=; \
+ r9 = r0; \
+ r6 = *(u64*)(r9 + 0); \
+ r6 s>>= 63; \
+ r6 |= 8; \
+ /* r6 is -1 (current) or 8 (pushed) */ \
+ if r6 s< 0 goto l0_%=; \
+ /* pushed path: r6 = 8, OOB for value_size=8 */ \
+ r9 += r6; \
+ r0 = *(u8*)(r9 + 0); \
+l0_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_8b)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("maybe_fork_scalars: AND with constant still works")
+__success __retval(0)
+__naked void and_scalar_fork_still_works(void)
+{
+ asm volatile (" \
+ 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_%=; \
+ r9 = r0; \
+ r6 = *(u64*)(r9 + 0); \
+ r6 s>>= 63; \
+ r6 &= 4; \
+ /* \
+ * r6 is 0 (pushed, 0&4==0) or 4 (current) \
+ * both within value_size=8 \
+ */ \
+ if r6 s< 0 goto l0_%=; \
+ r9 += r6; \
+ r0 = *(u8*)(r9 + 0); \
+l0_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_8b)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("maybe_fork_scalars: OR with constant allows in-bounds")
+__success __retval(0)
+__naked void or_scalar_fork_allows_inbounds(void)
+{
+ asm volatile (" \
+ 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_%=; \
+ r9 = r0; \
+ r6 = *(u64*)(r9 + 0); \
+ r6 s>>= 63; \
+ r6 |= 4; \
+ /* \
+ * r6 is -1 (current) or 4 (pushed) \
+ * pushed path: r6 = 4, within value_size=8 \
+ */ \
+ if r6 s< 0 goto l0_%=; \
+ r9 += r6; \
+ r0 = *(u8*)(r9 + 0); \
+l0_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_8b)
+ : __clobber_all);
+}
+
+/*
+ * Last jump can be detected as always taken because the intersection of R5 and
+ * R7 32bit tnums produces a constant that isn't within R7's s32 bounds.
+ */
+SEC("socket")
+__description("dead branch: tnums give impossible constant if equal")
+__success
+__naked void tnums_equal_impossible_constant(void *ctx)
+{
+ asm volatile(" \
+ call %[bpf_get_prandom_u32]; \
+ r5 = r0; \
+ /* Set r5's var_off32 to (0; 0xfffffffc) */ \
+ r5 &= 0xfffffffffffffffc; \
+ r7 = r0; \
+ /* Set r7's var_off32 to (0x0; 0x1) */ \
+ r7 &= 0x1; \
+ /* Now, s32=[-43; -42], var_off32=(0xffffffd4; 0x3) */ \
+ r7 += -43; \
+ /* On fallthrough, var_off32=-44, not in s32 */ \
+ if w5 != w7 goto +1; \
+ r10 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * 32-bit range starts before 64-bit range low bits in each 2^32 block.
+ *
+ * N*2^32 (N+1)*2^32 (N+2)*2^32 (N+3)*2^32
+ * ||----|=====|--|----------||----|=====|-------------||--|-|=====|-------------||
+ * |< b >| | |< b >| | |< b >|
+ * | | | |
+ * |<---------------+- a -+---------------->|
+ * | |
+ * |< t >| refined r0 range
+ *
+ * a = u64 [0x1'00000008, 0x3'00000001]
+ * b = u32 [2, 5]
+ * t = u64 [0x2'00000002, 0x2'00000005]
+ */
+SEC("socket")
+__success
+__flag(BPF_F_TEST_REG_INVARIANTS)
+__naked void deduce64_from_32_before_block_start(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = 0x100000008 ll; \
+ if r0 < r1 goto 2f; \
+ r1 = 0x300000001 ll; \
+ if r0 > r1 goto 2f; /* u64: [0x1'00000008, 0x3'00000001] */ \
+ if w0 < 2 goto 2f; \
+ if w0 > 5 goto 2f; /* u32: [2, 5] */ \
+ r2 = 0x200000002 ll; \
+ r3 = 0x200000005 ll; \
+ if r0 >= r2 goto 1f; /* should be always true */ \
+ r10 = 0; /* dead code */ \
+1: if r0 <= r3 goto 2f; /* should be always true */ \
+ r10 = 0; /* dead code */ \
+2: exit; \
+ "
+ :: __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * 32-bit range crossing U32_MAX / 0 boundary.
+ *
+ * N*2^32 (N+1)*2^32 (N+2)*2^32 (N+3)*2^32
+ * ||===|---------|------|===||===|----------------|===||===|---------|------|===||
+ * |b >| | |< b||b >| |< b||b >| | |< b|
+ * | | | |
+ * |<-----+----------------- a --------------+-------->|
+ * | |
+ * |<---------------- t ------------->| refined r0 range
+ *
+ * a = u64 [0x1'00000006, 0x2'FFFFFFEF]
+ * b = s32 [-16, 5] (u32 wrapping [0xFFFFFFF0, 0x00000005])
+ * t = u64 [0x1'FFFFFFF0, 0x2'00000005]
+ */
+SEC("socket")
+__success
+__flag(BPF_F_TEST_REG_INVARIANTS)
+__naked void deduce64_from_32_wrapping_32bit(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = 0x100000006 ll; \
+ if r0 < r1 goto 2f; \
+ r1 = 0x2ffffffef ll; \
+ if r0 > r1 goto 2f; /* u64: [0x1'00000006, 0x2'FFFFFFEF] */ \
+ if w0 s< -16 goto 2f; \
+ if w0 s> 5 goto 2f; /* s32: [-16, 5] */ \
+ r1 = 0x1fffffff0 ll; \
+ r2 = 0x200000005 ll; \
+ if r0 >= r1 goto 1f; /* should be always true */ \
+ r10 = 0; /* dead code */ \
+1: if r0 <= r2 goto 2f; /* should be always true */ \
+ r10 = 0; /* dead code */ \
+2: exit; \
+ "
+ :: __imm(bpf_get_prandom_u32)
+ : __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")
+__flag(BPF_F_TEST_STATE_FREQ)
+__naked void range_within_cnum_cross_both_boundaries(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = 0x80000020; \
+ if r0 > r1 goto 1f; \
+ r0 += 0x7FFFFFF0; /* PATH 1 */ \
+ goto 2f; \
+1: call %[bpf_get_prandom_u32]; /* PATH 2 */ \
+ if r0 < 0x100 goto 3f; \
+ if r0 > 0x200 goto 3f; \
+2: /* PATH 1: r0 ∈ [0x7FFFFFF0, U32_MAX] ∪ [0, 0x10] */ \
+ /* PATH 2: r0 ∈ [0x100, 0x200] */ \
+ if r0 != 0x100 goto 3f; /* True only on PATH 2 */ \
+ r0 /= 0; \
+3: exit; \
+ "
+ :: __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_8b),
+ __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_bpf_fastcall.c b/tools/testing/selftests/bpf/progs/verifier_bpf_fastcall.c
index fb4fa465d67c..a73b837553fb 100644
--- a/tools/testing/selftests/bpf/progs/verifier_bpf_fastcall.c
+++ b/tools/testing/selftests/bpf/progs/verifier_bpf_fastcall.c
@@ -10,7 +10,8 @@
SEC("raw_tp")
__arch_x86_64
-__log_level(4) __msg("stack depth 8")
+__log_level(4)
+__msg("subprog 0 (simple) main insns_self {{[0-9]+}} insns_total {{[0-9]+}} stack 8")
__xlated("4: r5 = 5")
__xlated("5: r0 = ")
__xlated("6: r0 = &(void __percpu *)(r0)")
@@ -96,7 +97,8 @@ __naked void canary_zero_spills(void)
SEC("raw_tp")
__arch_x86_64
-__log_level(4) __msg("stack depth 16")
+__log_level(4)
+__msg("subprog 0 (wrong_reg_in_pattern1) main {{.*}} stack 16")
__xlated("1: *(u64 *)(r10 -16) = r1")
__xlated("...")
__xlated("3: r0 = &(void __percpu *)(r0)")
@@ -598,7 +600,8 @@ __naked static void subprogs_use_independent_offsets_aux(void)
SEC("raw_tp")
__arch_x86_64
-__log_level(4) __msg("stack depth 8")
+__log_level(4)
+__msg("subprog 0 (helper_call_does_not_prevent_bpf_fastcall) main {{.*}} stack 8")
__xlated("2: r0 = &(void __percpu *)(r0)")
__success
__naked void helper_call_does_not_prevent_bpf_fastcall(void)
@@ -618,9 +621,120 @@ __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("stack depth 24")
+__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)
+__msg("subprog 0 (may_goto_interaction_x86_64) main {{.*}} stack 24")
/* may_goto counter at -24 */
__xlated("0: *(u64 *)(r10 -24) =")
/* may_goto timestamp at -16 */
@@ -630,13 +744,13 @@ __xlated("...")
__xlated("4: r0 = &(void __percpu *)(r0)")
__xlated("...")
/* may_goto expansion starts */
-__xlated("6: r11 = *(u64 *)(r10 -24)")
-__xlated("7: if r11 == 0x0 goto pc+6")
-__xlated("8: r11 -= 1")
-__xlated("9: if r11 != 0x0 goto pc+2")
-__xlated("10: r11 = -24")
+__xlated("6: r12 = *(u64 *)(r10 -24)")
+__xlated("7: if r12 == 0x0 goto pc+6")
+__xlated("8: r12 -= 1")
+__xlated("9: if r12 != 0x0 goto pc+2")
+__xlated("10: r12 = -24")
__xlated("11: call unknown")
-__xlated("12: *(u64 *)(r10 -24) = r11")
+__xlated("12: *(u64 *)(r10 -24) = r12")
/* may_goto expansion ends */
__xlated("13: *(u64 *)(r10 -8) = r1")
__xlated("14: exit")
@@ -660,7 +774,10 @@ __naked void may_goto_interaction_x86_64(void)
SEC("raw_tp")
__arch_arm64
-__log_level(4) __msg("stack depth 24")
+__arch_riscv64
+__arch_loongarch
+__log_level(4)
+__msg("subprog 0 (may_goto_interaction) main {{.*}} stack 24")
/* may_goto counter at -24 */
__xlated("0: *(u64 *)(r10 -24) =")
/* may_goto timestamp at -16 */
@@ -668,18 +785,18 @@ __xlated("1: *(u64 *)(r10 -16) =")
__xlated("2: r1 = 1")
__xlated("3: call bpf_get_smp_processor_id")
/* may_goto expansion starts */
-__xlated("4: r11 = *(u64 *)(r10 -24)")
-__xlated("5: if r11 == 0x0 goto pc+6")
-__xlated("6: r11 -= 1")
-__xlated("7: if r11 != 0x0 goto pc+2")
-__xlated("8: r11 = -24")
+__xlated("4: r12 = *(u64 *)(r10 -24)")
+__xlated("5: if r12 == 0x0 goto pc+6")
+__xlated("6: r12 -= 1")
+__xlated("7: if r12 != 0x0 goto pc+2")
+__xlated("8: r12 = -24")
__xlated("9: call unknown")
-__xlated("10: *(u64 *)(r10 -24) = r11")
+__xlated("10: *(u64 *)(r10 -24) = r12")
/* may_goto expansion ends */
__xlated("11: *(u64 *)(r10 -8) = r1")
__xlated("12: exit")
__success
-__naked void may_goto_interaction_arm64(void)
+__naked void may_goto_interaction(void)
{
asm volatile (
"r1 = 1;"
@@ -707,7 +824,9 @@ __naked static void dummy_loop_callback(void)
SEC("raw_tp")
__arch_x86_64
-__log_level(4) __msg("stack depth 32+0")
+__log_level(4)
+__msg("subprog 0 (bpf_loop_interaction1) main {{.*}} stack 32")
+__msg("subprog 1 (dummy_loop_callback) static {{.*}} stack 0")
__xlated("2: r1 = 1")
__xlated("3: r0 =")
__xlated("4: r0 = &(void __percpu *)(r0)")
@@ -755,7 +874,9 @@ __naked int bpf_loop_interaction1(void)
SEC("raw_tp")
__arch_x86_64
-__log_level(4) __msg("stack depth 40+0")
+__log_level(4)
+__msg("subprog 0 (bpf_loop_interaction2) main {{.*}} stack 40")
+__msg("subprog 1 (dummy_loop_callback) static {{.*}} stack 0")
/* call bpf_get_smp_processor_id */
__xlated("2: r1 = 42")
__xlated("3: r0 =")
@@ -800,7 +921,9 @@ __naked int bpf_loop_interaction2(void)
SEC("raw_tp")
__arch_x86_64
__log_level(4)
-__msg("stack depth 512+0")
+__msg("stack depth max 512")
+__msg("subprog 0 (cumulative_stack_depth) main {{.*}} stack 512")
+__msg("subprog 1 (cumulative_stack_depth_subprog) static {{.*}} stack 0")
/* just to print xlated version when debugging */
__xlated("r0 = &(void __percpu *)(r0)")
__success
diff --git a/tools/testing/selftests/bpf/progs/verifier_bswap.c b/tools/testing/selftests/bpf/progs/verifier_bswap.c
index e61755656e8d..cffaf36192bc 100644
--- a/tools/testing/selftests/bpf/progs/verifier_bswap.c
+++ b/tools/testing/selftests/bpf/progs/verifier_bswap.c
@@ -48,6 +48,71 @@ __naked void bswap_64(void)
: __clobber_all);
}
+#define BSWAP_RANGE_TEST(name, op, in_value, out_value) \
+ SEC("socket") \
+ __success __log_level(2) \
+ __msg("r0 &= {{.*}}; R0=scalar({{.*}},var_off=(0x0; " #in_value "))") \
+ __msg("r0 = " op " r0 {{.*}}; R0=scalar({{.*}},var_off=(0x0; " #out_value "))") \
+ __naked void name(void) \
+ { \
+ asm volatile ( \
+ "call %[bpf_get_prandom_u32];" \
+ "r0 &= " #in_value ";" \
+ "r0 = " op " r0;" \
+ "r2 = " #out_value " ll;" \
+ "if r0 > r2 goto trap_%=;" \
+ "r0 = 0;" \
+ "exit;" \
+ "trap_%=:" \
+ "r1 = 42;" \
+ "r0 = *(u64 *)(r1 + 0);" \
+ "exit;" \
+ : \
+ : __imm(bpf_get_prandom_u32) \
+ : __clobber_all); \
+ }
+
+BSWAP_RANGE_TEST(bswap16_range, "bswap16", 0x3f00, 0x3f)
+BSWAP_RANGE_TEST(bswap32_range, "bswap32", 0x3f00, 0x3f0000)
+BSWAP_RANGE_TEST(bswap64_range, "bswap64", 0x3f00, 0x3f000000000000)
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+BSWAP_RANGE_TEST(be16_range, "be16", 0x3f00, 0x3f)
+BSWAP_RANGE_TEST(be32_range, "be32", 0x3f00, 0x3f0000)
+BSWAP_RANGE_TEST(be64_range, "be64", 0x3f00, 0x3f000000000000)
+BSWAP_RANGE_TEST(le16_range, "le16", 0x3f00, 0x3f00)
+BSWAP_RANGE_TEST(le32_range, "le32", 0x3f00, 0x3f00)
+BSWAP_RANGE_TEST(le64_range, "le64", 0x3f00, 0x3f00)
+#else
+BSWAP_RANGE_TEST(be16_range, "be16", 0x3f00, 0x3f00)
+BSWAP_RANGE_TEST(be32_range, "be32", 0x3f00, 0x3f00)
+BSWAP_RANGE_TEST(be64_range, "be64", 0x3f00, 0x3f00)
+BSWAP_RANGE_TEST(le16_range, "le16", 0x3f00, 0x3f)
+BSWAP_RANGE_TEST(le32_range, "le32", 0x3f00, 0x3f0000)
+BSWAP_RANGE_TEST(le64_range, "le64", 0x3f00, 0x3f000000000000)
+#endif
+
+SEC("socket")
+__description("BSWAP, reset reg id")
+__failure __msg("math between fp pointer and register with unbounded min value is not allowed")
+__naked void bswap_reset_reg_id(void)
+{
+ asm volatile (" \
+ call %[bpf_ktime_get_ns]; \
+ r1 = r0; \
+ r0 = be16 r0; \
+ if r0 != 1 goto l0_%=; \
+ r2 = r10; \
+ r2 += -512; \
+ r2 += r1; \
+ *(u8 *)(r2 + 0) = 0; \
+l0_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_ktime_get_ns)
+ : __clobber_all);
+}
+
#else
SEC("socket")
diff --git a/tools/testing/selftests/bpf/progs/verifier_btf_flex_array.c b/tools/testing/selftests/bpf/progs/verifier_btf_flex_array.c
new file mode 100644
index 000000000000..59b84261f622
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_btf_flex_array.c
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+#include "bpf_experimental.h"
+#include "bpf_misc.h"
+
+struct test_empty_event {};
+
+struct test_flex_batch {
+ int nr;
+ struct test_empty_event events[];
+};
+
+struct map_value {
+ struct test_flex_batch __kptr *batch;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, int);
+ __type(value, struct map_value);
+ __uint(max_entries, 1);
+} batches SEC(".maps");
+
+SEC("syscall")
+__description("btf walk into flexible array of zero-sized elements")
+__failure __msg("access beyond struct test_flex_batch at off 4 size 1")
+int stash_and_peek(void *ctx)
+{
+ struct test_flex_batch *b, *old;
+ struct map_value *v;
+ int key = 0;
+
+ v = bpf_map_lookup_elem(&batches, &key);
+ if (!v)
+ return 0;
+
+ b = bpf_obj_new(struct test_flex_batch);
+ if (!b)
+ return 0;
+ b->nr = 1;
+
+ old = bpf_kptr_xchg(&v->batch, b);
+ if (old)
+ bpf_obj_drop(old);
+
+ b = v->batch;
+ if (!b)
+ return 0;
+
+ return b->nr + *(char *)&b->events[0];
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_call_large_imm.c b/tools/testing/selftests/bpf/progs/verifier_call_large_imm.c
new file mode 100644
index 000000000000..7998df07f6a6
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_call_large_imm.c
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+int call_happened = 0;
+
+/*
+ * 32765 is the exact minimum number of padding instructions needed to
+ * trigger the verifier failure, because:
+ * 1. Counting the wrapper instructions around the padding block (one
+ * "r0=0" and two "exit" instructions), the actual jump distance
+ * evaluates to N + 3.
+ * 2. To overflow the s16 max bound (32767), we need N + 3 > 32767.
+ * Thus, N = 32765 is the exact minimum padding size required.
+ */
+static __attribute__((noinline)) void padding_subprog(void)
+{
+ asm volatile (
+ "r0 = 0;"
+ ".rept 32765;"
+ "r0 += 0;"
+ ".endr;"
+ ::: __clobber_all);
+}
+
+static __attribute__((noinline)) int target_subprog(void)
+{
+ /* Use volatile variable here to prevent optimization. */
+ volatile int magic_ret = 3;
+ return magic_ret;
+}
+
+SEC("syscall")
+__success __retval(3)
+int call_large_imm_test(void *ctx)
+{
+ /*
+ * Landing pad to handle call error on kernel without the fix,
+ * preventing kernel panic.
+ */
+ asm volatile (
+ "r0 = 0;"
+ ".rept 32768;"
+ "r0 += 0;"
+ ".endr;"
+ ::: __clobber_all);
+
+ /*
+ * The call_happened variable is 1 only when the call insn wrongly
+ * go back to the landing pad above.
+ */
+ if (call_happened == 1) {
+ /* Use volatile variable here to prevent optimization. */
+ volatile int flag = -1;
+ return flag;
+ }
+
+ call_happened = 1;
+
+ padding_subprog();
+
+ return target_subprog();
+}
+
+char LICENSE[] SEC("license") = "GPL";
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_ctx.c b/tools/testing/selftests/bpf/progs/verifier_ctx.c
index 5ebf7d9bcc55..7856dad3d1f3 100644
--- a/tools/testing/selftests/bpf/progs/verifier_ctx.c
+++ b/tools/testing/selftests/bpf/progs/verifier_ctx.c
@@ -4,6 +4,10 @@
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+static const char ctx_strncmp_target[] = "ctx";
+static const char ctx_snprintf_fmt[] = "";
SEC("tc")
__description("context stores via BPF_ATOMIC")
@@ -69,7 +73,6 @@ __naked void ctx_pointer_to_helper_1(void)
SEC("socket")
__description("pass modified ctx pointer to helper, 2")
__failure __msg("negative offset ctx ptr R1 off=-612 disallowed")
-__failure_unpriv __msg_unpriv("negative offset ctx ptr R1 off=-612 disallowed")
__naked void ctx_pointer_to_helper_2(void)
{
asm volatile (" \
@@ -292,4 +295,568 @@ padding_access("cgroup/post_bind4", bpf_sock, dst_port, 2);
__failure __msg("invalid bpf_context access")
padding_access("sk_reuseport", sk_reuseport_md, hash, 4);
+SEC("?syscall")
+__description("syscall: write to ctx with fixed offset")
+__success
+int syscall_ctx_fixed_off_write(void *ctx)
+{
+ char *p = ctx;
+
+ *(__u32 *)p = 0;
+ *(__u32 *)(p + 4) = 0;
+ return 0;
+}
+
+SEC("?syscall")
+__description("syscall: read ctx with fixed offset")
+__success
+int syscall_ctx_fixed_off_read(void *ctx)
+{
+ char *p = ctx;
+ volatile __u32 val;
+
+ val = *(__u32 *)(p + 4);
+ (void)val;
+ return 0;
+}
+
+SEC("?syscall")
+__description("syscall: unaligned read ctx with fixed offset")
+__success
+int syscall_ctx_unaligned_fixed_off_read(void *ctx)
+{
+ char *p = ctx;
+ volatile __u32 val;
+
+ val = *(__u32 *)(p + 2);
+ (void)val;
+ return 0;
+}
+
+SEC("?syscall")
+__description("syscall: unaligned write ctx with fixed offset")
+__success
+int syscall_ctx_unaligned_fixed_off_write(void *ctx)
+{
+ char *p = ctx;
+
+ *(__u32 *)(p + 2) = 0;
+ return 0;
+}
+
+SEC("?syscall")
+__description("syscall: read ctx with variable offset")
+__success
+int syscall_ctx_var_off_read(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+ volatile __u32 val;
+
+ off &= 0xfc;
+ p += off;
+ val = *(__u32 *)p;
+ (void)val;
+ return 0;
+}
+
+SEC("?syscall")
+__description("syscall: write ctx with variable offset")
+__success
+int syscall_ctx_var_off_write(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off &= 0xfc;
+ p += off;
+ *(__u32 *)p = 0;
+ return 0;
+}
+
+SEC("?syscall")
+__description("syscall: unaligned read ctx with variable offset")
+__success
+int syscall_ctx_unaligned_var_off_read(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+ volatile __u32 val;
+
+ off &= 0xfc;
+ off += 2;
+ p += off;
+ val = *(__u32 *)p;
+ (void)val;
+ return 0;
+}
+
+SEC("?syscall")
+__description("syscall: unaligned write ctx with variable offset")
+__success
+int syscall_ctx_unaligned_var_off_write(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off &= 0xfc;
+ off += 2;
+ p += off;
+ *(__u32 *)p = 0;
+ return 0;
+}
+
+SEC("?syscall")
+__description("syscall: reject ctx access past U16_MAX with fixed offset")
+__failure __msg("outside of the allowed memory range")
+int syscall_ctx_u16_max_fixed_off(void *ctx)
+{
+ char *p = ctx;
+ volatile __u32 val;
+
+ p += 65535;
+ val = *(__u32 *)p;
+ (void)val;
+ return 0;
+}
+
+SEC("?syscall")
+__description("syscall: reject ctx access past U16_MAX with variable offset")
+__failure __msg("outside of the allowed memory range")
+int syscall_ctx_u16_max_var_off(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+ volatile __u32 val;
+
+ off &= 0xffff;
+ off += 1;
+ p += off;
+ val = *(__u32 *)p;
+ (void)val;
+ return 0;
+}
+
+SEC("?syscall")
+__description("syscall: reject negative variable offset ctx access")
+__failure __msg("min value is negative")
+int syscall_ctx_neg_var_off(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off &= 4;
+ p -= off;
+ return *(__u32 *)p;
+}
+
+SEC("?syscall")
+__description("syscall: reject unbounded variable offset ctx access")
+__failure __msg("unbounded memory access")
+int syscall_ctx_unbounded_var_off(void *ctx)
+{
+ __u64 off = (__u32)bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off <<= 2;
+ p += off;
+ return *(__u32 *)p;
+}
+
+SEC("?syscall")
+__description("syscall: helper read ctx with fixed offset")
+__success
+int syscall_ctx_helper_fixed_off_read(void *ctx)
+{
+ char *p = ctx;
+
+ p += 4;
+ return bpf_strncmp(p, 4, ctx_strncmp_target);
+}
+
+SEC("?syscall")
+__description("syscall: helper write ctx with fixed offset")
+__success
+int syscall_ctx_helper_fixed_off_write(void *ctx)
+{
+ char *p = ctx;
+
+ p += 4;
+ return bpf_probe_read_kernel(p, 4, 0);
+}
+
+SEC("?syscall")
+__description("syscall: helper unaligned read ctx with fixed offset")
+__success
+int syscall_ctx_helper_unaligned_fixed_off_read(void *ctx)
+{
+ char *p = ctx;
+
+ p += 2;
+ return bpf_strncmp(p, 4, ctx_strncmp_target);
+}
+
+SEC("?syscall")
+__description("syscall: helper unaligned write ctx with fixed offset")
+__success
+int syscall_ctx_helper_unaligned_fixed_off_write(void *ctx)
+{
+ char *p = ctx;
+
+ p += 2;
+ return bpf_probe_read_kernel(p, 4, 0);
+}
+
+SEC("?syscall")
+__description("syscall: helper read ctx with variable offset")
+__success
+int syscall_ctx_helper_var_off_read(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off &= 0xfc;
+ p += off;
+ return bpf_strncmp(p, 4, ctx_strncmp_target);
+}
+
+SEC("?syscall")
+__description("syscall: helper write ctx with variable offset")
+__success
+int syscall_ctx_helper_var_off_write(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off &= 0xfc;
+ p += off;
+ return bpf_probe_read_kernel(p, 4, 0);
+}
+
+SEC("?syscall")
+__description("syscall: helper unaligned read ctx with variable offset")
+__success
+int syscall_ctx_helper_unaligned_var_off_read(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off &= 0xfc;
+ off += 2;
+ p += off;
+ return bpf_strncmp(p, 4, ctx_strncmp_target);
+}
+
+SEC("?syscall")
+__description("syscall: helper unaligned write ctx with variable offset")
+__success
+int syscall_ctx_helper_unaligned_var_off_write(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off &= 0xfc;
+ off += 2;
+ p += off;
+ return bpf_probe_read_kernel(p, 4, 0);
+}
+
+SEC("?syscall")
+__description("syscall: reject helper read ctx past U16_MAX with fixed offset")
+__failure __msg("outside of the allowed memory range")
+int syscall_ctx_helper_u16_max_fixed_off_read(void *ctx)
+{
+ char *p = ctx;
+
+ p += 65535;
+ return bpf_strncmp(p, 4, ctx_strncmp_target);
+}
+
+SEC("?syscall")
+__description("syscall: reject helper write ctx past U16_MAX with fixed offset")
+__failure __msg("outside of the allowed memory range")
+int syscall_ctx_helper_u16_max_fixed_off_write(void *ctx)
+{
+ char *p = ctx;
+
+ p += 65535;
+ return bpf_probe_read_kernel(p, 4, 0);
+}
+
+SEC("?syscall")
+__description("syscall: reject helper read ctx past U16_MAX with variable offset")
+__failure __msg("outside of the allowed memory range")
+int syscall_ctx_helper_u16_max_var_off_read(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off &= 0xffff;
+ off += 1;
+ p += off;
+ return bpf_strncmp(p, 4, ctx_strncmp_target);
+}
+
+SEC("?syscall")
+__description("syscall: reject helper write ctx past U16_MAX with variable offset")
+__failure __msg("outside of the allowed memory range")
+int syscall_ctx_helper_u16_max_var_off_write(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off &= 0xffff;
+ off += 1;
+ p += off;
+ return bpf_probe_read_kernel(p, 4, 0);
+}
+
+SEC("?syscall")
+__description("syscall: helper read zero-sized ctx access")
+__success
+int syscall_ctx_helper_zero_sized_read(void *ctx)
+{
+ return bpf_snprintf(0, 0, ctx_snprintf_fmt, ctx, 0);
+}
+
+SEC("?syscall")
+__description("syscall: helper write zero-sized ctx access")
+__success
+int syscall_ctx_helper_zero_sized_write(void *ctx)
+{
+ return bpf_probe_read_kernel(ctx, 0, 0);
+}
+
+SEC("?syscall")
+__description("syscall: kfunc access ctx with fixed offset")
+__success
+int syscall_ctx_kfunc_fixed_off(void *ctx)
+{
+ char *p = ctx;
+
+ p += 4;
+ bpf_kfunc_call_test_mem_len_pass1(p, 4);
+ return 0;
+}
+
+SEC("?syscall")
+__description("syscall: kfunc access ctx with variable offset")
+__success
+int syscall_ctx_kfunc_var_off(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off &= 0xfc;
+ p += off;
+ bpf_kfunc_call_test_mem_len_pass1(p, 4);
+ return 0;
+}
+
+SEC("?syscall")
+__description("syscall: kfunc unaligned access ctx with fixed offset")
+__success
+int syscall_ctx_kfunc_unaligned_fixed_off(void *ctx)
+{
+ char *p = ctx;
+
+ p += 2;
+ bpf_kfunc_call_test_mem_len_pass1(p, 4);
+ return 0;
+}
+
+SEC("?syscall")
+__description("syscall: kfunc unaligned access ctx with variable offset")
+__success
+int syscall_ctx_kfunc_unaligned_var_off(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off &= 0xfc;
+ off += 2;
+ p += off;
+ bpf_kfunc_call_test_mem_len_pass1(p, 4);
+ return 0;
+}
+
+SEC("?syscall")
+__description("syscall: reject kfunc ctx access past U16_MAX with fixed offset")
+__failure __msg("outside of the allowed memory range")
+int syscall_ctx_kfunc_u16_max_fixed_off(void *ctx)
+{
+ char *p = ctx;
+
+ p += 65535;
+ bpf_kfunc_call_test_mem_len_pass1(p, 4);
+ return 0;
+}
+
+SEC("?syscall")
+__description("syscall: reject kfunc ctx access past U16_MAX with variable offset")
+__failure __msg("outside of the allowed memory range")
+int syscall_ctx_kfunc_u16_max_var_off(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off &= 0xffff;
+ off += 1;
+ p += off;
+ bpf_kfunc_call_test_mem_len_pass1(p, 4);
+ return 0;
+}
+
+SEC("?syscall")
+__description("syscall: kfunc access zero-sized ctx")
+__success
+int syscall_ctx_kfunc_zero_sized(void *ctx)
+{
+ bpf_kfunc_call_test_mem_len_pass1(ctx, 0);
+ return 0;
+}
+
+/*
+ * For non-syscall program types without convert_ctx_access, direct ctx
+ * dereference is still allowed after adding a fixed offset, while variable
+ * and negative direct accesses reject.
+ *
+ * Passing ctx as a helper or kfunc memory argument is only permitted for
+ * syscall programs, so the helper and kfunc cases below validate rejection
+ * for non-syscall ctx pointers at fixed, variable, and zero-sized accesses.
+ */
+#define no_rewrite_ctx_access(type, name, off, load_t) \
+ SEC("?" type) \
+ __description(type ": read ctx at fixed offset") \
+ __success \
+ int no_rewrite_##name##_fixed(void *ctx) \
+ { \
+ char *p = ctx; \
+ volatile load_t val; \
+ \
+ val = *(load_t *)(p + off); \
+ (void)val; \
+ return 0; \
+ } \
+ SEC("?" type) \
+ __description(type ": reject variable offset ctx access") \
+ __failure __msg("variable ctx access var_off=") \
+ int no_rewrite_##name##_var(void *ctx) \
+ { \
+ __u64 off_var = bpf_get_prandom_u32(); \
+ char *p = ctx; \
+ \
+ off_var &= 4; \
+ p += off_var; \
+ return *(load_t *)p; \
+ } \
+ SEC("?" type) \
+ __description(type ": reject negative offset ctx access") \
+ __failure __msg("invalid bpf_context access") \
+ int no_rewrite_##name##_neg(void *ctx) \
+ { \
+ char *p = ctx; \
+ \
+ p -= 612; \
+ return *(load_t *)p; \
+ } \
+ SEC("?" type) \
+ __description(type ": reject helper read ctx at fixed offset") \
+ __failure __msg("dereference of modified ctx ptr") \
+ int no_rewrite_##name##_helper_read_fixed(void *ctx) \
+ { \
+ char *p = ctx; \
+ \
+ p += off; \
+ return bpf_strncmp(p, 4, ctx_strncmp_target); \
+ } \
+ SEC("?" type) \
+ __description(type ": reject helper write ctx at fixed offset") \
+ __failure __msg("dereference of modified ctx ptr") \
+ int no_rewrite_##name##_helper_write_fixed(void *ctx) \
+ { \
+ char *p = ctx; \
+ \
+ p += off; \
+ return bpf_probe_read_kernel(p, 4, 0); \
+ } \
+ SEC("?" type) \
+ __description(type ": reject helper read ctx with variable offset") \
+ __failure __msg("variable ctx access var_off=") \
+ int no_rewrite_##name##_helper_read_var(void *ctx) \
+ { \
+ __u64 off_var = bpf_get_prandom_u32(); \
+ char *p = ctx; \
+ \
+ off_var &= 4; \
+ p += off_var; \
+ return bpf_strncmp(p, 4, ctx_strncmp_target); \
+ } \
+ SEC("?" type) \
+ __description(type ": reject helper write ctx with variable offset") \
+ __failure __msg("variable ctx access var_off=") \
+ int no_rewrite_##name##_helper_write_var(void *ctx) \
+ { \
+ __u64 off_var = bpf_get_prandom_u32(); \
+ char *p = ctx; \
+ \
+ off_var &= 4; \
+ p += off_var; \
+ return bpf_probe_read_kernel(p, 4, 0); \
+ } \
+ SEC("?" type) \
+ __description(type ": reject helper read zero-sized ctx access") \
+ __failure __msg("R4 type=ctx expected=fp") \
+ int no_rewrite_##name##_helper_read_zero(void *ctx) \
+ { \
+ return bpf_snprintf(0, 0, ctx_snprintf_fmt, ctx, 0); \
+ } \
+ SEC("?" type) \
+ __description(type ": reject helper write zero-sized ctx access") \
+ __failure __msg("R1 type=ctx expected=fp") \
+ int no_rewrite_##name##_helper_write_zero(void *ctx) \
+ { \
+ return bpf_probe_read_kernel(ctx, 0, 0); \
+ } \
+ SEC("?" type) \
+ __description(type ": reject kfunc ctx at fixed offset") \
+ __failure __msg("dereference of modified ctx ptr") \
+ int no_rewrite_##name##_kfunc_fixed(void *ctx) \
+ { \
+ char *p = ctx; \
+ \
+ p += off; \
+ bpf_kfunc_call_test_mem_len_pass1(p, 4); \
+ return 0; \
+ } \
+ SEC("?" type) \
+ __description(type ": reject kfunc ctx with variable offset") \
+ __failure __msg("variable ctx access var_off=") \
+ int no_rewrite_##name##_kfunc_var(void *ctx) \
+ { \
+ __u64 off_var = bpf_get_prandom_u32(); \
+ char *p = ctx; \
+ \
+ off_var &= 4; \
+ p += off_var; \
+ bpf_kfunc_call_test_mem_len_pass1(p, 4); \
+ return 0; \
+ } \
+ SEC("?" type) \
+ __description(type ": reject kfunc zero-sized ctx access") \
+ __failure __msg("R1 type=ctx expected=fp") \
+ int no_rewrite_##name##_kfunc_zero(void *ctx) \
+ { \
+ bpf_kfunc_call_test_mem_len_pass1(ctx, 0); \
+ return 0; \
+ }
+
+no_rewrite_ctx_access("kprobe", kprobe, 8, u64);
+no_rewrite_ctx_access("tracepoint", tp, 8, u64);
+no_rewrite_ctx_access("raw_tp", raw_tp, 8, u64);
+no_rewrite_ctx_access("raw_tracepoint.w", raw_tp_w, 8, u64);
+no_rewrite_ctx_access("fentry/bpf_modify_return_test", fentry, 8, u64);
+no_rewrite_ctx_access("cgroup/dev", cgroup_dev, 4, u32);
+no_rewrite_ctx_access("netfilter", netfilter, offsetof(struct bpf_nf_ctx, skb), u64);
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_ctx_ptr_param.c b/tools/testing/selftests/bpf/progs/verifier_ctx_ptr_param.c
new file mode 100644
index 000000000000..d5cc8fc01fe6
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_ctx_ptr_param.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Verifier tests for single- and multi-level pointer parameter handling
+ * Copyright (c) 2026 CrowdStrike, Inc.
+ */
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+
+SEC("fentry/bpf_fentry_test_ppvoid")
+__description("fentry/void**: void ** inferred as scalar")
+__success __retval(0)
+__log_level(2)
+__msg("R1=ctx() R2=scalar()")
+__naked void fentry_ppvoid_as_scalar(void)
+{
+ asm volatile (" \
+ r2 = *(u64 *)(r1 + 0); \
+ r0 = 0; \
+ exit; \
+ " ::: __clobber_all);
+}
+
+SEC("fentry/bpf_fentry_test_pppvoid")
+__description("fentry/void***: void *** inferred as scalar")
+__success __retval(0)
+__log_level(2)
+__msg("R1=ctx() R2=scalar()")
+__naked void fentry_pppvoid_as_scalar(void)
+{
+ asm volatile (" \
+ r2 = *(u64 *)(r1 + 0); \
+ r0 = 0; \
+ exit; \
+ " ::: __clobber_all);
+}
+
+SEC("fentry/bpf_fentry_test_ppfile")
+__description("fentry/struct file**: struct file ** inferred as scalar")
+__success __retval(0)
+__log_level(2)
+__msg("R1=ctx() R2=scalar()")
+__naked void fentry_ppfile_as_scalar(void)
+{
+ asm volatile (" \
+ r2 = *(u64 *)(r1 + 0); \
+ r0 = 0; \
+ exit; \
+ " ::: __clobber_all);
+}
+
+SEC("fexit/bpf_fexit_test_ret_ppfile")
+__description("fexit/return struct file**: returned struct file ** inferred as scalar")
+__success __retval(0)
+__log_level(2)
+__msg("R1=ctx() R2=scalar()")
+__naked void fexit_ppfile_as_scalar(void)
+{
+ asm volatile (" \
+ r2 = *(u64 *)(r1 + 0); \
+ r0 = 0; \
+ exit; \
+ " ::: __clobber_all);
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_default_trusted_ptr.c b/tools/testing/selftests/bpf/progs/verifier_default_trusted_ptr.c
new file mode 100644
index 000000000000..fa3b656ad4fb
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_default_trusted_ptr.c
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2026 Google LLC.
+ */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+#include "bpf_misc.h"
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+SEC("syscall")
+__success __retval(0)
+int test_default_trusted_ptr(void *ctx)
+{
+ struct prog_test_member *trusted_ptr;
+
+ trusted_ptr = bpf_kfunc_get_default_trusted_ptr_test();
+ /*
+ * Test BPF kfunc bpf_get_default_trusted_ptr_test() returns a
+ * PTR_TO_BTF_ID | PTR_TRUSTED, therefore it should be accepted when
+ * passed to a BPF kfunc only accepting KF_TRUSTED_ARGS.
+ */
+ bpf_kfunc_put_default_trusted_ptr_test(trusted_ptr);
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c
index 911caa8fd1b7..915a9707298b 100644
--- a/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c
+++ b/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c
@@ -412,7 +412,7 @@ l0_%=: r0 = 0; \
SEC("tc")
__description("direct packet access: test17 (pruning, alignment)")
-__failure __msg("misaligned packet access off 2+0+15+-4 size 4")
+__failure __msg("misaligned packet access off 2+15+-4 size 4")
__flag(BPF_F_STRICT_ALIGNMENT)
__naked void packet_access_test17_pruning_alignment(void)
{
@@ -569,7 +569,7 @@ l0_%=: r0 = 0; \
SEC("tc")
__description("direct packet access: test23 (x += pkt_ptr, 4)")
-__failure __msg("invalid access to packet, off=0 size=8, R5(id=3,off=0,r=0)")
+__failure __msg("invalid access to packet, off=31 size=8, R5(id=3,off=31,r=0)")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void test23_x_pkt_ptr_4(void)
{
@@ -859,4 +859,65 @@ l0_%=: r0 = 1; \
: __clobber_all);
}
+SEC("tc")
+__description("direct packet access: pkt_range cleared after sub with known scalar")
+__failure __msg("invalid access to packet")
+__naked void pkt_range_clear_after_sub(void)
+{
+ asm volatile (" \
+ r9 = *(u32*)(r1 + %[__sk_buff_data]); \
+ r8 = *(u32*)(r1 + %[__sk_buff_data_end]); \
+ r9 += 256; \
+ if r9 >= r8 goto l0_%=; \
+ r0 = 0; \
+ exit; \
+l0_%=: /* r9 has AT_PKT_END (pkt + 256 >= pkt_end) */ \
+ r9 -= 256; \
+ /* \
+ * AT_PKT_END must not survive the arithmetic. \
+ * is_pkt_ptr_branch_taken must validate both \
+ * branches when visiting the next condition. \
+ */ \
+ if r9 < r8 goto l1_%=; \
+ r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u8*)(r9 + 0); \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+ __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end))
+ : __clobber_all);
+}
+
+SEC("tc")
+__description("direct packet access: pkt_range cleared after add with known scalar")
+__failure __msg("invalid access to packet")
+__naked void pkt_range_clear_after_add(void)
+{
+ asm volatile (" \
+ r9 = *(u32*)(r1 + %[__sk_buff_data]); \
+ r8 = *(u32*)(r1 + %[__sk_buff_data_end]); \
+ r9 += 256; \
+ if r9 >= r8 goto l0_%=; \
+ r0 = 0; \
+ exit; \
+l0_%=: /* r9 has AT_PKT_END (pkt + 256 >= pkt_end) */ \
+ r9 += -256; \
+ /* \
+ * Same as sub, but goes through BPF_ADD path. \
+ * AT_PKT_END must not survive the arithmetic. \
+ */ \
+ if r9 < r8 goto l1_%=; \
+ r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u8*)(r9 + 0); \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+ __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end))
+ : __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_div_mod_bounds.c b/tools/testing/selftests/bpf/progs/verifier_div_mod_bounds.c
new file mode 100644
index 000000000000..e814a054d69a
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_div_mod_bounds.c
@@ -0,0 +1,1151 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bpf.h>
+#include <limits.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+/* This file contains unit tests for signed/unsigned division and modulo
+ * operations (with divisor as a constant), focusing on verifying whether
+ * BPF verifier's range tracking module soundly and precisely computes
+ * the results.
+ */
+
+SEC("socket")
+__description("UDIV32, positive divisor")
+__success __retval(0) __log_level(2)
+__msg("w1 /= 3 {{.*}}; R1=scalar(smin=smin32=0,smax=umax=smax32=umax32=3,var_off=(0x0; 0x3))")
+__naked void udiv32_pos_divisor(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ w1 &= 8; \
+ w1 |= 1; \
+ w1 /= 3; \
+ if w1 > 3 goto l0_%=; \
+ r0 = 0; \
+ exit; \
+l0_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("UDIV32, zero divisor")
+__success __retval(0) __log_level(2)
+__msg("w1 /= w2 {{.*}}; R1=0")
+__naked void udiv32_zero_divisor(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ w1 &= 8; \
+ w1 |= 1; \
+ w2 = 0; \
+ w1 /= w2; \
+ if w1 != 0 goto l0_%=; \
+ r0 = 0; \
+ exit; \
+l0_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("UDIV64, positive divisor")
+__success __retval(0) __log_level(2)
+__msg("r1 /= 3 {{.*}}; R1=scalar(smin=smin32=0,smax=umax=smax32=umax32=3,var_off=(0x0; 0x3))")
+__naked void udiv64_pos_divisor(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ r1 &= 8; \
+ r1 |= 1; \
+ r1 /= 3; \
+ if r1 > 3 goto l0_%=; \
+ r0 = 0; \
+ exit; \
+l0_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("UDIV64, zero divisor")
+__success __retval(0) __log_level(2)
+__msg("r1 /= r2 {{.*}}; R1=0")
+__naked void udiv64_zero_divisor(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ r1 &= 8; \
+ r1 |= 1; \
+ r2 = 0; \
+ r1 /= r2; \
+ if r1 != 0 goto l0_%=; \
+ r0 = 0; \
+ exit; \
+l0_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SDIV32, positive divisor, positive dividend")
+__success __retval(0) __log_level(2)
+__msg("w1 s/= 3 {{.*}}; R1=scalar(smin=umin=smin32=umin32=2,smax=umax=smax32=umax32=3,var_off=(0x2; 0x1))")
+__naked void sdiv32_pos_divisor_1(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ if w1 s< 8 goto l0_%=; \
+ if w1 s> 10 goto l0_%=; \
+ w1 s/= 3; \
+ if w1 s< 2 goto l1_%=; \
+ if w1 s> 3 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SDIV32, positive divisor, negative dividend")
+__success __retval(0) __log_level(2)
+__msg("w1 s/= 3 {{.*}}; R1=scalar(smin=umin=umin32=0xfffffffd,smax=umax=umax32=0xfffffffe,smin32=-3,smax32=-2,var_off=(0xfffffffc; 0x3))")
+__naked void sdiv32_pos_divisor_2(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ if w1 s> -8 goto l0_%=; \
+ if w1 s< -10 goto l0_%=; \
+ w1 s/= 3; \
+ if w1 s< -3 goto l1_%=; \
+ if w1 s> -2 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SDIV32, positive divisor, mixed sign dividend")
+__success __retval(0) __log_level(2)
+__msg("w1 s/= 3 {{.*}}; R1=scalar(smin=0,smax=umax=0xffffffff,smin32=-2,smax32=3,var_off=(0x0; 0xffffffff))")
+__naked void sdiv32_pos_divisor_3(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ if w1 s< -8 goto l0_%=; \
+ if w1 s> 10 goto l0_%=; \
+ w1 s/= 3; \
+ if w1 s< -2 goto l1_%=; \
+ if w1 s> 3 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SDIV32, negative divisor, positive dividend")
+__success __retval(0) __log_level(2)
+__msg("w1 s/= -3 {{.*}}; R1=scalar(smin=umin=umin32=0xfffffffd,smax=umax=umax32=0xfffffffe,smin32=-3,smax32=-2,var_off=(0xfffffffc; 0x3))")
+__naked void sdiv32_neg_divisor_1(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ if w1 s< 8 goto l0_%=; \
+ if w1 s> 10 goto l0_%=; \
+ w1 s/= -3; \
+ if w1 s< -3 goto l1_%=; \
+ if w1 s> -2 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SDIV32, negative divisor, positive dividend")
+__success __retval(0) __log_level(2)
+__msg("w1 s/= -3 {{.*}}; R1=scalar(smin=umin=smin32=umin32=2,smax=umax=smax32=umax32=3,var_off=(0x2; 0x1))")
+__naked void sdiv32_neg_divisor_2(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ if w1 s> -8 goto l0_%=; \
+ if w1 s< -10 goto l0_%=; \
+ w1 s/= -3; \
+ if w1 s< 2 goto l1_%=; \
+ if w1 s> 3 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SDIV32, negative divisor, mixed sign dividend")
+__success __retval(0) __log_level(2)
+__msg("w1 s/= -3 {{.*}}; R1=scalar(smin=0,smax=umax=0xffffffff,smin32=-3,smax32=2,var_off=(0x0; 0xffffffff))")
+__naked void sdiv32_neg_divisor_3(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ if w1 s< -8 goto l0_%=; \
+ if w1 s> 10 goto l0_%=; \
+ w1 s/= -3; \
+ if w1 s< -3 goto l1_%=; \
+ if w1 s> 2 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SDIV32, zero divisor")
+__success __retval(0) __log_level(2)
+__msg("w1 s/= w2 {{.*}}; R1=0")
+__naked void sdiv32_zero_divisor(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ w1 &= 8; \
+ w1 |= 1; \
+ w2 = 0; \
+ w1 s/= w2; \
+ if w1 != 0 goto l0_%=; \
+ r0 = 0; \
+ exit; \
+l0_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SDIV32, overflow (S32_MIN/-1)")
+__success __retval(0) __log_level(2)
+__msg("w1 s/= -1 {{.*}}; R1=scalar(smin=0,smax=umax=0xffffffff,var_off=(0x0; 0xffffffff))")
+__naked void sdiv32_overflow_1(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ w2 = %[int_min]; \
+ w2 += 10; \
+ if w1 s> w2 goto l0_%=; \
+ w1 s/= -1; \
+ r2 = r1; \
+l0_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm_const(int_min, INT_MIN),
+ __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SDIV32, overflow (S32_MIN/-1), constant dividend")
+__success __retval(0) __log_level(2)
+__msg("w1 s/= -1 {{.*}}; R1=0x80000000")
+__naked void sdiv32_overflow_2(void)
+{
+ asm volatile (" \
+ w1 = %[int_min]; \
+ w1 s/= -1; \
+ if w1 != %[int_min] goto l0_%=; \
+ r0 = 0; \
+ exit; \
+l0_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm_const(int_min, INT_MIN)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SDIV64, positive divisor, positive dividend")
+__success __retval(0) __log_level(2)
+__msg("r1 s/= 3 {{.*}}; R1=scalar(smin=umin=smin32=umin32=2,smax=umax=smax32=umax32=3,var_off=(0x2; 0x1))")
+__naked void sdiv64_pos_divisor_1(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ if r1 s< 8 goto l0_%=; \
+ if r1 s> 10 goto l0_%=; \
+ r1 s/= 3; \
+ if r1 s< 2 goto l1_%=; \
+ if r1 s> 3 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SDIV64, positive divisor, negative dividend")
+__success __retval(0) __log_level(2)
+__msg("r1 s/= 3 {{.*}}; R1=scalar(smin=smin32=-3,smax=smax32=-2,umin=0xfffffffffffffffd,umax=0xfffffffffffffffe,umin32=0xfffffffd,umax32=0xfffffffe,var_off=(0xfffffffffffffffc; 0x3))")
+__naked void sdiv64_pos_divisor_2(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ if r1 s> -8 goto l0_%=; \
+ if r1 s< -10 goto l0_%=; \
+ r1 s/= 3; \
+ if r1 s< -3 goto l1_%=; \
+ if r1 s> -2 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SDIV64, positive divisor, mixed sign dividend")
+__success __retval(0) __log_level(2)
+__msg("r1 s/= 3 {{.*}}; R1=scalar(smin=smin32=-2,smax=smax32=3)")
+__naked void sdiv64_pos_divisor_3(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ if r1 s< -8 goto l0_%=; \
+ if r1 s> 10 goto l0_%=; \
+ r1 s/= 3; \
+ if r1 s< -2 goto l1_%=; \
+ if r1 s> 3 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SDIV64, negative divisor, positive dividend")
+__success __retval(0) __log_level(2)
+__msg("r1 s/= -3 {{.*}}; R1=scalar(smin=smin32=-3,smax=smax32=-2,umin=0xfffffffffffffffd,umax=0xfffffffffffffffe,umin32=0xfffffffd,umax32=0xfffffffe,var_off=(0xfffffffffffffffc; 0x3))")
+__naked void sdiv64_neg_divisor_1(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ if r1 s< 8 goto l0_%=; \
+ if r1 s> 10 goto l0_%=; \
+ r1 s/= -3; \
+ if r1 s< -3 goto l1_%=; \
+ if r1 s> -2 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SDIV64, negative divisor, positive dividend")
+__success __retval(0) __log_level(2)
+__msg("r1 s/= -3 {{.*}}; R1=scalar(smin=umin=smin32=umin32=2,smax=umax=smax32=umax32=3,var_off=(0x2; 0x1))")
+__naked void sdiv64_neg_divisor_2(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ if r1 s> -8 goto l0_%=; \
+ if r1 s< -10 goto l0_%=; \
+ r1 s/= -3; \
+ if r1 s< 2 goto l1_%=; \
+ if r1 s> 3 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SDIV64, negative divisor, mixed sign dividend")
+__success __retval(0) __log_level(2)
+__msg("r1 s/= -3 {{.*}}; R1=scalar(smin=smin32=-3,smax=smax32=2)")
+__naked void sdiv64_neg_divisor_3(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ if r1 s< -8 goto l0_%=; \
+ if r1 s> 10 goto l0_%=; \
+ r1 s/= -3; \
+ if r1 s< -3 goto l1_%=; \
+ if r1 s> 2 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SDIV64, zero divisor")
+__success __retval(0) __log_level(2)
+__msg("r1 s/= r2 {{.*}}; R1=0")
+__naked void sdiv64_zero_divisor(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ r1 &= 8; \
+ r1 |= 1; \
+ r2 = 0; \
+ r1 s/= r2; \
+ if r1 != 0 goto l0_%=; \
+ r0 = 0; \
+ exit; \
+l0_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SDIV64, overflow (S64_MIN/-1)")
+__success __retval(0) __log_level(2)
+__msg("r1 s/= -1 {{.*}}; R1=scalar()")
+__naked void sdiv64_overflow_1(void)
+{
+ asm volatile (" \
+ call %[bpf_ktime_get_ns]; \
+ r1 = r0; \
+ r2 = %[llong_min] ll; \
+ r2 += 10; \
+ if r1 s> r2 goto l0_%=; \
+ r1 s/= -1; \
+ r2 = r1; \
+l0_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm_const(llong_min, LLONG_MIN),
+ __imm(bpf_ktime_get_ns)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SDIV64, overflow (S64_MIN/-1), constant dividend")
+__success __retval(0) __log_level(2)
+__msg("r1 s/= -1 {{.*}}; R1=0x8000000000000000")
+__naked void sdiv64_overflow_2(void)
+{
+ asm volatile (" \
+ r1 = %[llong_min] ll; \
+ r1 s/= -1; \
+ r2 = %[llong_min] ll; \
+ if r1 != r2 goto l0_%=; \
+ r0 = 0; \
+ exit; \
+l0_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm_const(llong_min, LLONG_MIN)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("UMOD32, positive divisor")
+__success __retval(0) __log_level(2)
+__msg("w1 %= 3 {{.*}}; R1=scalar(smin=smin32=0,smax=umax=smax32=umax32=2,var_off=(0x0; 0x3))")
+__naked void umod32_pos_divisor(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ w1 &= 8; \
+ w1 |= 1; \
+ w1 %%= 3; \
+ if w1 > 3 goto l0_%=; \
+ r0 = 0; \
+ exit; \
+l0_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("UMOD32, positive divisor, small dividend")
+__success __retval(0) __log_level(2)
+__msg("w1 %= 10 {{.*}}; R1=scalar(smin=umin=smin32=umin32=1,smax=umax=smax32=umax32=9,var_off=(0x1; 0x8))")
+__naked void umod32_pos_divisor_unchanged(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ w1 &= 8; \
+ w1 |= 1; \
+ w1 %%= 10; \
+ if w1 < 1 goto l0_%=; \
+ if w1 > 9 goto l0_%=; \
+ if w1 & 1 != 1 goto l0_%=; \
+ r0 = 0; \
+ exit; \
+l0_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("UMOD32, zero divisor")
+__success __retval(0) __log_level(2)
+__msg("w1 %= w2 {{.*}}; R1=scalar(smin=umin=smin32=umin32=1,smax=umax=smax32=umax32=9,var_off=(0x1; 0x8))")
+__naked void umod32_zero_divisor(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ w1 &= 8; \
+ w1 |= 1; \
+ w2 = 0; \
+ w1 %%= w2; \
+ if w1 < 1 goto l0_%=; \
+ if w1 > 9 goto l0_%=; \
+ if w1 & 1 != 1 goto l0_%=; \
+ r0 = 0; \
+ exit; \
+l0_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("UMOD64, positive divisor")
+__success __retval(0) __log_level(2)
+__msg("r1 %= 3 {{.*}}; R1=scalar(smin=smin32=0,smax=umax=smax32=umax32=2,var_off=(0x0; 0x3))")
+__naked void umod64_pos_divisor(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ r1 &= 8; \
+ r1 |= 1; \
+ r1 %%= 3; \
+ if r1 > 3 goto l0_%=; \
+ r0 = 0; \
+ exit; \
+l0_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("UMOD64, positive divisor, small dividend")
+__success __retval(0) __log_level(2)
+__msg("r1 %= 10 {{.*}}; R1=scalar(smin=umin=smin32=umin32=1,smax=umax=smax32=umax32=9,var_off=(0x1; 0x8))")
+__naked void umod64_pos_divisor_unchanged(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ r1 &= 8; \
+ r1 |= 1; \
+ r1 %%= 10; \
+ if r1 < 1 goto l0_%=; \
+ if r1 > 9 goto l0_%=; \
+ if r1 & 1 != 1 goto l0_%=; \
+ r0 = 0; \
+ exit; \
+l0_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("UMOD64, zero divisor")
+__success __retval(0) __log_level(2)
+__msg("r1 %= r2 {{.*}}; R1=scalar(smin=umin=smin32=umin32=1,smax=umax=smax32=umax32=9,var_off=(0x1; 0x8))")
+__naked void umod64_zero_divisor(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ r1 &= 8; \
+ r1 |= 1; \
+ r2 = 0; \
+ r1 %%= r2; \
+ if r1 < 1 goto l0_%=; \
+ if r1 > 9 goto l0_%=; \
+ if r1 & 1 != 1 goto l0_%=; \
+ r0 = 0; \
+ exit; \
+l0_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD32, positive divisor, positive dividend")
+__success __retval(0) __log_level(2)
+__msg("w1 s%= 3 {{.*}}; R1=scalar(smin=smin32=0,smax=umax=smax32=umax32=2,var_off=(0x0; 0x3))")
+__naked void smod32_pos_divisor_1(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ if w1 s< 8 goto l0_%=; \
+ if w1 s> 10 goto l0_%=; \
+ w1 s%%= 3; \
+ if w1 s< 0 goto l1_%=; \
+ if w1 s> 2 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD32, positive divisor, negative dividend")
+__success __retval(0) __log_level(2)
+__msg("w1 s%= 3 {{.*}}; R1=scalar(smin=0,smax=umax=0xffffffff,smin32=-2,smax32=0,var_off=(0x0; 0xffffffff))")
+__naked void smod32_pos_divisor_2(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ if w1 s> -8 goto l0_%=; \
+ if w1 s< -10 goto l0_%=; \
+ w1 s%%= 3; \
+ if w1 s< -2 goto l1_%=; \
+ if w1 s> 0 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD32, positive divisor, mixed sign dividend")
+__success __retval(0) __log_level(2)
+__msg("w1 s%= 3 {{.*}}; R1=scalar(smin=0,smax=umax=0xffffffff,smin32=-2,smax32=2,var_off=(0x0; 0xffffffff))")
+__naked void smod32_pos_divisor_3(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ if w1 s< -8 goto l0_%=; \
+ if w1 s> 10 goto l0_%=; \
+ w1 s%%= 3; \
+ if w1 s< -2 goto l1_%=; \
+ if w1 s> 2 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD32, positive divisor, small dividend")
+__success __retval(0) __log_level(2)
+__msg("w1 s%= 11 {{.*}}; R1=scalar(smin=0,smax=umax=0xffffffff,smin32=-8,smax32=10,var_off=(0x0; 0xffffffff))")
+__naked void smod32_pos_divisor_unchanged(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ if w1 s< -8 goto l0_%=; \
+ if w1 s> 10 goto l0_%=; \
+ w1 s%%= 11; \
+ if w1 s< -8 goto l1_%=; \
+ if w1 s> 10 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD32, negative divisor, positive dividend")
+__success __retval(0) __log_level(2)
+__msg("w1 s%= -3 {{.*}}; R1=scalar(smin=smin32=0,smax=umax=smax32=umax32=2,var_off=(0x0; 0x3))")
+__naked void smod32_neg_divisor_1(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ if w1 s< 8 goto l0_%=; \
+ if w1 s> 10 goto l0_%=; \
+ w1 s%%= -3; \
+ if w1 s< 0 goto l1_%=; \
+ if w1 s> 2 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD32, negative divisor, negative dividend")
+__success __retval(0) __log_level(2)
+__msg("w1 s%= -3 {{.*}}; R1=scalar(smin=0,smax=umax=0xffffffff,smin32=-2,smax32=0,var_off=(0x0; 0xffffffff))")
+__naked void smod32_neg_divisor_2(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ if w1 s> -8 goto l0_%=; \
+ if w1 s< -10 goto l0_%=; \
+ w1 s%%= -3; \
+ if w1 s< -2 goto l1_%=; \
+ if w1 s> 0 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD32, negative divisor, mixed sign dividend")
+__success __retval(0) __log_level(2)
+__msg("w1 s%= -3 {{.*}}; R1=scalar(smin=0,smax=umax=0xffffffff,smin32=-2,smax32=2,var_off=(0x0; 0xffffffff))")
+__naked void smod32_neg_divisor_3(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ if w1 s< -8 goto l0_%=; \
+ if w1 s> 10 goto l0_%=; \
+ w1 s%%= -3; \
+ if w1 s< -2 goto l1_%=; \
+ if w1 s> 2 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD32, negative divisor, small dividend")
+__success __retval(0) __log_level(2)
+__msg("w1 s%= -11 {{.*}}; R1=scalar(smin=0,smax=umax=0xffffffff,smin32=-8,smax32=10,var_off=(0x0; 0xffffffff))")
+__naked void smod32_neg_divisor_unchanged(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ if w1 s< -8 goto l0_%=; \
+ if w1 s> 10 goto l0_%=; \
+ w1 s%%= -11; \
+ if w1 s< -8 goto l1_%=; \
+ if w1 s> 10 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD32, zero divisor")
+__success __retval(0) __log_level(2)
+__msg("w1 s%= w2 {{.*}}; R1=scalar(smin=0,smax=umax=0xffffffff,smin32=-8,smax32=10,var_off=(0x0; 0xffffffff))")
+__naked void smod32_zero_divisor(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ if w1 s< -8 goto l0_%=; \
+ if w1 s> 10 goto l0_%=; \
+ w2 = 0; \
+ w1 s%%= w2; \
+ if w1 s< -8 goto l1_%=; \
+ if w1 s> 10 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD32, overflow (S32_MIN%-1)")
+__success __retval(0) __log_level(2)
+__msg("w1 s%= -1 {{.*}}; R1=0")
+__naked void smod32_overflow_1(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ w2 = %[int_min]; \
+ w2 += 10; \
+ if w1 s> w2 goto l0_%=; \
+ w1 s%%= -1; \
+ if w1 != 0 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm_const(int_min, INT_MIN),
+ __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD32, overflow (S32_MIN%-1), constant dividend")
+__success __retval(0) __log_level(2)
+__msg("w1 s%= -1 {{.*}}; R1=0")
+__naked void smod32_overflow_2(void)
+{
+ asm volatile (" \
+ w1 = %[int_min]; \
+ w1 s%%= -1; \
+ if w1 != 0 goto l0_%=; \
+ r0 = 0; \
+ exit; \
+l0_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm_const(int_min, INT_MIN)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD64, positive divisor, positive dividend")
+__success __retval(0) __log_level(2)
+__msg("r1 s%= 3 {{.*}}; R1=scalar(smin=smin32=0,smax=umax=smax32=umax32=2,var_off=(0x0; 0x3))")
+__naked void smod64_pos_divisor_1(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ if r1 s< 8 goto l0_%=; \
+ if r1 s> 10 goto l0_%=; \
+ r1 s%%= 3; \
+ if r1 s< 0 goto l1_%=; \
+ if r1 s> 2 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD64, positive divisor, negative dividend")
+__success __retval(0) __log_level(2)
+__msg("r1 s%= 3 {{.*}}; R1=scalar(smin=smin32=-2,smax=smax32=0)")
+__naked void smod64_pos_divisor_2(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ if r1 s> -8 goto l0_%=; \
+ if r1 s< -10 goto l0_%=; \
+ r1 s%%= 3; \
+ if r1 s< -2 goto l1_%=; \
+ if r1 s> 0 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD64, positive divisor, mixed sign dividend")
+__success __retval(0) __log_level(2)
+__msg("r1 s%= 3 {{.*}}; R1=scalar(smin=smin32=-2,smax=smax32=2)")
+__naked void smod64_pos_divisor_3(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ if r1 s< -8 goto l0_%=; \
+ if r1 s> 10 goto l0_%=; \
+ r1 s%%= 3; \
+ if r1 s< -2 goto l1_%=; \
+ if r1 s> 2 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD64, positive divisor, small dividend")
+__success __retval(0) __log_level(2)
+__msg("r1 s%= 11 {{.*}}; R1=scalar(smin=smin32=-8,smax=smax32=10)")
+__naked void smod64_pos_divisor_unchanged(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ if r1 s< -8 goto l0_%=; \
+ if r1 s> 10 goto l0_%=; \
+ r1 s%%= 11; \
+ if r1 s< -8 goto l1_%=; \
+ if r1 s> 10 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD64, negative divisor, positive dividend")
+__success __retval(0) __log_level(2)
+__msg("r1 s%= -3 {{.*}}; R1=scalar(smin=smin32=0,smax=umax=smax32=umax32=2,var_off=(0x0; 0x3))")
+__naked void smod64_neg_divisor_1(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ if r1 s< 8 goto l0_%=; \
+ if r1 s> 10 goto l0_%=; \
+ r1 s%%= -3; \
+ if r1 s< 0 goto l1_%=; \
+ if r1 s> 2 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD64, negative divisor, negative dividend")
+__success __retval(0) __log_level(2)
+__msg("r1 s%= -3 {{.*}}; R1=scalar(smin=smin32=-2,smax=smax32=0)")
+__naked void smod64_neg_divisor_2(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ if r1 s> -8 goto l0_%=; \
+ if r1 s< -10 goto l0_%=; \
+ r1 s%%= -3; \
+ if r1 s< -2 goto l1_%=; \
+ if r1 s> 0 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD64, negative divisor, mixed sign dividend")
+__success __retval(0) __log_level(2)
+__msg("r1 s%= -3 {{.*}}; R1=scalar(smin=smin32=-2,smax=smax32=2)")
+__naked void smod64_neg_divisor_3(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ if r1 s< -8 goto l0_%=; \
+ if r1 s> 10 goto l0_%=; \
+ r1 s%%= -3; \
+ if r1 s< -2 goto l1_%=; \
+ if r1 s> 2 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD64, negative divisor, small dividend")
+__success __retval(0) __log_level(2)
+__msg("r1 s%= -11 {{.*}}; R1=scalar(smin=smin32=-8,smax=smax32=10)")
+__naked void smod64_neg_divisor_unchanged(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ if r1 s< -8 goto l0_%=; \
+ if r1 s> 10 goto l0_%=; \
+ r1 s%%= -11; \
+ if r1 s< -8 goto l1_%=; \
+ if r1 s> 10 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD64, zero divisor")
+__success __retval(0) __log_level(2)
+__msg("r1 s%= r2 {{.*}}; R1=scalar(smin=smin32=-8,smax=smax32=10)")
+__naked void smod64_zero_divisor(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ if r1 s< -8 goto l0_%=; \
+ if r1 s> 10 goto l0_%=; \
+ r2 = 0; \
+ r1 s%%= r2; \
+ if r1 s< -8 goto l1_%=; \
+ if r1 s> 10 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD64, overflow (S64_MIN%-1)")
+__success __retval(0) __log_level(2)
+__msg("r1 s%= -1 {{.*}}; R1=0")
+__naked void smod64_overflow_1(void)
+{
+ asm volatile (" \
+ call %[bpf_ktime_get_ns]; \
+ r1 = r0; \
+ r2 = %[llong_min] ll; \
+ r2 += 10; \
+ if r1 s> r2 goto l0_%=; \
+ r1 s%%= -1; \
+ if r1 != 0 goto l1_%=; \
+l0_%=: r0 = 0; \
+ exit; \
+l1_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm_const(llong_min, LLONG_MIN),
+ __imm(bpf_ktime_get_ns)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD64, overflow (S64_MIN%-1), constant dividend")
+__success __retval(0) __log_level(2)
+__msg("r1 s%= -1 {{.*}}; R1=0")
+__naked void smod64_overflow_2(void)
+{
+ asm volatile (" \
+ r1 = %[llong_min] ll; \
+ r1 s%%= -1; \
+ if r1 != 0 goto l0_%=; \
+ r0 = 0; \
+ exit; \
+l0_%=: r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm_const(llong_min, LLONG_MIN)
+ : __clobber_all);
+}
diff --git a/tools/testing/selftests/bpf/progs/verifier_flow_keys.c b/tools/testing/selftests/bpf/progs/verifier_flow_keys.c
new file mode 100644
index 000000000000..d780a36a6e9a
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_flow_keys.c
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Bounds checks for PTR_TO_FLOW_KEYS pointer arithmetic. */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+/* sizeof(struct bpf_flow_keys) is well under 4096, so +0x1000 is OOB. */
+
+SEC("flow_dissector")
+__description("flow_keys: in-bounds constant pointer arithmetic accepted")
+__success
+__naked void flow_keys_const_inbounds(void)
+{
+ asm volatile (" \
+ r1 = *(u64 *)(r1 + %[flow_keys]); \
+ r1 += 8; \
+ r0 = *(u64 *)(r1 + 0); \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm_const(flow_keys, offsetof(struct __sk_buff, flow_keys))
+ : __clobber_all);
+}
+
+SEC("flow_dissector")
+__description("flow_keys: OOB via constant pointer arithmetic rejected")
+__failure __msg("invalid access to flow keys off=4096 size=8")
+__naked void flow_keys_const_oob_read(void)
+{
+ asm volatile (" \
+ r1 = *(u64 *)(r1 + %[flow_keys]); \
+ r1 += 4096; \
+ r0 = *(u64 *)(r1 + 0); \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm_const(flow_keys, offsetof(struct __sk_buff, flow_keys))
+ : __clobber_all);
+}
+
+SEC("flow_dissector")
+__description("flow_keys: OOB write via constant pointer arithmetic rejected")
+__failure __msg("invalid access to flow keys off=4096 size=8")
+__naked void flow_keys_const_oob_write(void)
+{
+ asm volatile (" \
+ r1 = *(u64 *)(r1 + %[flow_keys]); \
+ r1 += 4096; \
+ r2 = 0; \
+ *(u64 *)(r1 + 0) = r2; \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm_const(flow_keys, offsetof(struct __sk_buff, flow_keys))
+ : __clobber_all);
+}
+
+/* Equivalent OOB expressed directly in insn->off; this form was always
+ * rejected and is kept to show both forms now share one diagnostic.
+ */
+SEC("flow_dissector")
+__description("flow_keys: OOB via insn->off rejected")
+__failure __msg("invalid access to flow keys off=4096 size=8")
+__naked void flow_keys_insn_off_oob(void)
+{
+ asm volatile (" \
+ r1 = *(u64 *)(r1 + %[flow_keys]); \
+ r0 = *(u64 *)(r1 + 4096); \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm_const(flow_keys, offsetof(struct __sk_buff, flow_keys))
+ : __clobber_all);
+}
+
+SEC("flow_dissector")
+__description("flow_keys: variable pointer arithmetic rejected")
+__failure __msg("R1 pointer arithmetic on flow_keys prohibited")
+__naked void flow_keys_var_read(void)
+{
+ asm volatile (" \
+ r6 = r1; \
+ call %[bpf_get_prandom_u32]; \
+ r0 &= 0xFFFF; \
+ r1 = *(u64 *)(r6 + %[flow_keys]); \
+ r1 += r0; \
+ r0 = *(u64 *)(r1 + 0); \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm_const(flow_keys, offsetof(struct __sk_buff, flow_keys)),
+ __imm(bpf_get_prandom_u32)
+ : __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 1204fbc58178..dcc2dd46751a 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;
@@ -72,7 +96,7 @@ int trusted_task_arg_nonnull_fail1(void *ctx)
SEC("?tp_btf/task_newtask")
__failure __log_level(2)
-__msg("R1 type=ptr_or_null_ expected=ptr_, trusted_ptr_, rcu_ptr_")
+__msg("R1 type=trusted_ptr_or_null_ expected=ptr_, trusted_ptr_, rcu_ptr_")
__msg("Caller passes invalid args into func#1 ('subprog_trusted_task_nonnull')")
int trusted_task_arg_nonnull_fail2(void *ctx)
{
@@ -153,7 +177,7 @@ __weak int subprog_trusted_destroy(struct task_struct *task __arg_trusted)
SEC("?tp_btf/task_newtask")
__failure __log_level(2)
-__msg("release kernel function bpf_task_release expects refcounted PTR_TO_BTF_ID")
+__msg("release kfunc bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
int BPF_PROG(trusted_destroy_fail, struct task_struct *task, u64 clone_flags)
{
return subprog_trusted_destroy(task);
@@ -287,6 +311,25 @@ int trusted_to_untrusted_mem(void *ctx)
return subprog_void_untrusted(bpf_get_current_task_btf());
}
+__weak int subprog_write_mem_arg(int *p)
+{
+ if (!p)
+ return 0;
+
+ *p = 42;
+ return 0;
+}
+
+SEC("?tp_btf/task_newtask")
+__failure
+__msg("only read is supported")
+int trusted_btf_field_to_writable_mem(void *ctx)
+{
+ struct task_struct *task = bpf_get_current_task_btf();
+
+ return subprog_write_mem_arg(&task->prio);
+}
+
SEC("tp_btf/sys_enter")
__success
int anything_to_untrusted_mem(void *ctx)
@@ -307,4 +350,57 @@ int anything_to_untrusted_mem(void *ctx)
return 0;
}
+struct pkt_arg {
+ __u64 x;
+ __u8 pad[56];
+};
+
+__weak int subprog_pkt_ptr_no_change(struct pkt_arg *p)
+{
+ if (!p)
+ return 0;
+
+ return p->x;
+}
+
+SEC("?tc")
+__success
+int pkt_ptr_to_global_mem_arg_no_change(struct __sk_buff *skb)
+{
+ void *data = (void *)(long)skb->data;
+ void *data_end = (void *)(long)skb->data_end;
+ struct pkt_arg *p = data;
+
+ if ((void *)(p + 1) > data_end)
+ return 0;
+
+ return subprog_pkt_ptr_no_change(p);
+}
+
+__weak int subprog_pkt_ptr_changes_data(struct __sk_buff *skb __arg_ctx,
+ struct pkt_arg *p)
+{
+ if (!p)
+ return 0;
+
+ bpf_skb_pull_data(skb, 0);
+ return p->x;
+}
+
+SEC("?tc")
+__failure __log_level(2)
+__msg("R2 is a packet pointer, but func#{{[0-9]+}} may change packet data")
+__msg("Caller passes invalid args into func#{{[0-9]+}} ('subprog_pkt_ptr_changes_data')")
+int pkt_ptr_to_global_mem_arg_changes_data(struct __sk_buff *skb)
+{
+ void *data = (void *)(long)skb->data;
+ void *data_end = (void *)(long)skb->data_end;
+ struct pkt_arg *p = data;
+
+ if ((void *)(p + 1) > data_end)
+ return 0;
+
+ return subprog_pkt_ptr_changes_data(skb, p);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c b/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
index 20904cd2baa2..966f49348787 100644
--- a/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
+++ b/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
@@ -46,12 +46,21 @@ __noinline long global_dead(void)
}
SEC("?raw_tp")
-__success __log_level(2)
+__success __log_level(6)
/* main prog is validated completely first */
__msg("('global_calls_good_only') is global and assumed valid.")
/* eventually global_good() is transitively validated as well */
__msg("Validating global_good() func")
__msg("('global_good') is safe for any args that match its prototype")
+__msg("subprog 0 (chained_global_func_calls_success) main insns_self 7 insns_total 7 stack")
+__msg("subprog {{[0-9]+}} (global_calls_good_only) global insns_self 2 insns_total 2 stack")
+#if defined(__BPF_CPU_VERSION__) && __BPF_CPU_VERSION__ >= 4
+__msg("subprog {{[0-9]+}} (global_good) global insns_self 3 insns_total 3 stack")
+__msg("processed 12 insns")
+#else
+__msg("subprog {{[0-9]+}} (global_good) global insns_self 5 insns_total 5 stack")
+__msg("processed 14 insns")
+#endif
int chained_global_func_calls_success(void)
{
int sum = 0;
@@ -134,7 +143,6 @@ __noinline __weak int subprog_user_anon_mem(user_struct_t *t)
SEC("?tracepoint")
__failure __log_level(2)
-__msg("invalid bpf_context access")
__msg("Caller passes invalid args into func#1 ('subprog_user_anon_mem')")
int anon_user_mem_invalid(void *ctx)
{
@@ -152,6 +160,23 @@ int anon_user_mem_valid(void *ctx)
return subprog_user_anon_mem(&t);
}
+__noinline __weak int subprog_user_anon_mem_huge(int (*p)[0x3fffffff])
+{
+ return p ? (*p)[1] : 0;
+}
+
+SEC("?tracepoint")
+__failure __log_level(2)
+__msg("R1 memory size 4294967292 is too large")
+int anon_user_mem_huge_size_invalid(void *ctx)
+{
+ int (*p)[0x3fffffff];
+ int tiny = 42;
+
+ p = (void *)&tiny;
+ return subprog_user_anon_mem_huge(p) + tiny;
+}
+
__noinline __weak int subprog_nonnull_ptr_good(int *p1 __arg_nonnull, int *p2 __arg_nonnull)
{
return (*p1) * (*p2); /* good, no need for NULL checks */
@@ -168,6 +193,16 @@ int arg_tag_nonnull_ptr_good(void *ctx)
return subprog_nonnull_ptr_good(&x, &y);
}
+SEC("?raw_tp")
+__failure __log_level(2)
+__msg("R1 is expected to be non-NULL")
+int arg_tag_nonnull_ptr_null_bad(void *ctx)
+{
+ int y = 74;
+
+ return subprog_nonnull_ptr_good(NULL, &y);
+}
+
/* this global subprog can be now called from many types of entry progs, each
* with different context type
*/
@@ -358,6 +393,100 @@ int arg_tag_ctx_syscall(void *ctx)
return tracing_subprog_void(ctx) + tracing_subprog_u64(ctx) + tp_whatever(ctx);
}
+__weak int syscall_array_bpf_for(void *ctx __arg_ctx)
+{
+ int *arr = ctx;
+ int i;
+
+ bpf_for(i, 0, 100)
+ arr[i] *= i;
+
+ return 0;
+}
+
+SEC("?syscall")
+__success __log_level(2)
+int arg_tag_ctx_syscall_bpf_for(void *ctx)
+{
+ return syscall_array_bpf_for(ctx);
+}
+
+SEC("syscall")
+__auxiliary
+int syscall_tailcall_target(void *ctx)
+{
+ return syscall_array_bpf_for(ctx);
+}
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PROG_ARRAY);
+ __uint(max_entries, 1);
+ __uint(key_size, sizeof(__u32));
+ __array(values, int (void *));
+} syscall_prog_array SEC(".maps") = {
+ .values = {
+ [0] = (void *)&syscall_tailcall_target,
+ },
+};
+
+SEC("?syscall")
+__success __log_level(2)
+int arg_tag_ctx_syscall_tailcall(void *ctx)
+{
+ bpf_tail_call(ctx, &syscall_prog_array, 0);
+ return 0;
+}
+
+SEC("?syscall")
+__failure __log_level(2)
+__msg("dereference of modified ctx ptr R1 off=8 disallowed")
+int arg_tag_ctx_syscall_tailcall_fixed_off_bad(void *ctx)
+{
+ char *p = ctx;
+
+ p += 8;
+ bpf_tail_call(p, &syscall_prog_array, 0);
+ return 0;
+}
+
+SEC("?syscall")
+__failure __log_level(2)
+__msg("variable ctx access var_off=(0x0; 0x4) disallowed")
+int arg_tag_ctx_syscall_tailcall_var_off_bad(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off &= 4;
+ p += off;
+ bpf_tail_call(p, &syscall_prog_array, 0);
+ return 0;
+}
+
+SEC("?syscall")
+__failure __log_level(2)
+__msg("dereference of modified ctx ptr R1 off=8 disallowed")
+int arg_tag_ctx_syscall_fixed_off_bad(void *ctx)
+{
+ char *p = ctx;
+
+ p += 8;
+ return subprog_ctx_tag(p);
+}
+
+SEC("?syscall")
+__failure __log_level(2)
+__msg("variable ctx access var_off=(0x0; 0x4) disallowed")
+int arg_tag_ctx_syscall_var_off_bad(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off &= 4;
+ p += off;
+ return subprog_ctx_tag(p);
+}
+
__weak int subprog_dynptr(struct bpf_dynptr *dptr)
{
long *d, t, buf[1] = {};
@@ -388,4 +517,23 @@ int arg_tag_dynptr(struct xdp_md *ctx)
return subprog_dynptr(&dptr);
}
+__weak
+void foo(void)
+{
+}
+
+SEC("?tc")
+__failure __msg("R0 !read_ok")
+int return_from_void_global(struct __sk_buff *skb)
+{
+ foo();
+
+ asm volatile(
+ "r1 = r0;"
+ :::
+ );
+
+ return 0;
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_gotox.c b/tools/testing/selftests/bpf/progs/verifier_gotox.c
index 607dad058ca1..0e27c2c79c57 100644
--- a/tools/testing/selftests/bpf/progs/verifier_gotox.c
+++ b/tools/testing/selftests/bpf/progs/verifier_gotox.c
@@ -6,7 +6,7 @@
#include "bpf_misc.h"
#include "../../../include/linux/filter.h"
-#if defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)
+#if defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64) || defined(__TARGET_ARCH_powerpc)
#define DEFINE_SIMPLE_JUMP_TABLE_PROG(NAME, SRC_REG, OFF, IMM, OUTCOME) \
\
@@ -47,6 +47,54 @@ DEFINE_SIMPLE_JUMP_TABLE_PROG(reserved_field_src_reg, BPF_REG_1, 0, 0, __fa
DEFINE_SIMPLE_JUMP_TABLE_PROG(reserved_field_non_zero_off, BPF_REG_0, 1, 0, __failure __msg("BPF_JA|BPF_X uses reserved fields"))
DEFINE_SIMPLE_JUMP_TABLE_PROG(reserved_field_non_zero_imm, BPF_REG_0, 0, 1, __failure __msg("BPF_JA|BPF_X uses reserved fields"))
+#define DEFINE_TERMINAL_GOTOX_PROG(NAME, BASE) \
+ __naked void NAME(void) \
+ { \
+ asm volatile (" \
+ .pushsection .jumptables,\"\",@progbits; \
+jt0_%=: \
+ .quad ret0_%= - " BASE "; \
+ .size jt0_%=, 8; \
+ .global jt0_%=; \
+ .popsection; \
+ \
+ r0 = jt0_%= ll; \
+ r0 = *(u64 *)(r0 + 0); \
+ goto end_%=; \
+ret0_%=: \
+ r0 = 0; \
+ exit; \
+end_%=: \
+ .8byte %[gotox_r0]; \
+" : \
+ : __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, \
+ BPF_REG_0, 0, 0, 0)) \
+ : __clobber_all); \
+ }
+
+SEC("socket")
+__success __retval(0)
+DEFINE_TERMINAL_GOTOX_PROG(jump_table_terminal_gotox, "socket")
+
+static __noinline __used
+DEFINE_TERMINAL_GOTOX_PROG(terminal_gotox_subprog1, ".text")
+
+static __noinline __used int terminal_gotox_subprog2(void)
+{
+ return 0;
+}
+
+SEC("socket")
+__success __retval(0)
+__naked void jump_table_terminal_gotox_subprog(void)
+{
+ asm volatile (" \
+ call terminal_gotox_subprog1; \
+ call terminal_gotox_subprog2; \
+ exit; \
+" ::: __clobber_all);
+}
+
/*
* Gotox is forbidden when there is no jump table loaded
* which points to the sub-function where the gotox is used
@@ -131,7 +179,7 @@ DEFINE_INVALID_SIZE_PROG(u16, __failure __msg("Invalid read of 2 bytes from insn
DEFINE_INVALID_SIZE_PROG(u8, __failure __msg("Invalid read of 1 bytes from insn_array"))
SEC("socket")
-__failure __msg("misaligned value access off 0+1+0 size 8")
+__failure __msg("misaligned value access off 1+0 size 8")
__naked void jump_table_misaligned_access(void)
{
asm volatile (" \
@@ -187,7 +235,7 @@ jt0_%=: \
}
SEC("socket")
-__failure __msg("invalid access to map value, value_size=16 off=-24 size=8")
+__failure __msg("R0 min value is negative")
__naked void jump_table_invalid_mem_acceess_neg(void)
{
asm volatile (" \
@@ -384,6 +432,31 @@ jt0_%=: \
: __clobber_all);
}
-#endif /* __TARGET_ARCH_x86 || __TARGET_ARCH_arm64 */
+/* check valid spill/fill, ptr to insn */
+SEC("socket")
+__success
+__naked void spill_fill_ptr_to_insn(void)
+{
+ asm volatile (
+ ".pushsection .jumptables,\"\",@progbits;"
+ "jt0_%=:"
+ ".quad ret0_%= - socket;"
+ ".size jt0_%=, 8;"
+ ".global jt0_%=;"
+ ".popsection;"
+ "r0 = jt0_%= ll;"
+ "r0 = *(u64 *)(r0 + 0);"
+ "*(u64 *)(r10 - 8) = r0;"
+ "r0 = *(u64 *)(r10 - 8);"
+ ".8byte %[gotox_r0];"
+ "ret0_%=:"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, BPF_REG_0, 0, 0, 0))
+ : __clobber_all);
+}
+
+#endif /* __TARGET_ARCH_x86 || __TARGET_ARCH_arm64 || __TARGET_ARCH_powerpc*/
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c b/tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c
index f2c54e4d89eb..343fc08d9747 100644
--- a/tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c
+++ b/tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c
@@ -85,7 +85,7 @@ __naked void stack_bitwise_and_zero_included(void)
r2 += -64; \
r4 = 0; \
/* Call bpf_ringbuf_output(), it is one of a few helper functions with\
- * ARG_CONST_SIZE_OR_ZERO parameter allowed in unpriv mode.\
+ * ARG_MEM_SIZE_OR_ZERO parameter allowed in unpriv mode.\
* For unpriv this should signal an error, because memory at &fp[-64] is\
* not initialized. \
*/ \
@@ -278,7 +278,7 @@ __naked void stack_jmp_no_min_check(void)
r2 += -64; \
r4 = 0; \
/* Call bpf_ringbuf_output(), it is one of a few helper functions with\
- * ARG_CONST_SIZE_OR_ZERO parameter allowed in unpriv mode.\
+ * ARG_MEM_SIZE_OR_ZERO parameter allowed in unpriv mode.\
* For unpriv this should signal an error, because memory at &fp[-64] is\
* not initialized. \
*/ \
@@ -778,7 +778,7 @@ __naked void variable_memory_8_bytes_leak(void)
r3 += 1; \
r4 = 0; \
/* Call bpf_ringbuf_output(), it is one of a few helper functions with\
- * ARG_CONST_SIZE_OR_ZERO parameter allowed in unpriv mode.\
+ * ARG_MEM_SIZE_OR_ZERO parameter allowed in unpriv mode.\
* For unpriv this should signal an error, because memory region [1, 64]\
* at &fp[-64] is not fully initialized. \
*/ \
diff --git a/tools/testing/selftests/bpf/progs/verifier_helper_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_helper_packet_access.c
index 74f5f9cd153d..71cee3f58324 100644
--- a/tools/testing/selftests/bpf/progs/verifier_helper_packet_access.c
+++ b/tools/testing/selftests/bpf/progs/verifier_helper_packet_access.c
@@ -360,7 +360,7 @@ l0_%=: r0 = 0; \
SEC("tc")
__description("helper access to packet: test15, cls helper fail sub")
-__failure __msg("invalid access to packet")
+__failure __msg("R1 min value is negative")
__naked void test15_cls_helper_fail_sub(void)
{
asm volatile (" \
diff --git a/tools/testing/selftests/bpf/progs/verifier_helper_restricted.c b/tools/testing/selftests/bpf/progs/verifier_helper_restricted.c
index 059aa716e3d0..889c9b78b912 100644
--- a/tools/testing/selftests/bpf/progs/verifier_helper_restricted.c
+++ b/tools/testing/selftests/bpf/progs/verifier_helper_restricted.c
@@ -17,17 +17,6 @@ struct {
__type(value, struct val);
} map_spin_lock SEC(".maps");
-struct timer {
- struct bpf_timer t;
-};
-
-struct {
- __uint(type, BPF_MAP_TYPE_ARRAY);
- __uint(max_entries, 1);
- __type(key, int);
- __type(value, struct timer);
-} map_timer SEC(".maps");
-
SEC("kprobe")
__description("bpf_ktime_get_coarse_ns is forbidden in BPF_PROG_TYPE_KPROBE")
__failure __msg("program of this type cannot use helper bpf_ktime_get_coarse_ns")
@@ -85,106 +74,6 @@ __naked void bpf_prog_type_raw_tracepoint_1(void)
}
SEC("kprobe")
-__description("bpf_timer_init isn restricted in BPF_PROG_TYPE_KPROBE")
-__failure __msg("tracing progs cannot use bpf_timer yet")
-__naked void in_bpf_prog_type_kprobe_2(void)
-{
- asm volatile (" \
- r2 = r10; \
- r2 += -8; \
- r1 = 0; \
- *(u64*)(r2 + 0) = r1; \
- r1 = %[map_timer] ll; \
- call %[bpf_map_lookup_elem]; \
- if r0 == 0 goto l0_%=; \
- r1 = r0; \
- r2 = %[map_timer] ll; \
- r3 = 1; \
-l0_%=: call %[bpf_timer_init]; \
- exit; \
-" :
- : __imm(bpf_map_lookup_elem),
- __imm(bpf_timer_init),
- __imm_addr(map_timer)
- : __clobber_all);
-}
-
-SEC("perf_event")
-__description("bpf_timer_init is forbidden in BPF_PROG_TYPE_PERF_EVENT")
-__failure __msg("tracing progs cannot use bpf_timer yet")
-__naked void bpf_prog_type_perf_event_2(void)
-{
- asm volatile (" \
- r2 = r10; \
- r2 += -8; \
- r1 = 0; \
- *(u64*)(r2 + 0) = r1; \
- r1 = %[map_timer] ll; \
- call %[bpf_map_lookup_elem]; \
- if r0 == 0 goto l0_%=; \
- r1 = r0; \
- r2 = %[map_timer] ll; \
- r3 = 1; \
-l0_%=: call %[bpf_timer_init]; \
- exit; \
-" :
- : __imm(bpf_map_lookup_elem),
- __imm(bpf_timer_init),
- __imm_addr(map_timer)
- : __clobber_all);
-}
-
-SEC("tracepoint")
-__description("bpf_timer_init is forbidden in BPF_PROG_TYPE_TRACEPOINT")
-__failure __msg("tracing progs cannot use bpf_timer yet")
-__naked void in_bpf_prog_type_tracepoint_2(void)
-{
- asm volatile (" \
- r2 = r10; \
- r2 += -8; \
- r1 = 0; \
- *(u64*)(r2 + 0) = r1; \
- r1 = %[map_timer] ll; \
- call %[bpf_map_lookup_elem]; \
- if r0 == 0 goto l0_%=; \
- r1 = r0; \
- r2 = %[map_timer] ll; \
- r3 = 1; \
-l0_%=: call %[bpf_timer_init]; \
- exit; \
-" :
- : __imm(bpf_map_lookup_elem),
- __imm(bpf_timer_init),
- __imm_addr(map_timer)
- : __clobber_all);
-}
-
-SEC("raw_tracepoint")
-__description("bpf_timer_init is forbidden in BPF_PROG_TYPE_RAW_TRACEPOINT")
-__failure __msg("tracing progs cannot use bpf_timer yet")
-__naked void bpf_prog_type_raw_tracepoint_2(void)
-{
- asm volatile (" \
- r2 = r10; \
- r2 += -8; \
- r1 = 0; \
- *(u64*)(r2 + 0) = r1; \
- r1 = %[map_timer] ll; \
- call %[bpf_map_lookup_elem]; \
- if r0 == 0 goto l0_%=; \
- r1 = r0; \
- r2 = %[map_timer] ll; \
- r3 = 1; \
-l0_%=: call %[bpf_timer_init]; \
- exit; \
-" :
- : __imm(bpf_map_lookup_elem),
- __imm(bpf_timer_init),
- __imm_addr(map_timer)
- : __clobber_all);
-}
-
-SEC("kprobe")
__description("bpf_spin_lock is forbidden in BPF_PROG_TYPE_KPROBE")
__failure __msg("tracing progs cannot use bpf_spin_lock yet")
__naked void in_bpf_prog_type_kprobe_3(void)
diff --git a/tools/testing/selftests/bpf/progs/verifier_helper_value_access.c b/tools/testing/selftests/bpf/progs/verifier_helper_value_access.c
index 886498b5e6f3..c6603a118fdc 100644
--- a/tools/testing/selftests/bpf/progs/verifier_helper_value_access.c
+++ b/tools/testing/selftests/bpf/progs/verifier_helper_value_access.c
@@ -91,7 +91,7 @@ l0_%=: exit; \
/* Call a function taking a pointer and a size which doesn't allow the size to
* be zero (i.e. bpf_trace_printk() declares the second argument to be
- * ARG_CONST_SIZE, not ARG_CONST_SIZE_OR_ZERO). We attempt to pass zero for the
+ * ARG_MEM_SIZE, not ARG_MEM_SIZE_OR_ZERO). We attempt to pass zero for the
* size and expect to fail.
*/
SEC("tracepoint")
@@ -1100,7 +1100,7 @@ l0_%=: exit; \
SEC("tracepoint")
__description("map helper access to adjusted map (via const imm): out-of-bound 2")
-__failure __msg("invalid access to map value, value_size=16 off=-4 size=8")
+__failure __msg("R2 min value is negative")
__naked void imm_out_of_bound_2(void)
{
asm volatile (" \
@@ -1176,7 +1176,7 @@ l0_%=: exit; \
SEC("tracepoint")
__description("map helper access to adjusted map (via const reg): out-of-bound 2")
-__failure __msg("invalid access to map value, value_size=16 off=-4 size=8")
+__failure __msg("R2 min value is negative")
__naked void reg_out_of_bound_2(void)
{
asm volatile (" \
diff --git a/tools/testing/selftests/bpf/progs/verifier_int_ptr.c b/tools/testing/selftests/bpf/progs/verifier_int_ptr.c
index 59e34d558654..6627f44faf4b 100644
--- a/tools/testing/selftests/bpf/progs/verifier_int_ptr.c
+++ b/tools/testing/selftests/bpf/progs/verifier_int_ptr.c
@@ -65,7 +65,7 @@ __naked void ptr_to_long_half_uninitialized(void)
SEC("cgroup/sysctl")
__description("arg pointer to long misaligned")
-__failure __msg("misaligned stack access off 0+-20+0 size 8")
+__failure __msg("misaligned stack access off -20+0 size 8")
__naked void arg_ptr_to_long_misaligned(void)
{
asm volatile (" \
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 bf16b00502f2..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;
@@ -210,4 +219,242 @@ l0_%=: /* return 0; */ \
: __clobber_all);
}
+/* Verified that we can detect the pointer as non_null when comparing with
+ * register with value 0. JEQ test case.
+ */
+SEC("xdp")
+__success __log_level(2)
+/* to make sure the branch is not falsely predicted*/
+__msg("r0 = *(u32 *)(r0 +0)")
+__msg("from 7 to 9")
+__naked void jeq_reg_reg_null_check(void)
+{
+ asm volatile (" \
+ *(u32*)(r10 - 8) = 0; \
+ r1 = %[map_xskmap] ll; \
+ r2 = r10; \
+ r2 += -8; \
+ call %[bpf_map_lookup_elem]; \
+ r1 = 0; \
+ if r0 == r1 goto 1f; \
+ r0 = *(u32*)(r0 +0); \
+1: r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_xskmap)
+ : __clobber_all);
+}
+
+/* Same as above but for JNE.
+ */
+SEC("xdp")
+__success __log_level(2)
+/* to make sure the branch is not falsely predicted*/
+__msg("r0 = *(u32 *)(r0 +0)")
+__msg("from 7 to 9")
+__naked void jne_reg_reg_null_check(void)
+{
+ asm volatile (" \
+ *(u32*)(r10 - 8) = 0; \
+ r1 = %[map_xskmap] ll; \
+ r2 = r10; \
+ r2 += -8; \
+ call %[bpf_map_lookup_elem]; \
+ r1 = 0; \
+ if r0 != r1 goto 1f; \
+ goto 2f; \
+1: r0 = *(u32*)(r0 +0); \
+2: r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_xskmap)
+ : __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_jit_inline.c b/tools/testing/selftests/bpf/progs/verifier_jit_inline.c
new file mode 100644
index 000000000000..02e562f56f9d
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_jit_inline.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+SEC("fentry/bpf_fentry_test1")
+__success __retval(0)
+__arch_x86_64
+__jited(" addq %gs:{{.*}}, %rax")
+__arch_arm64
+__jited(" mrs x8, SP_EL0")
+__arch_riscv64
+__jited(" mv a5, tp")
+__arch_loongarch
+__jited(" move $a5, $tp")
+int inline_bpf_get_current_task(void)
+{
+ bpf_get_current_task();
+
+ return 0;
+}
+
+SEC("fentry/bpf_fentry_test2")
+__success __retval(0)
+__arch_loongarch
+__jited(" ld.wu $a5, $tp, 16")
+int inline_bpf_get_smp_processor_id(void)
+{
+ bpf_get_smp_processor_id();
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_kfunc_perfmon.c b/tools/testing/selftests/bpf/progs/verifier_kfunc_perfmon.c
new file mode 100644
index 000000000000..76c39ef30e96
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_kfunc_perfmon.c
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+void *user_ptr;
+char dynptr_buf[8];
+u64 kaddr;
+
+extern struct kmem_cache *bpf_get_kmem_cache(u64 addr) __ksym;
+
+SEC("socket")
+__success
+__caps_unpriv(CAP_BPF)
+__failure_unpriv
+__msg_unpriv("bpf_rdonly_cast is allowed only to CAP_PERFMON and CAP_SYS_ADMIN")
+int rdonly_cast_noperfmon(void *ctx)
+{
+ char *p = bpf_rdonly_cast(0, 0);
+
+ return p[0x7fff];
+}
+
+SEC("socket")
+__success
+__caps_unpriv(CAP_BPF)
+__failure_unpriv
+__msg_unpriv("bpf_probe_read_kernel_dynptr is allowed only to CAP_PERFMON and CAP_SYS_ADMIN")
+int probe_read_kernel_dynptr_noperfmon(void *ctx)
+{
+ struct bpf_dynptr dptr;
+
+ bpf_dynptr_from_mem(dynptr_buf, sizeof(dynptr_buf), 0, &dptr);
+ bpf_probe_read_kernel_dynptr(&dptr, 0, sizeof(dynptr_buf), user_ptr);
+ return 0;
+}
+
+SEC("socket")
+__success
+__caps_unpriv(CAP_BPF)
+__failure_unpriv
+__msg_unpriv("bpf_stream_vprintk is allowed only to CAP_PERFMON and CAP_SYS_ADMIN")
+int stream_vprintk_noperfmon(void *ctx)
+{
+ bpf_stream_printk(BPF_STDOUT, "%pB", (void *)kaddr);
+ return 0;
+}
+
+SEC("socket")
+__success
+__caps_unpriv(CAP_BPF)
+__failure_unpriv
+__msg_unpriv("bpf_get_kmem_cache is allowed only to CAP_PERFMON and CAP_SYS_ADMIN")
+int get_kmem_cache_noperfmon(void *ctx)
+{
+ return !!bpf_get_kmem_cache(kaddr);
+}
+
+__weak int subprog_untrusted_read(void *p __arg_untrusted)
+{
+ return *(char *)p;
+}
+
+SEC("socket")
+__success
+__caps_unpriv(CAP_BPF)
+__failure_unpriv
+__msg_unpriv("rdonly_untrusted_mem access is allowed only to CAP_PERFMON and CAP_SYS_ADMIN")
+int arg_untrusted_read_noperfmon(void *ctx)
+{
+ return subprog_untrusted_read(0);
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_kfunc_prog_types.c b/tools/testing/selftests/bpf/progs/verifier_kfunc_prog_types.c
index a509cad97e69..1fce7a7e8d03 100644
--- a/tools/testing/selftests/bpf/progs/verifier_kfunc_prog_types.c
+++ b/tools/testing/selftests/bpf/progs/verifier_kfunc_prog_types.c
@@ -32,7 +32,7 @@ static void task_kfunc_load_test(void)
}
SEC("raw_tp")
-__failure __msg("calling kernel function")
+__success
int BPF_PROG(task_kfunc_raw_tp)
{
task_kfunc_load_test();
@@ -86,7 +86,7 @@ static void cgrp_kfunc_load_test(void)
}
SEC("raw_tp")
-__failure __msg("calling kernel function")
+__success
int BPF_PROG(cgrp_kfunc_raw_tp)
{
cgrp_kfunc_load_test();
@@ -138,7 +138,7 @@ static void cpumask_kfunc_load_test(void)
}
SEC("raw_tp")
-__failure __msg("calling kernel function")
+__success
int BPF_PROG(cpumask_kfunc_raw_tp)
{
cpumask_kfunc_load_test();
diff --git a/tools/testing/selftests/bpf/progs/verifier_ld_ind.c b/tools/testing/selftests/bpf/progs/verifier_ld_ind.c
index c925ba9a2e74..32989f981fb6 100644
--- a/tools/testing/selftests/bpf/progs/verifier_ld_ind.c
+++ b/tools/testing/selftests/bpf/progs/verifier_ld_ind.c
@@ -107,4 +107,242 @@ __naked void ind_check_calling_conv_r7(void)
: __clobber_all);
}
+/*
+ * ld_{abs,ind} subprog that always sets r0=1 on the success path.
+ * bpf_gen_ld_abs() emits a hidden exit with r0=0 when the load helper
+ * fails. The verifier must model this failure return so that callers
+ * account for r0=0 as a possible return value.
+ */
+__naked __noinline __used
+static int ldabs_subprog(void)
+{
+ asm volatile (
+ "r6 = r1;"
+ ".8byte %[ld_abs];"
+ "r0 = 1;"
+ "exit;"
+ :
+ : __imm_insn(ld_abs, BPF_LD_ABS(BPF_W, 0))
+ : __clobber_all);
+}
+
+__naked __noinline __used
+static int ldind_subprog(void)
+{
+ asm volatile (
+ "r6 = r1;"
+ "r7 = 0;"
+ ".8byte %[ld_ind];"
+ "r0 = 1;"
+ "exit;"
+ :
+ : __imm_insn(ld_ind, BPF_LD_IND(BPF_W, BPF_REG_7, 0))
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("ld_abs: subprog early exit on ld_abs failure")
+__failure __msg("R9 !read_ok")
+__naked void ld_abs_subprog_early_exit(void)
+{
+ asm volatile (
+ "call ldabs_subprog;"
+ "if r0 != 0 goto l_exit_%=;"
+ "r0 = r9;"
+ "l_exit_%=:"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+SEC("socket")
+__description("ld_ind: subprog early exit on ld_ind failure")
+__failure __msg("R9 !read_ok")
+__naked void ld_ind_subprog_early_exit(void)
+{
+ asm volatile (
+ "call ldind_subprog;"
+ "if r0 != 0 goto l_exit_%=;"
+ "r0 = r9;"
+ "l_exit_%=:"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+SEC("socket")
+__description("ld_abs: subprog with both paths safe")
+__success
+__naked void ld_abs_subprog_both_paths_safe(void)
+{
+ asm volatile (
+ "call ldabs_subprog;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+SEC("socket")
+__description("ld_ind: subprog with both paths safe")
+__success
+__naked void ld_ind_subprog_both_paths_safe(void)
+{
+ asm volatile (
+ "call ldind_subprog;"
+ "r0 = 0;"
+ "exit;"
+ ::: __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.
+ */
+__naked __noinline __used
+static void ldabs_void_subprog(void)
+{
+ asm volatile (
+ "r6 = r1;"
+ ".8byte %[ld_abs];"
+ "r0 = 1;"
+ "exit;"
+ :
+ : __imm_insn(ld_abs, BPF_LD_ABS(BPF_W, 0))
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("ld_abs: reject void return subprog")
+__failure __msg("LD_ABS is only allowed in functions that return 'int'")
+__naked void ld_abs_void_subprog_reject(void)
+{
+ asm volatile (
+ "call ldabs_void_subprog;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+__naked __noinline __used
+static void ldind_void_subprog(void)
+{
+ asm volatile (
+ "r6 = r1;"
+ "r7 = 0;"
+ ".8byte %[ld_ind];"
+ "r0 = 1;"
+ "exit;"
+ :
+ : __imm_insn(ld_ind, BPF_LD_IND(BPF_W, BPF_REG_7, 0))
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("ld_ind: reject void return subprog")
+__failure __msg("LD_ABS is only allowed in functions that return 'int'")
+__naked void ld_ind_void_subprog_reject(void)
+{
+ asm volatile (
+ "call ldind_void_subprog;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_ldsx.c b/tools/testing/selftests/bpf/progs/verifier_ldsx.c
index c8494b682c31..41340877dc9d 100644
--- a/tools/testing/selftests/bpf/progs/verifier_ldsx.c
+++ b/tools/testing/selftests/bpf/progs/verifier_ldsx.c
@@ -3,7 +3,7 @@
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
-#include "bpf_arena_common.h"
+#include <bpf_arena_common.h>
#if (defined(__TARGET_ARCH_arm64) || defined(__TARGET_ARCH_x86) || \
(defined(__TARGET_ARCH_riscv) && __riscv_xlen == 64) || \
@@ -274,11 +274,11 @@ __jited("movslq 0x10(%rdi,%r12), %r15")
__jited("movswq 0x18(%rdi,%r12), %r15")
__jited("movsbq 0x20(%rdi,%r12), %r15")
__arch_arm64
-__jited("add x11, x7, x28")
+__jited("add x11, x8, x28")
__jited("ldrsw x21, [x11, #0x10]")
-__jited("add x11, x7, x28")
+__jited("add x11, x8, x28")
__jited("ldrsh x21, [x11, #0x18]")
-__jited("add x11, x7, x28")
+__jited("add x11, x8, x28")
__jited("ldrsb x21, [x11, #0x20]")
__jited("add x11, x0, x28")
__jited("ldrsw x22, [x11, #0x10]")
diff --git a/tools/testing/selftests/bpf/progs/verifier_linked_scalars.c b/tools/testing/selftests/bpf/progs/verifier_linked_scalars.c
index 8f755d2464cf..d571fbfc86a3 100644
--- a/tools/testing/selftests/bpf/progs/verifier_linked_scalars.c
+++ b/tools/testing/selftests/bpf/progs/verifier_linked_scalars.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/bpf.h>
+#include <limits.h>
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
@@ -18,9 +19,9 @@ __naked void scalars(void)
r4 = r1; \
w2 += 0x7FFFFFFF; \
w4 += 0; \
- if r2 == 0 goto l1; \
+ if r2 == 0 goto l0_%=; \
exit; \
-l1: \
+l0_%=: \
r4 >>= 63; \
r3 = 1; \
r3 -= r4; \
@@ -31,4 +32,682 @@ l1: \
" ::: __clobber_all);
}
+/*
+ * Test that sync_linked_regs() preserves register IDs.
+ *
+ * The sync_linked_regs() function copies bounds from known_reg to linked
+ * registers. When doing so, it must preserve each register's original id
+ * to allow subsequent syncs from the same source to work correctly.
+ *
+ */
+SEC("socket")
+__success
+__naked void sync_linked_regs_preserves_id(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r0 &= 0xff; /* r0 in [0, 255] */ \
+ r1 = r0; /* r0, r1 linked with id 1 */ \
+ r1 += 4; /* r1 has id=1 and off=4 in [4, 259] */ \
+ if r1 < 10 goto l0_%=; \
+ /* r1 in [10, 259], r0 synced to [6, 255] */ \
+ r2 = r0; /* r2 has id=1 and in [6, 255] */ \
+ if r1 < 14 goto l0_%=; \
+ /* r1 in [14, 259], r0 synced to [10, 255] */ \
+ if r0 >= 10 goto l0_%=; \
+ /* Never executed */ \
+ r0 /= 0; \
+l0_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__success
+__naked void scalars_neg(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r0 &= 0xff; \
+ r1 = r0; \
+ r1 += -4; \
+ if r1 s< 0 goto l0_%=; \
+ if r0 != 0 goto l0_%=; \
+ r0 /= 0; \
+l0_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* Same test but using BPF_SUB instead of BPF_ADD with negative immediate */
+SEC("socket")
+__success
+__naked void scalars_neg_sub(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r0 &= 0xff; \
+ r1 = r0; \
+ r1 -= 4; \
+ if r1 s< 0 goto l0_%=; \
+ if r0 != 0 goto l0_%=; \
+ r0 /= 0; \
+l0_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* alu32 with negative offset */
+SEC("socket")
+__success
+__naked void scalars_neg_alu32_add(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w0 &= 0xff; \
+ w1 = w0; \
+ w1 += -4; \
+ if w1 s< 0 goto l0_%=; \
+ if w0 != 0 goto l0_%=; \
+ r0 /= 0; \
+l0_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* alu32 with negative offset using SUB */
+SEC("socket")
+__success
+__naked void scalars_neg_alu32_sub(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w0 &= 0xff; \
+ w1 = w0; \
+ w1 -= 4; \
+ if w1 s< 0 goto l0_%=; \
+ if w0 != 0 goto l0_%=; \
+ r0 /= 0; \
+l0_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* Positive offset: r1 = r0 + 4, then if r1 >= 6, r0 >= 2, so r0 != 0 */
+SEC("socket")
+__success
+__naked void scalars_pos(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r0 &= 0xff; \
+ r1 = r0; \
+ r1 += 4; \
+ if r1 < 6 goto l0_%=; \
+ if r0 != 0 goto l0_%=; \
+ r0 /= 0; \
+l0_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* SUB with negative immediate: r1 -= -4 is equivalent to r1 += 4 */
+SEC("socket")
+__success
+__naked void scalars_sub_neg_imm(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r0 &= 0xff; \
+ r1 = r0; \
+ r1 -= -4; \
+ if r1 < 6 goto l0_%=; \
+ if r0 != 0 goto l0_%=; \
+ r0 /= 0; \
+l0_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* Double ADD clears the ID (can't accumulate offsets) */
+SEC("socket")
+__failure
+__msg("div by zero")
+__naked void scalars_double_add(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r0 &= 0xff; \
+ r1 = r0; \
+ r1 += 2; \
+ r1 += 2; \
+ if r1 < 6 goto l0_%=; \
+ if r0 != 0 goto l0_%=; \
+ r0 /= 0; \
+l0_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * Test that sync_linked_regs() correctly handles large offset differences.
+ * r1.off = S32_MIN, r2.off = 1, delta = S32_MIN - 1 requires 64-bit math.
+ */
+SEC("socket")
+__success
+__naked void scalars_sync_delta_overflow(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r0 &= 0xff; \
+ r1 = r0; \
+ r2 = r0; \
+ r1 += %[s32_min]; \
+ r2 += 1; \
+ if r2 s< 100 goto l0_%=; \
+ if r1 s< 0 goto l0_%=; \
+ r0 /= 0; \
+l0_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32),
+ [s32_min]"i"(INT_MIN)
+ : __clobber_all);
+}
+
+/*
+ * Another large delta case: r1.off = S32_MAX, r2.off = -1.
+ * delta = S32_MAX - (-1) = S32_MAX + 1 requires 64-bit math.
+ */
+SEC("socket")
+__success
+__naked void scalars_sync_delta_overflow_large_range(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r0 &= 0xff; \
+ r1 = r0; \
+ r2 = r0; \
+ r1 += %[s32_max]; \
+ r2 += -1; \
+ if r2 s< 0 goto l0_%=; \
+ if r1 s>= 0 goto l0_%=; \
+ r0 /= 0; \
+l0_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32),
+ [s32_max]"i"(INT_MAX)
+ : __clobber_all);
+}
+
+/*
+ * Test linked scalar tracking with alu32 and large positive offset (0x7FFFFFFF).
+ * After w1 += 0x7FFFFFFF, w1 wraps to negative for any r0 >= 1.
+ * If w1 is signed-negative, then r0 >= 1, so r0 != 0.
+ */
+SEC("socket")
+__success
+__naked void scalars_alu32_big_offset(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w0 &= 0xff; \
+ w1 = w0; \
+ w1 += 0x7FFFFFFF; \
+ if w1 s>= 0 goto l0_%=; \
+ if w0 != 0 goto l0_%=; \
+ r0 /= 0; \
+l0_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__failure
+__msg("div by zero")
+__naked void scalars_alu32_basic(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ w1 += 1; \
+ if r1 > 10 goto 1f; \
+ r0 >>= 32; \
+ if r0 == 0 goto 1f; \
+ r0 /= 0; \
+1: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * Test alu32 linked register tracking with wrapping.
+ * R0 is bounded to [0xffffff00, 0xffffffff] (high 32-bit values)
+ * w1 += 0x100 causes R1 to wrap to [0, 0xff]
+ *
+ * After sync_linked_regs, if bounds are computed correctly:
+ * R0 should be [0x00000000_ffffff00, 0x00000000_ffffff80]
+ * R0 >> 32 == 0, so div by zero is unreachable
+ *
+ * If bounds are computed incorrectly (64-bit underflow):
+ * R0 becomes [0xffffffff_ffffff00, 0xffffffff_ffffff80]
+ * R0 >> 32 == 0xffffffff != 0, so div by zero is reachable
+ */
+SEC("socket")
+__success
+__naked void scalars_alu32_wrap(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w0 |= 0xffffff00; \
+ r1 = r0; \
+ w1 += 0x100; \
+ if r1 > 0x80 goto l0_%=; \
+ r2 = r0; \
+ r2 >>= 32; \
+ if r2 == 0 goto l0_%=; \
+ r0 /= 0; \
+l0_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * Test that sync_linked_regs() checks reg->id (the linked target register)
+ * for BPF_ADD_CONST32 rather than known_reg->id (the branch register).
+ */
+SEC("socket")
+__success
+__naked void scalars_alu32_zext_linked_reg(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w6 = w0; /* r6 in [0, 0xFFFFFFFF] */ \
+ r7 = r6; /* linked: same id as r6 */ \
+ w7 += 1; /* alu32: r7.id |= BPF_ADD_CONST32 */ \
+ r8 = 0xFFFFffff ll; \
+ if r6 < r8 goto l0_%=; \
+ /* r6 in [0xFFFFFFFF, 0xFFFFFFFF] */ \
+ /* sync_linked_regs: known_reg=r6, reg=r7 */ \
+ /* CPU: w7 = (u32)(0xFFFFFFFF + 1) = 0, zext -> r7 = 0 */ \
+ /* With fix: r7 64-bit = [0, 0] (zext applied) */ \
+ /* Without fix: r7 64-bit = [0x100000000] (no zext) */ \
+ r7 >>= 32; \
+ if r7 == 0 goto l0_%=; \
+ r0 /= 0; /* unreachable with fix */ \
+l0_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * Test that sync_linked_regs() skips propagation when one register used
+ * alu32 (BPF_ADD_CONST32) and the other used alu64 (BPF_ADD_CONST64).
+ * The delta relationship doesn't hold across different ALU widths.
+ */
+SEC("socket")
+__failure __msg("div by zero")
+__naked void scalars_alu32_alu64_cross_type(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w6 = w0; /* r6 in [0, 0xFFFFFFFF] */ \
+ r7 = r6; /* linked: same id as r6 */ \
+ w7 += 1; /* alu32: BPF_ADD_CONST32, delta = 1 */ \
+ r8 = r6; /* linked: same id as r6 */ \
+ r8 += 2; /* alu64: BPF_ADD_CONST64, delta = 2 */ \
+ r9 = 0xFFFFffff ll; \
+ if r7 < r9 goto l0_%=; \
+ /* r7 = 0xFFFFFFFF */ \
+ /* sync: known_reg=r7 (ADD_CONST32), reg=r8 (ADD_CONST64) */ \
+ /* Without fix: r8 = zext(0xFFFFFFFF + 1) = 0 */ \
+ /* With fix: r8 stays [2, 0x100000001] (r8 >= 2) */ \
+ if r8 > 0 goto l1_%=; \
+ goto l0_%=; \
+l1_%=: \
+ r0 /= 0; /* div by zero */ \
+l0_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * Test that regsafe() prevents pruning when two paths reach the same program
+ * point with linked registers carrying different ADD_CONST flags (one
+ * BPF_ADD_CONST32 from alu32, another BPF_ADD_CONST64 from alu64).
+ */
+SEC("socket")
+__failure __msg("div by zero")
+__flag(BPF_F_TEST_STATE_FREQ)
+__naked void scalars_alu32_alu64_regsafe_pruning(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w6 = w0; /* r6 in [0, 0xFFFFFFFF] */ \
+ r7 = r6; /* linked: same id as r6 */ \
+ /* Get another random value for the path branch */ \
+ call %[bpf_get_prandom_u32]; \
+ if r0 > 0 goto l_pathb_%=; \
+ /* Path A: alu32 */ \
+ w7 += 1; /* BPF_ADD_CONST32, delta = 1 */\
+ goto l_merge_%=; \
+l_pathb_%=: \
+ /* Path B: alu64 */ \
+ r7 += 1; /* BPF_ADD_CONST64, delta = 1 */\
+l_merge_%=: \
+ /* Merge point: regsafe() compares path B against cached path A. */ \
+ /* Narrow r6 to trigger sync_linked_regs for r7 */ \
+ r9 = 0xFFFFffff ll; \
+ if r6 < r9 goto l0_%=; \
+ /* r6 = 0xFFFFFFFF */ \
+ /* sync: r7 = 0xFFFFFFFF + 1 = 0x100000000 */ \
+ /* Path A: zext -> r7 = 0 */ \
+ /* Path B: no zext -> r7 = 0x100000000 */ \
+ r7 >>= 32; \
+ if r7 == 0 goto l0_%=; \
+ r0 /= 0; /* div by zero on path B */ \
+l0_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__success
+void alu32_negative_offset(void)
+{
+ volatile char path[5];
+ volatile int offset = bpf_get_prandom_u32();
+ int off = offset;
+
+ if (off >= 5 && off < 10)
+ path[off - 5] = '.';
+
+ /* So compiler doesn't say: error: variable 'path' set but not used */
+ __sink(path[0]);
+}
+
+void dummy_calls(void)
+{
+ bpf_iter_num_new(0, 0, 0);
+ bpf_iter_num_next(0);
+ bpf_iter_num_destroy(0);
+}
+
+SEC("socket")
+__success
+__flag(BPF_F_TEST_STATE_FREQ)
+int spurious_precision_marks(void *ctx)
+{
+ struct bpf_iter_num iter;
+
+ asm volatile(
+ "r1 = %[iter];"
+ "r2 = 0;"
+ "r3 = 10;"
+ "call %[bpf_iter_num_new];"
+ "1:"
+ "r1 = %[iter];"
+ "call %[bpf_iter_num_next];"
+ "if r0 == 0 goto 4f;"
+ "r7 = *(u32 *)(r0 + 0);"
+ "r8 = *(u32 *)(r0 + 0);"
+ /* This jump can't be predicted and does not change r7 or r8 state. */
+ "if r7 > r8 goto 2f;"
+ /* Branch explored first ties r2 and r7 as having the same id. */
+ "r2 = r7;"
+ "goto 3f;"
+ "2:"
+ /* Branch explored second does not tie r2 and r7 but has a function call. */
+ "call %[bpf_get_prandom_u32];"
+ "3:"
+ /*
+ * A checkpoint.
+ * When first branch is explored, this would inject linked registers
+ * r2 and r7 into the jump history.
+ * When second branch is explored, this would be a cache hit point,
+ * triggering propagate_precision().
+ */
+ "if r7 <= 42 goto +0;"
+ /*
+ * Mark r7 as precise using an if condition that is always true.
+ * When reached via the second branch, this triggered a bug in the backtrack_insn()
+ * because r2 (tied to r7) was propagated as precise to a call.
+ */
+ "if r7 <= 0xffffFFFF goto +0;"
+ "goto 1b;"
+ "4:"
+ "r1 = %[iter];"
+ "call %[bpf_iter_num_destroy];"
+ :
+ : __imm_ptr(iter),
+ __imm(bpf_iter_num_new),
+ __imm(bpf_iter_num_next),
+ __imm(bpf_iter_num_destroy),
+ __imm(bpf_get_prandom_u32)
+ : __clobber_common, "r7", "r8"
+ );
+
+ return 0;
+}
+
+/*
+ * Test that r += r (self-add, src_reg == dst_reg) clears the scalar ID
+ * so that sync_linked_regs() does not propagate an incorrect delta.
+ */
+SEC("socket")
+__failure
+__msg("div by zero")
+__naked void scalars_self_add_clears_id(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r6 = r0; /* r6 unknown, id A */ \
+ r7 = r6; /* r7 linked to r6, id A */ \
+ call %[bpf_get_prandom_u32]; \
+ r8 = r0; /* r8 unknown, id B */ \
+ r9 = r8; /* r9 linked to r8, id B */ \
+ if r7 != 1 goto l_exit_%=; \
+ /* r7 == 1; sync propagates: r6 = 1 (known, id A) */ \
+ r6 += r6; /* r6 = 2; should clear id */ \
+ if r7 == r9 goto l_exit_%=; \
+ /* Bug: r6 synced to r7(1)+delta(2)=3; Fix: r6 = 2 */ \
+ if r6 == 3 goto l_exit_%=; \
+ r0 /= 0; \
+l_exit_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* Same as above but with alu32 such that w6 += w6 also clears id. */
+SEC("socket")
+__failure
+__msg("div by zero")
+__naked void scalars_self_add_alu32_clears_id(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w6 = w0; \
+ w7 = w6; \
+ call %[bpf_get_prandom_u32]; \
+ w8 = w0; \
+ w9 = w8; \
+ if w7 != 1 goto l_exit_%=; \
+ w6 += w6; \
+ if w7 == w9 goto l_exit_%=; \
+ if w6 == 3 goto l_exit_%=; \
+ r0 /= 0; \
+l_exit_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * Test that stale delta from a cleared BPF_ADD_CONST does not leak
+ * through assign_scalar_id_before_mov() into a new id, causing
+ * sync_linked_regs() to compute an incorrect offset.
+ */
+SEC("socket")
+__failure
+__msg("div by zero")
+__naked void scalars_stale_delta_from_cleared_id(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r6 = r0; /* r6 unknown, gets id A */ \
+ r6 += 5; /* id A|ADD_CONST, delta 5 */ \
+ r6 ^= 0; /* id cleared; delta stays 5 */ \
+ r8 = r6; /* new id B, stale delta 5 */ \
+ r8 += 3; /* id B|ADD_CONST, delta 3 */ \
+ r9 = r6; /* id B, stale delta 5 */ \
+ if r9 != 10 goto l_exit_%=; \
+ /* Bug: r8 = 10+(3-5) = 8; Fix: r8 = 10+(3-0) = 13 */ \
+ if r8 == 8 goto l_exit_%=; \
+ r0 /= 0; \
+l_exit_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* Same as above but with alu32. */
+SEC("socket")
+__failure
+__msg("div by zero")
+__naked void scalars_stale_delta_from_cleared_id_alu32(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w6 = w0; \
+ w6 += 5; \
+ w6 ^= 0; \
+ w8 = w6; \
+ w8 += 3; \
+ w9 = w6; \
+ if w9 != 10 goto l_exit_%=; \
+ if w8 == 8 goto l_exit_%=; \
+ r0 /= 0; \
+l_exit_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * Test that regsafe() verifies base_id consistency for BPF_ADD_CONST
+ * linked scalars during state pruning.
+ *
+ * The false branch (explored first) links R3 to R2 via ADD_CONST.
+ * The true branch (runtime path) links R3 to R4 (unrelated base_id).
+ * At the merge point, pruning must fail because the linkage topology
+ * differs.
+ */
+SEC("socket")
+__description("linked scalars: add_const base_id must be consistent for pruning")
+__failure __msg("invalid variable-offset")
+__flag(BPF_F_TEST_STATE_FREQ)
+__naked void add_const_base_id_pruning(void)
+{
+ asm volatile (" \
+ r1 = 0; \
+ *(u64*)(r10 - 16) = r1; \
+ call %[bpf_get_prandom_u32]; \
+ r6 = r0; \
+ r6 &= 1; \
+ if r6 >= 1 goto l_true_%=; \
+ \
+ /* False branch (explored first, old state) */ \
+ call %[bpf_get_prandom_u32]; \
+ r2 = r0; \
+ r2 &= 0xff; /* R2 = scalar(id=A) [0,255] */ \
+ r3 = r2; /* R3 linked to R2 (id=A) */ \
+ r3 += 10; /* R3 id=A|ADD_CONST, delta=10 */\
+ r6 = 0; \
+ goto l_merge_%=; \
+ \
+l_true_%=: \
+ /* True branch (runtime path, cur state) */ \
+ call %[bpf_get_prandom_u32]; \
+ r2 = r0; \
+ r2 &= 0xff; /* R2 = scalar [0,255], id=0 */ \
+ r4 = r0; \
+ r4 &= 0xff; /* R4 = scalar [0,255], id=0 */ \
+ r3 = r4; /* R3 linked to R4 (new id=C) */\
+ r3 += 10; /* R3 id=C|ADD_CONST, delta=10 */\
+ r6 = 0; \
+ \
+l_merge_%=: \
+ /* At merge, old R3 linked to R2, cur R3 linked to R4. */\
+ /* Pruning must fail: base_ids A vs C inconsistent. */ \
+ if r2 >= 6 goto l_exit_%=; \
+ /* sync_linked_regs: R2<6 => R3<16 in old state. */ \
+ /* Without fix: R3 in [10,15] from incorrect pruning. */\
+ /* With fix: R3 in [10,265], not synced from R2. */ \
+ r3 -= 10; /* [0,5] vs [0,255] */ \
+ r9 = r10; \
+ r9 += -16; \
+ r9 += r3; /* fp-16+[0,5] vs fp-16+[0,255] */\
+ *(u8*)(r9 + 0) = r6; /* within 16B vs past fp */ \
+l_exit_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_live_stack.c b/tools/testing/selftests/bpf/progs/verifier_live_stack.c
index 2de105057bbc..401152b2b64f 100644
--- a/tools/testing/selftests/bpf/progs/verifier_live_stack.c
+++ b/tools/testing/selftests/bpf/progs/verifier_live_stack.c
@@ -3,8 +3,10 @@
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
+#include "../../../include/linux/filter.h"
#include "bpf_misc.h"
+char _license[] SEC("license") = "GPL";
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1);
@@ -12,14 +14,20 @@ struct {
__type(value, long long);
} map SEC(".maps");
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, __u32);
+ __type(value, __u64);
+} array_map_8b SEC(".maps");
+
+const char snprintf_u64_fmt[] = "%llu";
+
SEC("socket")
__log_level(2)
-__msg("(0) frame 0 insn 2 +written -8")
-__msg("(0) frame 0 insn 1 +live -24")
-__msg("(0) frame 0 insn 1 +written -8")
-__msg("(0) frame 0 insn 0 +live -8,-24")
-__msg("(0) frame 0 insn 0 +written -8")
-__msg("(0) live stack update done in 2 iterations")
+__msg("0: (79) r1 = *(u64 *)(r10 -8) ; use: fp0-8")
+__msg("1: (79) r2 = *(u64 *)(r10 -24) ; use: fp0-24")
+__msg("2: (7b) *(u64 *)(r10 -8) = r1 ; def: fp0-8")
__naked void simple_read_simple_write(void)
{
asm volatile (
@@ -33,12 +41,8 @@ __naked void simple_read_simple_write(void)
SEC("socket")
__log_level(2)
-__msg("(0) frame 0 insn 1 +live -8")
-__not_msg("(0) frame 0 insn 1 +written")
-__msg("(0) live stack update done in 2 iterations")
-__msg("(0) frame 0 insn 1 +live -16")
-__msg("(0) frame 0 insn 1 +written -32")
-__msg("(0) live stack update done in 2 iterations")
+__msg("2: (79) r0 = *(u64 *)(r10 -8) ; use: fp0-8")
+__msg("6: (79) r0 = *(u64 *)(r10 -16) ; use: fp0-16")
__naked void read_write_join(void)
{
asm volatile (
@@ -58,13 +62,9 @@ __naked void read_write_join(void)
SEC("socket")
__log_level(2)
-__msg("2: (25) if r0 > 0x2a goto pc+1")
-__msg("7: (95) exit")
-__msg("(0) frame 0 insn 2 +written -16")
-__msg("(0) live stack update done in 2 iterations")
-__msg("7: (95) exit")
-__not_msg("(0) frame 0 insn 2")
-__msg("(0) live stack update done in 1 iterations")
+__msg("stack use/def subprog#0 must_write_not_same_slot (d0,cs0):")
+__msg("6: (7b) *(u64 *)(r2 +0) = r0{{$}}")
+__msg("Live regs before insn:")
__naked void must_write_not_same_slot(void)
{
asm volatile (
@@ -83,10 +83,8 @@ __naked void must_write_not_same_slot(void)
SEC("socket")
__log_level(2)
-__msg("(0) frame 0 insn 0 +written -8,-16")
-__msg("(0) live stack update done in 2 iterations")
-__msg("(0) frame 0 insn 0 +written -8")
-__msg("(0) live stack update done in 2 iterations")
+__msg("0: (7a) *(u64 *)(r10 -8) = 0 ; def: fp0-8")
+__msg("5: (85) call bpf_map_lookup_elem#1 ; use: fp0-8h")
__naked void must_write_not_same_type(void)
{
asm volatile (
@@ -110,10 +108,11 @@ __naked void must_write_not_same_type(void)
SEC("socket")
__log_level(2)
-__msg("(2,4) frame 0 insn 4 +written -8")
-__msg("(2,4) live stack update done in 2 iterations")
-__msg("(0) frame 0 insn 2 +written -8")
-__msg("(0) live stack update done in 2 iterations")
+/* Callee writes fp[0]-8: stack_use at call site has slots 0,1 live */
+__msg("stack use/def subprog#0 caller_stack_write (d0,cs0):")
+__msg("2: (85) call pc+1{{$}}")
+__msg("stack use/def subprog#1 write_first_param (d1,cs2):")
+__msg("4: (7a) *(u64 *)(r1 +0) = 7 ; def: fp0-8")
__naked void caller_stack_write(void)
{
asm volatile (
@@ -135,23 +134,15 @@ static __used __naked void write_first_param(void)
SEC("socket")
__log_level(2)
-/* caller_stack_read() function */
-__msg("2: .12345.... (85) call pc+4")
-__msg("5: .12345.... (85) call pc+1")
-__msg("6: 0......... (95) exit")
-/* read_first_param() function */
-__msg("7: .1........ (79) r0 = *(u64 *)(r1 +0)")
-__msg("8: 0......... (95) exit")
-/* update for callsite at (2) */
-__msg("(2,7) frame 0 insn 7 +live -8")
-__msg("(2,7) live stack update done in 2 iterations")
-__msg("(0) frame 0 insn 2 +live -8")
-__msg("(0) live stack update done in 2 iterations")
-/* update for callsite at (5) */
-__msg("(5,7) frame 0 insn 7 +live -16")
-__msg("(5,7) live stack update done in 2 iterations")
-__msg("(0) frame 0 insn 5 +live -16")
-__msg("(0) live stack update done in 2 iterations")
+__msg("stack use/def subprog#0 caller_stack_read (d0,cs0):")
+__msg("2: (85) call pc+{{.*}} ; use: fp0-8{{$}}")
+__msg("5: (85) call pc+{{.*}} ; use: fp0-16{{$}}")
+__msg("stack use/def subprog#1 read_first_param (d1,cs2):")
+__msg("7: (79) r0 = *(u64 *)(r1 +0) ; use: fp0-8{{$}}")
+__msg("8: (95) exit")
+__msg("stack use/def subprog#1 read_first_param (d1,cs5):")
+__msg("7: (79) r0 = *(u64 *)(r1 +0) ; use: fp0-16{{$}}")
+__msg("8: (95) exit")
__naked void caller_stack_read(void)
{
asm volatile (
@@ -174,20 +165,48 @@ static __used __naked void read_first_param(void)
}
SEC("socket")
+__success
+__naked void arg_track_join_convergence(void)
+{
+ asm volatile (
+ "r1 = 1;"
+ "r2 = 2;"
+ "call arg_track_join_convergence_subprog;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void arg_track_join_convergence_subprog(void)
+{
+ asm volatile (
+ "if r1 == 0 goto 1f;"
+ "r0 = r1;"
+ "goto 2f;"
+"1:"
+ "r0 = r2;"
+"2:"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+SEC("socket")
__flag(BPF_F_TEST_STATE_FREQ)
__log_level(2)
-/* read_first_param2() function */
-__msg(" 9: .1........ (79) r0 = *(u64 *)(r1 +0)")
-__msg("10: .......... (b7) r0 = 0")
-__msg("11: 0......... (05) goto pc+0")
-__msg("12: 0......... (95) exit")
+/* fp0-8 consumed at insn 9, dead by insn 11. stack_def at insn 4 kills slots 0,1. */
+__msg("4: (7b) *(u64 *)(r10 -8) = r0 ; def: fp0-8")
+/* stack_use at call site: callee reads fp0-8, slots 0,1 live */
+__msg("7: (85) call pc+{{.*}} ; use: fp0-8")
+/* read_first_param2: no caller stack live inside callee after first read */
+__msg("9: (79) r0 = *(u64 *)(r1 +0) ; use: fp0-8")
+__msg("10: (b7) r0 = 0{{$}}")
+__msg("11: (05) goto pc+0{{$}}")
+__msg("12: (95) exit")
/*
- * The purpose of the test is to check that checkpoint in
- * read_first_param2() stops path traversal. This will only happen if
- * verifier understands that fp[0]-8 at insn (12) is not alive.
+ * Checkpoint at goto +0 fires because fp0-8 is dead → state pruning.
*/
__msg("12: safe")
-__msg("processed 20 insns")
__naked void caller_stack_pruning(void)
{
asm volatile (
@@ -342,3 +361,2482 @@ static __used __naked unsigned long write_tail_call(void)
__imm_addr(map_array)
: __clobber_all);
}
+
+/* Test precise subprog stack access analysis.
+ * Caller passes fp-32 (SPI 3) to callee that only accesses arg+0 and arg+8
+ * (SPIs 3 and 2). Slots 0 and 1 should NOT be live at the call site.
+ *
+ * Insn layout:
+ * 0: *(u64*)(r10 - 8) = 0 write SPI 0
+ * 1: *(u64*)(r10 - 16) = 0 write SPI 1
+ * 2: *(u64*)(r10 - 24) = 0 write SPI 2
+ * 3: *(u64*)(r10 - 32) = 0 write SPI 3
+ * 4: r1 = r10
+ * 5: r1 += -32
+ * 6: call precise_read_two passes fp-32 (SPI 3)
+ * 7: r0 = 0
+ * 8: exit
+ *
+ * At insn 6 only SPIs 2,3 should be live (slots 4-7, 0xf0).
+ * SPIs 0,1 are written but never read → dead.
+ */
+SEC("socket")
+__log_level(2)
+__msg("6: (85) call pc+{{.*}} ; use: fp0-24 fp0-32{{$}}")
+__naked void subprog_precise_stack_access(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 8) = 0;"
+ "*(u64 *)(r10 - 16) = 0;"
+ "*(u64 *)(r10 - 24) = 0;"
+ "*(u64 *)(r10 - 32) = 0;"
+ "r1 = r10;"
+ "r1 += -32;"
+ "call precise_read_two;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* Callee reads only at arg+0 (SPI 3) and arg+8 (SPI 2) */
+static __used __naked void precise_read_two(void)
+{
+ asm volatile (
+ "r0 = *(u64 *)(r1 + 0);"
+ "r2 = *(u64 *)(r1 + 8);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* Test that multi-level subprog calls (callee passes arg-derived ptr
+ * to another BPF subprog) are analyzed precisely.
+ *
+ * Caller passes fp-32 (SPI 3). The callee forwards it to inner_callee.
+ * inner_callee only reads at offset 0 from the pointer.
+ * The analysis recurses into forward_to_inner -> inner_callee and
+ * determines only SPI 3 is accessed (slots 6-7, 0xc0), not all of SPIs 0-3.
+ *
+ * Insn layout:
+ * 0: *(u64*)(r10 - 8) = 0 write SPI 0
+ * 1: *(u64*)(r10 - 16) = 0 write SPI 1
+ * 2: *(u64*)(r10 - 24) = 0 write SPI 2
+ * 3: *(u64*)(r10 - 32) = 0 write SPI 3
+ * 4: r1 = r10
+ * 5: r1 += -32
+ * 6: call forward_to_inner passes fp-32 (SPI 3)
+ * 7: r0 = 0
+ * 8: exit
+ */
+SEC("socket")
+__log_level(2)
+__msg("6: (85) call pc+{{.*}} ; use: fp0-32{{$}}")
+__naked void subprog_multilevel_conservative(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 8) = 0;"
+ "*(u64 *)(r10 - 16) = 0;"
+ "*(u64 *)(r10 - 24) = 0;"
+ "*(u64 *)(r10 - 32) = 0;"
+ "r1 = r10;"
+ "r1 += -32;"
+ "call forward_to_inner;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* Forwards arg to another subprog */
+static __used __naked void forward_to_inner(void)
+{
+ asm volatile (
+ "call inner_callee;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void inner_callee(void)
+{
+ asm volatile (
+ "r0 = *(u64 *)(r1 + 0);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* Test multi-frame precision loss: callee consumes caller stack early,
+ * but static liveness keeps it live at pruning points inside callee.
+ *
+ * Caller stores map_ptr or scalar(42) at fp-8, then calls
+ * consume_and_call_inner. The callee reads fp0-8 at entry (consuming
+ * the slot), then calls do_nothing2. After do_nothing2 returns (a
+ * pruning point), fp-8 should be dead -- the read already happened.
+ * But because the call instruction's stack_use includes SPI 0, the
+ * static live_stack_before at insn 7 is 0x1, keeping fp-8 live inside
+ * the callee and preventing state pruning between the two paths.
+ *
+ * Insn layout:
+ * 0: call bpf_get_prandom_u32
+ * 1: if r0 == 42 goto pc+2 -> insn 4
+ * 2: r0 = map ll (ldimm64 part1)
+ * 3: (ldimm64 part2)
+ * 4: *(u64)(r10 - 8) = r0 fp-8 = map_ptr OR scalar(42)
+ * 5: r1 = r10
+ * 6: r1 += -8
+ * 7: call consume_and_call_inner
+ * 8: r0 = 0
+ * 9: exit
+ *
+ * At insn 7, live_stack_before = 0x3 (slots 0-1 live due to stack_use).
+ * At insn 8, live_stack_before = 0x0 (SPI 0 dead, caller doesn't need it).
+ */
+SEC("socket")
+__flag(BPF_F_TEST_STATE_FREQ)
+__log_level(2)
+__success
+__msg(" 7: (85) call pc+{{.*}} ; use: fp0-8")
+__msg(" 8: {{.*}} (b7)")
+__naked void callee_consumed_caller_stack(void)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "if r0 == 42 goto 1f;"
+ "r0 = %[map] ll;"
+"1:"
+ "*(u64 *)(r10 - 8) = r0;"
+ "r1 = r10;"
+ "r1 += -8;"
+ "call consume_and_call_inner;"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_get_prandom_u32),
+ __imm_addr(map)
+ : __clobber_all);
+}
+
+static __used __naked void consume_and_call_inner(void)
+{
+ asm volatile (
+ "r0 = *(u64 *)(r1 + 0);" /* read fp[0]-8 into caller-saved r0 */
+ "call do_nothing2;" /* inner call clobbers r0 */
+ "r0 = 0;"
+ "goto +0;" /* checkpoint */
+ "r0 = 0;"
+ "goto +0;" /* checkpoint */
+ "r0 = 0;"
+ "goto +0;" /* checkpoint */
+ "r0 = 0;"
+ "goto +0;" /* checkpoint */
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void do_nothing2(void)
+{
+ asm volatile (
+ "r0 = 0;"
+ "r0 = 0;"
+ "r0 = 0;"
+ "r0 = 0;"
+ "r0 = 0;"
+ "r0 = 0;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/*
+ * Reproducer for unsound pruning when clean_verifier_state() promotes
+ * live STACK_ZERO bytes to STACK_MISC.
+ *
+ * Program shape:
+ * - Build key at fp-4:
+ * - path A keeps key byte as STACK_ZERO;
+ * - path B writes unknown byte making it STACK_MISC.
+ * - Branches merge at a prune point before map_lookup.
+ * - map_lookup on ARRAY map is value-sensitive to constant zero key:
+ * - path A: const key 0 => PTR_TO_MAP_VALUE (non-NULL);
+ * - path B: non-const key => PTR_TO_MAP_VALUE_OR_NULL.
+ * - Dereference lookup result without null check.
+ *
+ * Note this behavior won't trigger at fp-8, since the verifier will
+ * track 32-bit scalar spill differently as spilled_ptr.
+ *
+ * Correct verifier behavior: reject (path B unsafe).
+ * With blanket STACK_ZERO->STACK_MISC promotion on live slots, cached path A
+ * state can be generalized and incorrectly prune path B, making program load.
+ */
+SEC("socket")
+__flag(BPF_F_TEST_STATE_FREQ)
+__failure __msg("R0 invalid mem access 'map_value_or_null'")
+__naked void stack_zero_to_misc_unsound_array_lookup(void)
+{
+ asm volatile (
+ /* key at fp-4: all bytes STACK_ZERO */
+ "*(u32 *)(r10 - 4) = 0;"
+ "call %[bpf_get_prandom_u32];"
+ /* fall-through (path A) explored first */
+ "if r0 != 0 goto l_nonconst%=;"
+ /* path A: keep key constant zero */
+ "goto l_lookup%=;"
+"l_nonconst%=:"
+ /* path B: key byte turns to STACK_MISC, key no longer const */
+ "*(u8 *)(r10 - 4) = r0;"
+"l_lookup%=:"
+ /* value-sensitive lookup */
+ "r2 = r10;"
+ "r2 += -4;"
+ "r1 = %[array_map_8b] ll;"
+ "call %[bpf_map_lookup_elem];"
+ /* unsafe when lookup result is map_value_or_null */
+ "r0 = *(u64 *)(r0 + 0);"
+ "exit;"
+ :
+ : __imm(bpf_get_prandom_u32),
+ __imm(bpf_map_lookup_elem),
+ __imm_addr(array_map_8b)
+ : __clobber_all);
+}
+
+/*
+ * Subprog variant of stack_zero_to_misc_unsound_array_lookup.
+ *
+ * Check unsound pruning when a callee modifies the caller's
+ * stack through a pointer argument.
+ *
+ * Program shape:
+ * main:
+ * *(u32)(fp - 4) = 0 key = 0 (all bytes STACK_ZERO)
+ * r1 = fp - 4
+ * call maybe_clobber_key may overwrite key[0] with scalar
+ * <-- prune point: two states meet here -->
+ * r2 = fp - 4
+ * r1 = array_map_8b
+ * call bpf_map_lookup_elem value-sensitive on const-zero key
+ * r0 = *(u64)(r0 + 0) deref without null check
+ * exit
+ *
+ * maybe_clobber_key(r1):
+ * r6 = r1 save &key
+ * call bpf_get_prandom_u32
+ * if r0 == 0 goto skip path A: key stays STACK_ZERO
+ * *(u8)(r6 + 0) = r0 path B: key[0] becomes STACK_MISC
+ * skip:
+ * r0 = 0
+ * exit
+ *
+ * Path A: const-zero key => array lookup => PTR_TO_MAP_VALUE => deref OK.
+ * Path B: non-const key => array lookup => PTR_TO_MAP_VALUE_OR_NULL => UNSAFE.
+ *
+ * If the cleaner collapses STACK_ZERO -> STACK_MISC for the live key
+ * slot, path A's cached state matches path B, pruning the unsafe path.
+ *
+ * Correct verifier behaviour: reject.
+ */
+SEC("socket")
+__flag(BPF_F_TEST_STATE_FREQ)
+__failure __msg("R0 invalid mem access 'map_value_or_null'")
+__naked void subprog_stack_zero_to_misc_unsound(void)
+{
+ asm volatile (
+ /* key at fp-4: all bytes STACK_ZERO */
+ "*(u32 *)(r10 - 4) = 0;"
+ /* subprog may clobber key[0] with a scalar byte */
+ "r1 = r10;"
+ "r1 += -4;"
+ "call maybe_clobber_key;"
+ /* value-sensitive array lookup */
+ "r2 = r10;"
+ "r2 += -4;"
+ "r1 = %[array_map_8b] ll;"
+ "call %[bpf_map_lookup_elem];"
+ /* unsafe when result is map_value_or_null (path B) */
+ "r0 = *(u64 *)(r0 + 0);"
+ "exit;"
+ :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(array_map_8b)
+ : __clobber_all);
+}
+
+static __used __naked void maybe_clobber_key(void)
+{
+ asm volatile (
+ "r6 = r1;"
+ "call %[bpf_get_prandom_u32];"
+ /* path A (r0==0): key stays STACK_ZERO, explored first */
+ "if r0 == 0 goto 1f;"
+ /* path B (r0!=0): overwrite key[0] with scalar */
+ "*(u8 *)(r6 + 0) = r0;"
+ "1:"
+ "r0 = 0;"
+ "exit;"
+ :: __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * Demonstrate that subprog arg spill/reload breaks arg tracking,
+ * inflating caller stack liveness and preventing state pruning.
+ *
+ * modifier2(fp-24) has two paths: one writes a scalar to *(r1+8)
+ * = caller fp-16, the other leaves it as zero. After modifier2
+ * returns, fp-16 is never read again — it is dead.
+ *
+ * spill_reload_reader2(fp-24) only reads caller fp-8 via
+ * *(r1+16), but it spills r1 across a helper call. This
+ * breaks compute_subprog_arg_access(): the reload from callee
+ * stack cannot be connected back to arg1, so arg1 access goes
+ * "all (conservative)". At the call site (r1 = fp-24, slot 5)
+ * apply_callee_stack_access() marks slots 0..5 as stack_use —
+ * pulling fp-16 (slots 2-3) into live_stack_before even though
+ * the reader never touches it.
+ *
+ * Result: at modifier2's return point two states with different
+ * fp-16 values cannot be pruned.
+ *
+ * With correct (or old dynamic) liveness fp-16 is dead at that
+ * point and the states prune → "6: safe" appears in the log.
+ */
+SEC("socket")
+__flag(BPF_F_TEST_STATE_FREQ)
+__log_level(2)
+__success
+__msg("6: safe")
+__naked void spill_reload_inflates_stack_liveness(void)
+{
+ asm volatile (
+ /* struct at fp-24: { ctx; ptr; tail; } */
+ "*(u64 *)(r10 - 24) = r1;" /* fp-24 = ctx */
+ "*(u64 *)(r10 - 16) = r1;" /* fp-16 = ctx (STACK_SPILL ptr) */
+ "*(u64 *)(r10 - 8) = 0;" /* fp-8 = tail */
+ /* modifier2 writes different values to fp-16 on two paths */
+ "r1 = r10;"
+ "r1 += -24;"
+ "call modifier2;"
+ /* insn 6: prune point — two states with different fp-16
+ * path A: fp-16 = STACK_MISC (scalar overwrote pointer)
+ * path B: fp-16 = STACK_SPILL (original ctx pointer)
+ * STACK_MISC does NOT subsume STACK_SPILL(ptr),
+ * so pruning fails unless fp-16 is cleaned (dead).
+ */
+ "r1 = r10;"
+ "r1 += -24;"
+ "call spill_reload_reader2;" /* reads fp-8 via *(r1+16) */
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* Two paths: one writes a scalar to *(r1+8) = caller fp-16,
+ * the other leaves it unchanged. Both return 0 via separate
+ * exits to prevent pruning inside the subprog at the merge.
+ */
+static __used __naked void modifier2(void)
+{
+ asm volatile (
+ "r6 = r1;"
+ "call %[bpf_get_prandom_u32];"
+ "if r0 == 0 goto 1f;"
+ "*(u64 *)(r6 + 8) = r0;" /* fp-16 = random */
+ "r0 = 0;"
+ "exit;" /* path A exit */
+ "1:"
+ "r0 = 0;"
+ "exit;" /* path B exit */
+ :: __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* Receives r1 = caller fp-24. Only reads *(r1+16) = fp-8.
+ * Spills r1 across a helper call → arg tracking goes conservative →
+ * slots 0..5 all appear used instead of just slot 1 (fp-8).
+ */
+static __used __naked void spill_reload_reader2(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 8) = r1;" /* spill arg1 */
+ "call %[bpf_get_prandom_u32];" /* clobbers r1-r5 */
+ "r1 = *(u64 *)(r10 - 8);" /* reload arg1 */
+ "r0 = *(u64 *)(r1 + 16);" /* read caller fp-8 */
+ "r0 = 0;"
+ "exit;"
+ :: __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* BTF FUNC records are not generated for kfuncs referenced
+ * from inline assembly. These records are necessary for
+ * libbpf to link the program. The function below is a hack
+ * to ensure that BTF FUNC records are generated.
+ */
+void __kfunc_btf_root(void)
+{
+ bpf_iter_num_new(0, 0, 0);
+ bpf_iter_num_next(0);
+ bpf_iter_num_destroy(0);
+}
+
+/* Test that open-coded iterator kfunc arguments get precise stack
+ * liveness tracking. struct bpf_iter_num is 8 bytes (1 SPI).
+ *
+ * Insn layout:
+ * 0: *(u64*)(r10 - 8) = 0 write SPI 0 (dead)
+ * 1: *(u64*)(r10 - 16) = 0 write SPI 1 (dead)
+ * 2: r1 = r10
+ * 3: r1 += -24 iter state at fp-24 (SPI 2)
+ * 4: r2 = 0
+ * 5: r3 = 10
+ * 6: call bpf_iter_num_new defines SPI 2 (KF_ITER_NEW) → 0x0
+ * 7-8: r1 = fp-24
+ * 9: call bpf_iter_num_next uses SPI 2 → 0x30
+ * 10: if r0 == 0 goto 2f
+ * 11: goto 1b
+ * 12-13: r1 = fp-24
+ * 14: call bpf_iter_num_destroy uses SPI 2 → 0x30
+ * 15: r0 = 0
+ * 16: exit
+ *
+ * At insn 6, SPI 2 is defined (KF_ITER_NEW initializes, doesn't read),
+ * so it kills liveness from successors. live_stack_before = 0x0.
+ * At insns 9 and 14, SPI 2 is used (iter_next/destroy read the state),
+ * so live_stack_before = 0x30.
+ */
+SEC("socket")
+__success __log_level(2)
+__msg(" 6: (85) call bpf_iter_num_new{{.*}} ; def: fp0-24{{$}}")
+__msg(" 9: (85) call bpf_iter_num_next{{.*}} ; use: fp0-24{{$}}")
+__msg("14: (85) call bpf_iter_num_destroy{{.*}} ; use: fp0-24{{$}}")
+__naked void kfunc_iter_stack_liveness(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 8) = 0;" /* SPI 0 - dead */
+ "*(u64 *)(r10 - 16) = 0;" /* SPI 1 - dead */
+ "r1 = r10;"
+ "r1 += -24;"
+ "r2 = 0;"
+ "r3 = 10;"
+ "call %[bpf_iter_num_new];"
+"1:"
+ "r1 = r10;"
+ "r1 += -24;"
+ "call %[bpf_iter_num_next];"
+ "if r0 == 0 goto 2f;"
+ "goto 1b;"
+"2:"
+ "r1 = r10;"
+ "r1 += -24;"
+ "call %[bpf_iter_num_destroy];"
+ "r0 = 0;"
+ "exit;"
+ :: __imm(bpf_iter_num_new),
+ __imm(bpf_iter_num_next),
+ __imm(bpf_iter_num_destroy)
+ : __clobber_all);
+}
+
+/*
+ * Test for soundness bug in static stack liveness analysis.
+ *
+ * The static pre-pass tracks FP-derived register offsets to determine
+ * which stack slots are accessed. When a PTR_TO_STACK is spilled to
+ * the stack and later reloaded, the reload (BPF_LDX) kills FP-derived
+ * tracking, making subsequent accesses through the reloaded pointer
+ * invisible to the static analysis.
+ *
+ * This causes the analysis to incorrectly mark SPI 0 as dead at the
+ * merge point. clean_verifier_state() zeros it in the cached state,
+ * and stacksafe() accepts the new state against STACK_INVALID,
+ * enabling incorrect pruning.
+ *
+ * Path A (verified first): stores PTR_TO_MAP_VALUE in SPI 0
+ * Path B (verified second): stores scalar 42 in SPI 0
+ * After merge: reads SPI 0 through spilled/reloaded PTR_TO_STACK
+ * and dereferences the result as a pointer.
+ *
+ * Correct behavior: reject (path B dereferences a scalar)
+ * Bug behavior: accept (path B is incorrectly pruned)
+ */
+SEC("socket")
+__flag(BPF_F_TEST_STATE_FREQ)
+__failure __msg("R0 invalid mem access 'scalar'")
+__naked void spill_ptr_liveness_type_confusion(void)
+{
+ asm volatile (
+ /* Map lookup to get PTR_TO_MAP_VALUE */
+ "r1 = %[map] ll;"
+ "*(u32 *)(r10 - 32) = 0;"
+ "r2 = r10;"
+ "r2 += -32;"
+ "call %[bpf_map_lookup_elem];"
+ "if r0 == 0 goto l_exit%=;"
+ /* r6 = PTR_TO_MAP_VALUE (callee-saved) */
+ "r6 = r0;"
+ /* Branch: fall-through (path A) verified first */
+ "call %[bpf_get_prandom_u32];"
+ "if r0 != 0 goto l_scalar%=;"
+ /* Path A: store map value ptr at SPI 0 */
+ "*(u64 *)(r10 - 8) = r6;"
+ "goto l_merge%=;"
+"l_scalar%=:"
+ /* Path B: store scalar at SPI 0 */
+ "r1 = 42;"
+ "*(u64 *)(r10 - 8) = r1;"
+"l_merge%=:"
+ /*
+ * Spill PTR_TO_STACK{off=-8} to SPI 1, then reload.
+ * Reload kills FP-derived tracking, hiding the
+ * subsequent SPI 0 access from the static analysis.
+ */
+ "r1 = r10;"
+ "r1 += -8;"
+ "*(u64 *)(r10 - 16) = r1;"
+ "goto +0;" /* checkpoint */
+ "goto +0;" /* checkpoint */
+ "goto +0;" /* checkpoint */
+ "r1 = *(u64 *)(r10 - 16);"
+ /* Read SPI 0 through reloaded pointer */
+ "r0 = *(u64 *)(r1 + 0);"
+ /* Dereference: safe for map value (path A),
+ * unsafe for scalar (path B).
+ */
+ "r0 = *(u64 *)(r0 + 0);"
+ "exit;"
+"l_exit%=:"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_map_lookup_elem),
+ __imm(bpf_get_prandom_u32),
+ __imm_addr(map)
+ : __clobber_all);
+}
+
+/* === Tests for 4-byte stack slot liveness granularity === */
+
+/* Test that a 4-byte aligned write is stack_def and kills liveness.
+ *
+ * 0: *(u64 *)(r10 - 8) = 0 def slots 0,1 (full SPI 0)
+ * 1: *(u32 *)(r10 - 8) = 0 def slot 1 (4-byte write kills slot 1)
+ * 2: r0 = *(u64 *)(r10 - 8) use slots 0,1
+ * 3: r0 = 0
+ * 4: exit
+ *
+ * At insn 1, the 4-byte write defines slot 1. Slot 0 still flows
+ * backward from insn 2's read: live_stack_before = 0x1.
+ */
+SEC("socket")
+__log_level(2)
+__msg("1: (62) *(u32 *)(r10 -8) = 0 ; def: fp0-8h")
+__naked void four_byte_write_kills_slot(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 8) = 0;"
+ "*(u32 *)(r10 - 8) = 0;"
+ "r0 = *(u64 *)(r10 - 8);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* Test that a write to the upper half of an SPI is dead when only
+ * the lower half is read. This was impossible at SPI granularity
+ * where any read of the SPI kept the entire SPI live.
+ *
+ * 0: *(u32 *)(r10 - 8) = 0 def slot 1 (DEAD: never read)
+ * 1: *(u32 *)(r10 - 4) = 0 def slot 0
+ * 2: r0 = *(u32 *)(r10 - 4) use slot 0 only
+ * 3: r0 = 0
+ * 4: exit
+ *
+ * At insn 0, nothing is live (0x0). Previously at SPI granularity,
+ * the read at insn 2 would mark the full SPI 0 as live and the
+ * 4-byte writes wouldn't count as def, so insn 0 would have had
+ * SPI 0 live (0x1).
+ */
+SEC("socket")
+__log_level(2)
+__msg("0: (62) *(u32 *)(r10 -8) = 0 ; def: fp0-8h")
+__msg("2: (61) r0 = *(u32 *)(r10 -4) ; use: fp0-4h")
+__naked void dead_half_spi_write(void)
+{
+ asm volatile (
+ "*(u32 *)(r10 - 8) = 0;"
+ "*(u32 *)(r10 - 4) = 0;"
+ "r0 = *(u32 *)(r10 - 4);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* Test that a 4-byte read from the upper half of SPI 0 makes only
+ * slot 1 live (0x2), not the full SPI (0x3).
+ *
+ * 0: *(u64 *)(r10 - 8) = 0 def slots 0,1
+ * 1: r0 = *(u32 *)(r10 - 8) use slot 1 only (upper half)
+ * 2: r0 = 0
+ * 3: exit
+ *
+ * At insn 1, live_stack_before = 0x2 (slot 1 only).
+ */
+SEC("socket")
+__log_level(2)
+__msg("1: (61) r0 = *(u32 *)(r10 -8) ; use: fp0-8h")
+__naked void four_byte_read_upper_half(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 8) = 0;"
+ "r0 = *(u32 *)(r10 - 8);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* Test that a 2-byte write does NOT count as stack_def.
+ * Sub-4-byte writes don't fully cover a 4-byte slot,
+ * so liveness passes through.
+ *
+ * 0: *(u64 *)(r10 - 8) = 0 def slots 0,1
+ * 1: *(u16 *)(r10 - 4) = 0 NOT stack_def (2 < 4 bytes)
+ * 2: r0 = *(u32 *)(r10 - 4) use slot 0
+ * 3: r0 = 0
+ * 4: exit
+ *
+ * At insn 1, slot 0 still live (0x1) because 2-byte write
+ * didn't kill it.
+ */
+SEC("socket")
+__log_level(2)
+__msg("0: (7a) *(u64 *)(r10 -8) = 0 ; def: fp0-8")
+__msg("1: (6a) *(u16 *)(r10 -4) = 0{{$}}")
+__msg("2: (61) r0 = *(u32 *)(r10 -4) ; use: fp0-4h")
+__naked void two_byte_write_no_kill(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 8) = 0;"
+ "*(u16 *)(r10 - 4) = 0;"
+ "r0 = *(u32 *)(r10 - 4);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* Test that a 1-byte write does NOT count as stack_def.
+ *
+ * 0: *(u64 *)(r10 - 8) = 0 def slots 0,1
+ * 1: *(u8 *)(r10 - 4) = 0 NOT stack_def (1 < 4 bytes)
+ * 2: r0 = *(u32 *)(r10 - 4) use slot 0
+ * 3: r0 = 0
+ * 4: exit
+ *
+ * At insn 1, slot 0 still live (0x1).
+ */
+SEC("socket")
+__log_level(2)
+__msg("0: (7a) *(u64 *)(r10 -8) = 0 ; def: fp0-8")
+__msg("1: (72) *(u8 *)(r10 -4) = 0")
+__msg("2: (61) r0 = *(u32 *)(r10 -4) ; use: fp0-4h")
+__naked void one_byte_write_no_kill(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 8) = 0;"
+ "*(u8 *)(r10 - 4) = 0;"
+ "r0 = *(u32 *)(r10 - 4);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* Test stack access beyond fp-256 exercising the second bitmask word.
+ * fp-264 is SPI 32, slots 64-65, which are bits 0-1 of live_stack[1].
+ *
+ * 0: *(u64 *)(r10 - 264) = 0 def slots 64,65
+ * 1: r0 = *(u64 *)(r10 - 264) use slots 64,65
+ * 2: r0 = 0
+ * 3: exit
+ *
+ * At insn 1, live_stack high word has bits 0,1 set: 0x3:0x0.
+ */
+SEC("socket")
+__log_level(2)
+__msg("1: (79) r0 = *(u64 *)(r10 -264) ; use: fp0-264")
+__naked void high_stack_second_bitmask_word(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 264) = 0;"
+ "r0 = *(u64 *)(r10 - 264);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* Test that two separate 4-byte writes to each half of an SPI
+ * together kill liveness for the full SPI.
+ *
+ * 0: *(u32 *)(r10 - 8) = 0 def slot 1 (upper half)
+ * 1: *(u32 *)(r10 - 4) = 0 def slot 0 (lower half)
+ * 2: r0 = *(u64 *)(r10 - 8) use slots 0,1
+ * 3: r0 = 0
+ * 4: exit
+ *
+ * At insn 0: live_stack_before = 0x0 (both slots killed by insns 0,1).
+ * At insn 1: live_stack_before = 0x2 (slot 1 still live, slot 0 killed here).
+ */
+SEC("socket")
+__log_level(2)
+__msg("0: (62) *(u32 *)(r10 -8) = 0 ; def: fp0-8h")
+__msg("1: (62) *(u32 *)(r10 -4) = 0 ; def: fp0-4h")
+__naked void two_four_byte_writes_kill_full_spi(void)
+{
+ asm volatile (
+ "*(u32 *)(r10 - 8) = 0;"
+ "*(u32 *)(r10 - 4) = 0;"
+ "r0 = *(u64 *)(r10 - 8);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* Test that 4-byte writes on both branches kill a slot at the
+ * join point. Previously at SPI granularity, a 4-byte write was
+ * not stack_def, so liveness would flow backward through the
+ * branch that only had a 4-byte write.
+ *
+ * 0: call bpf_get_prandom_u32
+ * 1: if r0 != 0 goto 1f
+ * 2: *(u64 *)(r10 - 8) = 0 path A: def slots 0,1
+ * 3: goto 2f
+ * 1:4: *(u32 *)(r10 - 4) = 0 path B: def slot 0
+ * 2:5: r0 = *(u32 *)(r10 - 4) use slot 0
+ * 6: r0 = 0
+ * 7: exit
+ *
+ * Both paths define slot 0 before the read. At insn 1 (branch),
+ * live_stack_before = 0x0 because slot 0 is killed on both paths.
+ */
+SEC("socket")
+__log_level(2)
+__msg("1: (55) if r0 != 0x0 goto pc+2")
+__msg("2: (7a) *(u64 *)(r10 -8) = 0 ; def: fp0-8")
+__msg("3: (05) goto pc+1")
+__msg("4: (62) *(u32 *)(r10 -4) = 0 ; def: fp0-4h")
+__msg("5: (61) r0 = *(u32 *)(r10 -4) ; use: fp0-4h")
+__naked void both_branches_kill_slot(void)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "if r0 != 0 goto 1f;"
+ "*(u64 *)(r10 - 8) = 0;"
+ "goto 2f;"
+"1:"
+ "*(u32 *)(r10 - 4) = 0;"
+"2:"
+ "r0 = *(u32 *)(r10 - 4);"
+ "r0 = 0;"
+ "exit;"
+ :: __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* Soundness: cleaning the dead upper half of an SPI must not
+ * affect the live lower half's type information for pruning.
+ *
+ * Both halves of SPI 0 are written separately. Only the lower
+ * half (slot 0) is used as a 4-byte map key. The upper half
+ * (slot 1) is dead and cleaned to STACK_INVALID.
+ *
+ * Path A: key stays 0 (STACK_ZERO) → non-null array lookup
+ * Path B: key byte turns STACK_MISC → may-null array lookup
+ * Deref without null check: safe for A, unsafe for B.
+ *
+ * If half-SPI cleaning incorrectly corrupted the live half's
+ * type info, path A's cached state could generalize and unsoundly
+ * prune path B.
+ *
+ * Expected: reject (path B unsafe).
+ */
+SEC("socket")
+__flag(BPF_F_TEST_STATE_FREQ)
+__failure __msg("R0 invalid mem access 'map_value_or_null'")
+__naked void half_spi_clean_preserves_stack_zero(void)
+{
+ asm volatile (
+ "*(u32 *)(r10 - 4) = 0;" /* slot 0: STACK_ZERO */
+ "*(u32 *)(r10 - 8) = 0;" /* slot 1: STACK_ZERO (dead) */
+ "call %[bpf_get_prandom_u32];"
+ "if r0 != 0 goto l_nonconst%=;"
+ "goto l_lookup%=;"
+"l_nonconst%=:"
+ "*(u8 *)(r10 - 4) = r0;" /* slot 0: STACK_MISC */
+"l_lookup%=:"
+ "r2 = r10;"
+ "r2 += -4;"
+ "r1 = %[array_map_8b] ll;"
+ "call %[bpf_map_lookup_elem];"
+ "r0 = *(u64 *)(r0 + 0);" /* unsafe if null */
+ "exit;"
+ :
+ : __imm(bpf_get_prandom_u32),
+ __imm(bpf_map_lookup_elem),
+ __imm_addr(array_map_8b)
+ : __clobber_all);
+}
+
+/*
+ * Model of scx_lavd's pick_idle_cpu_at_cpdom iat block:
+ * conditional block with helper call and temporary stack spill,
+ * spill dead after merge.
+ *
+ * Path A (fall-through): spill r6 to fp-8 across helper call
+ * Path B (branch taken): skip the block entirely
+ * At merge (insn 6): fp-8 is dead (never read after merge)
+ *
+ * Static liveness marks fp-8 dead at merge. clean_verifier_state()
+ * converts path A's STACK_SPILL to STACK_INVALID. Path B has
+ * STACK_INVALID. stacksafe() matches -> path B pruned -> "6: safe".
+ */
+SEC("socket")
+__flag(BPF_F_TEST_STATE_FREQ)
+__success
+__log_level(2)
+__msg("6: safe")
+__naked void dead_spill_at_merge_enables_pruning(void)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "r6 = 7;"
+ "if r0 != 0 goto l_skip%=;"
+ /* conditional block: spill, call, reload */
+ "*(u64 *)(r10 - 8) = r6;"
+ "call %[bpf_get_prandom_u32];"
+ "r6 = *(u64 *)(r10 - 8);"
+"l_skip%=:"
+ /* fp-8 dead. Path B pruned here -> "6: safe" */
+ "r0 = r6;"
+ "exit;"
+ :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * FP-offset tracking loses precision on second ADD, killing all liveness.
+ *
+ * fp_off_insn_xfer() handles "FP itself + negative imm" precisely
+ * (e.g. r6 = r10; r6 += -24 -> slot 5). But any subsequent ADD/SUB
+ * on a register that already has non-zero spis falls through to
+ * spis_set_all(), because the code only handles the FP-itself case.
+ *
+ * A write through this imprecise register enters the non-zero-spis
+ * branch of set_indirect_stack_access(), which OR's the all-ones
+ * mask into stack_def. The backward liveness equation
+ *
+ * stack_in = (stack_out & ~stack_def) | stack_use
+ *
+ * sees ~ALL = 0, killing ALL slot liveness at that instruction.
+ *
+ * At the merge pruning point, live_stack_before is empty.
+ * clean_verifier_state() marks fp-8 as STACK_INVALID.
+ * stacksafe() skips STACK_INVALID (line "continue"), so pruning
+ * succeeds regardless of the current state's fp-8 value.
+ * Path B is pruned, its null deref is never explored.
+ *
+ * Correct behavior: reject (path B dereferences NULL).
+ * Bug behavior: accept (path B pruned away).
+ */
+SEC("socket")
+__flag(BPF_F_TEST_STATE_FREQ)
+__failure __msg("R1 invalid mem access 'scalar'")
+__naked void fp_add_loses_precision_kills_liveness(void)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "if r0 != 0 goto l_pathB%=;"
+
+ /* Path A (fall-through, explored first): fp-8 = 0 */
+ "r1 = 0;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "goto l_merge%=;"
+
+"l_pathB%=:"
+ /* Path B (explored second): fp-8 = 42 */
+ "r1 = 42;"
+ "*(u64 *)(r10 - 8) = r1;"
+
+"l_merge%=:"
+ /*
+ * Create imprecise FP-derived register.
+ * r6 = r10 - 24 gets precise slot 5.
+ * r6 += 8 hits the else branch (spis non-zero, delta > 0)
+ * and sets spis to ALL. r6 is actually r10-16.
+ */
+ "r6 = r10;"
+ "r6 += -24;"
+ "r6 += 8;"
+
+ /*
+ * Write through imprecise r6. Actually writes to fp-16
+ * (does NOT touch fp-8), but liveness marks ALL slots
+ * as stack_def, killing fp-8's liveness.
+ */
+ "r7 = 0;"
+ "*(u64 *)(r6 + 0) = r7;"
+
+ /* Read fp-8: liveness says dead, but value is needed. */
+ "r2 = *(u64 *)(r10 - 8);"
+ "if r2 == 42 goto l_danger%=;"
+
+ /* r2 != 42 (path A: r2 == 0): safe exit */
+ "r0 = 0;"
+ "exit;"
+
+"l_danger%=:"
+ /* Only reachable from path B (r2 == 42): null deref */
+ "r1 = 0;"
+ "r0 = *(u64 *)(r1 + 0);"
+ "exit;"
+ :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__flag(BPF_F_TEST_STATE_FREQ)
+__failure __msg("R1 invalid mem access 'scalar'")
+__naked void fp_spill_loses_precision_kills_liveness(void)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "if r0 != 0 goto l_pathB%=;"
+
+ "r1 = 0;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "goto l_merge%=;"
+
+"l_pathB%=:"
+ "r1 = 42;"
+ "*(u64 *)(r10 - 8) = r1;"
+
+"l_merge%=:"
+ "r6 = r10;"
+ "r6 += -64;"
+ "*(u64 *)(r10 - 160) = r6;"
+ "r6 = *(u64 *)(r10 - 160);"
+
+ "r7 = 0;"
+ "*(u64 *)(r6 + 0) = r7;"
+
+ "r2 = *(u64 *)(r10 - 8);"
+ "if r2 == 42 goto l_danger%=;"
+
+ "r0 = *(u64 *)(r10 - 56);"
+ "exit;"
+
+"l_danger%=:"
+ "r1 = 0;"
+ "r0 = *(u64 *)(r1 + 0);"
+ "exit;"
+ :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* === Tests for frame-based AT_FP tracking === */
+
+/*
+ * Test 1: conditional_stx_in_subprog
+ * Subprog conditionally writes caller's slot.
+ * Verify slot stays live (backward pass handles conditional def via CFG).
+ *
+ * Main writes fp-8=42, calls cond_writer(fp-8), reads fp-8.
+ * cond_writer only writes on one path → parent_def only on that path.
+ * The backward parent_live correctly keeps fp-8 live at entry
+ * (conditional write doesn't kill liveness at the join).
+ */
+SEC("socket")
+__log_level(2)
+/* fp-8 live at call (callee conditionally writes → slot not killed) */
+__msg("1: (7b) *(u64 *)(r10 -8) = r1 ; def: fp0-8")
+__msg("4: (85) call pc+2{{$}}")
+__msg("5: (79) r0 = *(u64 *)(r10 -8) ; use: fp0-8")
+__naked void conditional_stx_in_subprog(void)
+{
+ asm volatile (
+ "r1 = 42;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r1 = r10;"
+ "r1 += -8;"
+ "call cond_writer;"
+ "r0 = *(u64 *)(r10 - 8);"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* Conditionally writes to *(r1+0) */
+static __used __naked void cond_writer(void)
+{
+ asm volatile (
+ "r6 = r1;"
+ "call %[bpf_get_prandom_u32];"
+ "if r0 == 0 goto 1f;"
+ "*(u64 *)(r6 + 0) = r0;"
+ "1:"
+ "r0 = 0;"
+ "exit;"
+ :: __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__log_level(2)
+__msg("4: (85) call pc+{{.*}} ; use: fp0-16")
+__msg("7: (85) call pc+{{.*}} ; use: fp0-32")
+__naked void multiple_callsites_different_offsets(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 16) = 0;"
+ "*(u64 *)(r10 - 32) = 0;"
+ "r1 = r10;"
+ "r1 += -16;"
+ "call read_first_param;"
+ "r1 = r10;"
+ "r1 += -32;"
+ "call read_first_param;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/*
+ * Test 3: nested_fp_passthrough
+ * main→A→B, main's FP forwarded to B. B accesses main's stack.
+ * Verify liveness propagates through.
+ *
+ * Main passes fp-32 to outer_forwarder, which passes it to inner_reader.
+ * inner_reader reads at arg+0 (= main's fp-32).
+ * parent_live propagates transitively: inner→outer→main.
+ */
+SEC("socket")
+__log_level(2)
+/* At call to outer_forwarder: main's fp-32 (slots 6,7) should be live */
+__msg("6: (85) call pc+{{.*}} ; use: fp0-32")
+__naked void nested_fp_passthrough(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 8) = 0;"
+ "*(u64 *)(r10 - 16) = 0;"
+ "*(u64 *)(r10 - 24) = 0;"
+ "*(u64 *)(r10 - 32) = 0;"
+ "r1 = r10;"
+ "r1 += -32;"
+ "call outer_forwarder;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* Forwards arg to inner_reader */
+static __used __naked void outer_forwarder(void)
+{
+ asm volatile (
+ "call inner_reader;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void inner_reader(void)
+{
+ asm volatile (
+ "r0 = *(u64 *)(r1 + 0);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/*
+ * Test 4: callee_must_write_before_read
+ * Callee unconditionally writes parent slot before reading.
+ * Verify slot is NOT live at call site (parent_def kills it).
+ */
+SEC("socket")
+__log_level(2)
+/* fp-8 NOT live at call: callee writes before reading (parent_def kills it) */
+__msg("2: .12345.... (85) call pc+")
+__naked void callee_must_write_before_read(void)
+{
+ asm volatile (
+ "r1 = r10;"
+ "r1 += -8;"
+ "call write_then_read;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* Unconditionally writes *(r1+0), then reads it back */
+static __used __naked void write_then_read(void)
+{
+ asm volatile (
+ "r6 = r1;"
+ "r7 = 99;"
+ "*(u64 *)(r6 + 0) = r7;"
+ "r0 = *(u64 *)(r6 + 0);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/*
+ * Test 5: return_site_liveness_bleeding
+ * Main calls subprog twice. Slot used after one call but not the other.
+ * Context-insensitive: slot conservatively live at both.
+ *
+ * After first call: read fp-8.
+ * After second call: don't read fp-8.
+ * Since parent_live is per-subprog (not per call-site),
+ * fp-8 is live at both call sites.
+ */
+SEC("socket")
+__log_level(2)
+/* Both calls have fp-8 live due to context-insensitive parent_live */
+__msg("3: (85) call pc+{{.*}} ; use: fp0-8")
+__msg("7: (85) call pc+{{.*}} ; use: fp0-8")
+__naked void return_site_liveness_bleeding(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 8) = 0;"
+ "r1 = r10;"
+ "r1 += -8;"
+ "call read_first_param;"
+ "r0 = *(u64 *)(r10 - 8);"
+ "r1 = r10;"
+ "r1 += -8;"
+ "call read_first_param;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+SEC("socket")
+__log_level(2)
+__msg("9: (85) call bpf_loop#181 ; use: fp0-16")
+__naked void callback_conditional_read_beyond_ctx(void)
+{
+ asm volatile (
+ "r1 = 42;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "*(u64 *)(r10 - 16) = r1;"
+ "r1 = 2;"
+ "r2 = cb_cond_read ll;"
+ "r3 = r10;"
+ "r3 += -8;"
+ "r4 = 0;"
+ "call %[bpf_loop];"
+ "r0 = 0;"
+ "exit;"
+ :: __imm(bpf_loop)
+ : __clobber_all);
+}
+
+/* Callback conditionally reads *(ctx - 8) = caller fp-16 */
+static __used __naked void cb_cond_read(void)
+{
+ asm volatile (
+ "r6 = r2;"
+ "call %[bpf_get_prandom_u32];"
+ "if r0 == 0 goto 1f;"
+ "r0 = *(u64 *)(r6 - 8);"
+ "1:"
+ "r0 = 0;"
+ "exit;"
+ :: __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__log_level(2)
+__msg("14: (7b) *(u64 *)(r6 -8) = r7 ; def: fp0-16")
+__msg("15: (79) r0 = *(u64 *)(r6 -8) ; use: fp0-16")
+__naked void callback_write_before_read_kills(void)
+{
+ asm volatile (
+ "r1 = 42;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "*(u64 *)(r10 - 16) = r1;"
+ "r1 = 2;"
+ "r2 = cb_write_read ll;"
+ "r3 = r10;"
+ "r3 += -8;"
+ "r4 = 0;"
+ "call %[bpf_loop];"
+ "r0 = 0;"
+ "exit;"
+ :: __imm(bpf_loop)
+ : __clobber_all);
+}
+
+/* Callback unconditionally writes *(ctx-8), then reads it back.
+ * The write (parent_def) kills liveness before entry.
+ */
+static __used __naked void cb_write_read(void)
+{
+ asm volatile (
+ "r6 = r2;"
+ "r7 = 99;"
+ "*(u64 *)(r6 - 8) = r7;"
+ "r0 = *(u64 *)(r6 - 8);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/*
+ * bpf_loop callback conditionally writes fp-16 then unconditionally
+ * reads it. The conditional write does NOT kill liveness
+ */
+SEC("socket")
+__log_level(2)
+__msg("9: (85) call bpf_loop#181 ; use: fp0-16")
+__naked void callback_conditional_write_preserves(void)
+{
+ asm volatile (
+ "r1 = 42;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "*(u64 *)(r10 - 16) = r1;"
+ "r1 = 2;"
+ "r2 = cb_cond_write_read ll;"
+ "r3 = r10;"
+ "r3 += -8;"
+ "r4 = 0;"
+ "call %[bpf_loop];"
+ "r0 = 0;"
+ "exit;"
+ :: __imm(bpf_loop)
+ : __clobber_all);
+}
+
+static __used __naked void cb_cond_write_read(void)
+{
+ asm volatile (
+ "r6 = r2;"
+ "call %[bpf_get_prandom_u32];"
+ "if r0 == 0 goto 1f;"
+ "*(u64 *)(r6 - 8) = r0;"
+ "1:"
+ "r0 = *(u64 *)(r6 - 8);"
+ "r0 = 0;"
+ "exit;"
+ :: __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * Two bpf_loop calls with the same callback but different ctx pointers.
+ *
+ * First call: ctx=fp-8, second call: ctx=fp-24.
+ */
+SEC("socket")
+__log_level(2)
+__msg(" 8: (85) call bpf_loop{{.*}} ; use: fp0-8")
+__msg("15: (85) call bpf_loop{{.*}} ; use: fp0-24")
+__naked void callback_two_calls_different_ctx(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 8) = 0;"
+ "*(u64 *)(r10 - 24) = 0;"
+ "r1 = 1;"
+ "r2 = cb_read_ctx ll;"
+ "r3 = r10;"
+ "r3 += -8;"
+ "r4 = 0;"
+ "call %[bpf_loop];"
+ "r1 = 1;"
+ "r2 = cb_read_ctx ll;"
+ "r3 = r10;"
+ "r3 += -24;"
+ "r4 = 0;"
+ "call %[bpf_loop];"
+ "r0 = 0;"
+ "exit;"
+ :: __imm(bpf_loop)
+ : __clobber_all);
+}
+
+/* Callback reads at ctx+0 unconditionally */
+static __used __naked void cb_read_ctx(void)
+{
+ asm volatile (
+ "r0 = *(u64 *)(r2 + 0);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/*
+ * Reproducer for unsound pruning in refined_caller_live_stack().
+ *
+ * Three-level call chain: main → mid_fwd → grandchild_deref.
+ * Main passes &fp-8 to mid_fwd, which forwards R1 to grandchild_deref.
+ * grandchild_deref reads main's fp-8 through the forwarded pointer
+ * and dereferences the result.
+ *
+ * refined_caller_live_stack() has a callee_offset++ when mid_fwd
+ * (frame 1) is mid-call. This drops the transitive parent_live
+ * contribution at mid_fwd's call instruction — the only place
+ * where grandchild_deref's read of main's fp-8 is recorded.
+ * As a result, main's fp-8 is cleaned to STACK_INVALID at the
+ * pruning point inside grandchild_deref, and path B is
+ * incorrectly pruned against path A.
+ *
+ * Path A: main stores PTR_TO_MAP_VALUE at fp-8
+ * Path B: main stores scalar 42 at fp-8
+ *
+ * Correct behavior: reject (path B dereferences scalar)
+ * Bug behavior: accept (path B pruned against cleaned path A)
+ */
+SEC("socket")
+__flag(BPF_F_TEST_STATE_FREQ)
+__failure __msg("R0 invalid mem access 'scalar'")
+__naked void transitive_parent_stack_read_unsound(void)
+{
+ asm volatile (
+ /* Map lookup to get PTR_TO_MAP_VALUE */
+ "r1 = %[map] ll;"
+ "*(u32 *)(r10 - 32) = 0;"
+ "r2 = r10;"
+ "r2 += -32;"
+ "call %[bpf_map_lookup_elem];"
+ "if r0 == 0 goto l_exit%=;"
+ "r6 = r0;"
+ /* Branch: path A (fall-through) explored first */
+ "call %[bpf_get_prandom_u32];"
+ "if r0 != 0 goto l_scalar%=;"
+ /* Path A: fp-8 = PTR_TO_MAP_VALUE */
+ "*(u64 *)(r10 - 8) = r6;"
+ "goto l_merge%=;"
+"l_scalar%=:"
+ /* Path B: fp-8 = scalar 42 */
+ "r1 = 42;"
+ "*(u64 *)(r10 - 8) = r1;"
+"l_merge%=:"
+ /* Pass &fp-8 to mid_fwd → grandchild_deref */
+ "r1 = r10;"
+ "r1 += -8;"
+ "call mid_fwd;"
+ "r0 = 0;"
+ "exit;"
+"l_exit%=:"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_map_lookup_elem),
+ __imm(bpf_get_prandom_u32),
+ __imm_addr(map)
+ : __clobber_all);
+}
+
+/* Forwards R1 (ptr to main's fp-8) to grandchild_deref */
+static __used __naked void mid_fwd(void)
+{
+ asm volatile (
+ "call grandchild_deref;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* Reads main's fp-8 through forwarded pointer, dereferences result */
+static __used __naked void grandchild_deref(void)
+{
+ asm volatile (
+ "goto +0;" /* checkpoint */
+ "goto +0;" /* checkpoint */
+ /* read main's fp-8: map_ptr (path A) or scalar (path B) */
+ "r0 = *(u64 *)(r1 + 0);"
+ /* dereference: safe for map_ptr, unsafe for scalar */
+ "r0 = *(u64 *)(r0 + 0);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+SEC("socket")
+__log_level(2)
+__success
+__msg("14: (79) r1 = *(u64 *)(r10 -8) // r6=fp0-8 r7=fp1-16 fp-8=fp1-16 fp-16=fp0-8")
+__msg("15: (79) r0 = *(u64 *)(r1 +0) // r1=fp1-16 r6=fp0-8 r7=fp1-16 fp-8=fp1-16 fp-16=fp0-8")
+__msg("stack use/def subprog#1 mid_two_fp_threshold (d1,cs2):")
+__msg("14: (79) r1 = *(u64 *)(r10 -8) ; use: fp1-8")
+__msg("15: (79) r0 = *(u64 *)(r1 +0) ; use: fp1-16")
+__naked void two_fp_clear_stack_threshold(void)
+{
+ asm volatile (
+ "r1 = r10;"
+ "r1 += -8;"
+ "call mid_two_fp_threshold;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void mid_two_fp_threshold(void)
+{
+ asm volatile (
+ "r6 = r1;"
+ "r7 = r10;"
+ "r7 += -16;"
+ "*(u64 *)(r10 - 8) = r7;"
+ "*(u64 *)(r10 - 16) = r6;"
+ "r1 = r10;"
+ "r1 += -8;"
+ "r2 = r6;"
+ "call inner_nop_fptest;"
+ "r1 = *(u64 *)(r10 - 8);"
+ "r0 = *(u64 *)(r1 + 0);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void inner_nop_fptest(void)
+{
+ asm volatile (
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+SEC("socket")
+__log_level(2)
+__success
+__msg("13: (79) r1 = *(u64 *)(r10 -8) // r6=fp0-8 r7=fp1-16 fp-8=fp1-16 fp-16=fp0-8")
+__msg("14: (79) r0 = *(u64 *)(r1 +0) // r1=fp1-16 r6=fp0-8 r7=fp1-16 fp-8=fp1-16 fp-16=fp0-8")
+__msg("stack use/def subprog#1 mid_one_fp_threshold (d1,cs2):")
+__msg("13: (79) r1 = *(u64 *)(r10 -8) ; use: fp1-8")
+__msg("14: (79) r0 = *(u64 *)(r1 +0) ; use: fp1-16")
+__naked void one_fp_clear_stack_threshold(void)
+{
+ asm volatile (
+ "r1 = r10;"
+ "r1 += -8;"
+ "call mid_one_fp_threshold;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void mid_one_fp_threshold(void)
+{
+ asm volatile (
+ "r6 = r1;"
+ "r7 = r10;"
+ "r7 += -16;"
+ "*(u64 *)(r10 - 8) = r7;"
+ "*(u64 *)(r10 - 16) = r6;"
+ "r1 = r10;"
+ "r1 += -8;"
+ "call inner_nop_fptest;"
+ "r1 = *(u64 *)(r10 - 8);"
+ "r0 = *(u64 *)(r1 + 0);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/*
+ * Reproducer for unsound pruning when a subprog forwards a parent
+ * stack pointer (AT_PARENT) to a helper with a memory argument.
+ *
+ * set_call_stack_access_at() previously only tracked AT_CURRENT args,
+ * skipping AT_PARENT entirely. This meant helper reads through parent
+ * stack pointers did not set parent_use, letting the slot appear dead
+ * at pruning checkpoints inside the subprog.
+ *
+ * Program shape:
+ * main:
+ * *(u32)(fp-4) = 0 key = STACK_ZERO (const 0)
+ * call bpf_get_prandom_u32
+ * if r0 != 0 goto clobber path A (fall-through) first
+ * goto merge
+ * clobber:
+ * *(u8)(fp-4) = r0 path B: key[0] = STACK_MISC
+ * merge:
+ * r1 = fp - 4
+ * call fwd_parent_key_to_helper
+ * r0 = 0
+ * exit
+ *
+ * fwd_parent_key_to_helper(r1 = &caller_fp-4):
+ * goto +0 checkpoint
+ * r2 = r1 R2 = AT_PARENT ptr to caller fp-4
+ * r1 = array_map_8b ll R1 = array map
+ * call bpf_map_lookup_elem reads key_size(4) from parent fp-4
+ * r0 = *(u64 *)(r0 + 0) deref without null check
+ * r0 = 0
+ * exit
+ *
+ * Path A: STACK_ZERO key = const 0 -> array lookup -> PTR_TO_MAP_VALUE
+ * (non-NULL for in-bounds const key) -> deref OK.
+ * Path B: STACK_MISC key = unknown -> array lookup ->
+ * PTR_TO_MAP_VALUE_OR_NULL -> deref UNSAFE.
+ *
+ * Bug: AT_PARENT R2 arg to bpf_map_lookup_elem skipped -> parent_use
+ * not set -> fp-4 cleaned at checkpoint -> STACK_ZERO collapses
+ * to STACK_INVALID -> path B pruned -> deref never checked.
+ *
+ * Correct verifier behavior: reject (path B deref of map_value_or_null).
+ */
+SEC("socket")
+__flag(BPF_F_TEST_STATE_FREQ)
+__failure __msg("R0 invalid mem access 'map_value_or_null'")
+__naked void helper_parent_stack_read_unsound(void)
+{
+ asm volatile (
+ /* key at fp-4: all bytes STACK_ZERO */
+ "*(u32 *)(r10 - 4) = 0;"
+ "call %[bpf_get_prandom_u32];"
+ /* fall-through (path A) explored first */
+ "if r0 != 0 goto l_clobber%=;"
+ /* path A: key stays constant zero */
+ "goto l_merge%=;"
+"l_clobber%=:"
+ /* path B: key[0] becomes STACK_MISC, key no longer const */
+ "*(u8 *)(r10 - 4) = r0;"
+"l_merge%=:"
+ "r1 = r10;"
+ "r1 += -4;"
+ "call fwd_parent_key_to_helper;"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * Subprog forwards parent stack pointer to bpf_map_lookup_elem as key
+ * on an array map, then dereferences the result without a null check.
+ * R1 = &parent_fp-4 (AT_PARENT in this frame).
+ *
+ * The helper reads key_size(4) bytes from parent stack. The deref of
+ * R0 reads the map value, NOT parent stack, so record_insn_mem_accesses
+ * does not set parent_use for it. The ONLY parent stack access is
+ * through the helper's R2 arg.
+ */
+static __used __naked void fwd_parent_key_to_helper(void)
+{
+ asm volatile (
+ "goto +0;" /* checkpoint */
+ "r2 = r1;" /* R2 = parent ptr (AT_PARENT) */
+ "r1 = %[array_map_8b] ll;" /* R1 = array map */
+ "call %[bpf_map_lookup_elem];" /* reads 4 bytes from parent fp-4 */
+ /* deref without null check: safe for PTR_TO_MAP_VALUE,
+ * unsafe for PTR_TO_MAP_VALUE_OR_NULL
+ */
+ "r0 = *(u64 *)(r0 + 0);"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(array_map_8b)
+ : __clobber_all);
+}
+
+/*
+ * Regression for keeping later helper args after a whole-stack fallback
+ * on an earlier local arg. The first bpf_snprintf() arg is a local
+ * frame-derived pointer with offset-imprecise tracking (`fp1 ?`), which
+ * conservatively marks the whole local stack live. The fourth arg still
+ * forwards &parent_fp-8 and must contribute nonlocal_use[0]=0:3.
+ */
+SEC("socket")
+__log_level(2)
+__success
+__msg("call bpf_snprintf{{.*}} ; use: fp1-8..-512 fp0-8")
+__naked void helper_arg_fallback_keeps_scanning(void)
+{
+ asm volatile (
+ "r1 = 42;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r1 = r10;"
+ "r1 += -8;"
+ "call helper_snprintf_parent_after_local_fallback;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void helper_snprintf_parent_after_local_fallback(void)
+{
+ asm volatile (
+ "r6 = r1;" /* save &parent_fp-8 */
+ "call %[bpf_get_prandom_u32];"
+ "r0 &= 8;"
+ "r1 = r10;"
+ "r1 += -16;"
+ "r1 += r0;" /* local fp, offset-imprecise */
+ "r2 = 8;"
+ "r3 = %[snprintf_u64_fmt] ll;"
+ "r4 = r6;" /* later arg: parent fp-8 */
+ "r5 = 8;"
+ "call %[bpf_snprintf];"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_get_prandom_u32),
+ __imm(bpf_snprintf),
+ __imm_addr(snprintf_u64_fmt)
+ : __clobber_all);
+}
+
+/*
+ * Test that propagate_callee_ancestor() correctly chains ancestor
+ * liveness across sequential calls within a single frame.
+ *
+ * main → mid_seq_touch → {nop_callee, deref_ancestor}
+ *
+ * mid_seq_touch receives two pointers: R1 = &main_fp-8 (forwarded to
+ * deref_ancestor) and R2 = &main_fp-16 (read directly by mid_seq_touch).
+ * The direct read of fp-16 forces ensure_anc_arrays() to allocate
+ * ancestor_live[0] for mid_seq_touch, so refined_caller_live_stack()
+ * uses the refined path (not the conservative fallback).
+ *
+ * mid_seq_touch calls nop_callee first (no-op, creates a pruning point),
+ * then calls deref_ancestor which reads main's fp-8 and dereferences it.
+ *
+ * propagate_callee_ancestor() propagates deref_ancestor's entry
+ * ancestor_live[0] into mid_seq_touch's anc_use[0] at the call-to-deref
+ * instruction. mid_seq_touch's backward pass flows this backward so
+ * ancestor_live[0] includes fp-8 at the pruning point between the calls.
+ *
+ * Without propagation, mid_seq_touch's ancestor_live[0] only has fp-16
+ * (from the direct read) — fp-8 is missing. refined_caller_live_stack()
+ * Term 1 says fp-8 is dead, the verifier cleans it, and path B
+ * (scalar 42) is incorrectly pruned against path A (MAP_VALUE).
+ *
+ * Path A: main stores PTR_TO_MAP_VALUE at fp-8 → deref succeeds
+ * Path B: main stores scalar 42 at fp-8 → deref must fail
+ *
+ * Correct: reject (path B dereferences scalar)
+ */
+SEC("socket")
+__flag(BPF_F_TEST_STATE_FREQ)
+__failure __msg("R0 invalid mem access 'scalar'")
+__naked void propagate_callee_ancestor_chain(void)
+{
+ asm volatile (
+ /* Map lookup to get PTR_TO_MAP_VALUE */
+ "r1 = %[map] ll;"
+ "*(u32 *)(r10 - 32) = 0;"
+ "r2 = r10;"
+ "r2 += -32;"
+ "call %[bpf_map_lookup_elem];"
+ "if r0 == 0 goto l_exit%=;"
+ "r6 = r0;"
+ /* Branch: path A (fall-through) explored first */
+ "call %[bpf_get_prandom_u32];"
+ "if r0 != 0 goto l_scalar%=;"
+ /* Path A: fp-8 = PTR_TO_MAP_VALUE */
+ "*(u64 *)(r10 - 8) = r6;"
+ "goto l_merge%=;"
+"l_scalar%=:"
+ /* Path B: fp-8 = scalar 42 */
+ "r1 = 42;"
+ "*(u64 *)(r10 - 8) = r1;"
+"l_merge%=:"
+ /* fp-16 = dummy value (mid_seq_touch reads it directly) */
+ "r1 = 99;"
+ "*(u64 *)(r10 - 16) = r1;"
+ /* R1 = &fp-8 (for deref_ancestor), R2 = &fp-16 (for mid_seq_touch) */
+ "r1 = r10;"
+ "r1 += -8;"
+ "r2 = r10;"
+ "r2 += -16;"
+ "call mid_seq_touch;"
+ "r0 = 0;"
+ "exit;"
+"l_exit%=:"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_map_lookup_elem),
+ __imm(bpf_get_prandom_u32),
+ __imm_addr(map)
+ : __clobber_all);
+}
+
+/*
+ * R1 = &main_fp-8 (forwarded to deref_ancestor)
+ * R2 = &main_fp-16 (read directly here → allocates ancestor_live[0])
+ *
+ * Reads main's fp-16 to force ancestor_live[0] allocation, then
+ * calls nop_callee (pruning point), then deref_ancestor.
+ */
+static __used __naked void mid_seq_touch(void)
+{
+ asm volatile (
+ "r6 = r1;" /* save &main_fp-8 in callee-saved */
+ "r0 = *(u64 *)(r2 + 0);" /* read main's fp-16: triggers anc_use[0] */
+ "call nop_callee;" /* no-op, creates pruning point after */
+ "r1 = r6;" /* restore ptr to &main_fp-8 */
+ "call deref_ancestor;" /* reads main's fp-8, dereferences */
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void nop_callee(void)
+{
+ asm volatile (
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* Reads main's fp-8 through forwarded pointer, dereferences result */
+static __used __naked void deref_ancestor(void)
+{
+ asm volatile (
+ "r0 = *(u64 *)(r1 + 0);" /* read main's fp-8 */
+ "r0 = *(u64 *)(r0 + 0);" /* deref: safe for map_ptr, unsafe for scalar */
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/*
+ * Test: callee loads an fp-derived pointer from caller's stack, then
+ * reads through it to access another caller stack slot.
+ *
+ * main stores PTR_TO_MAP_VALUE at fp-24, stores &fp-24 (an fp-derived
+ * pointer) at fp-8, passes &fp-8 through mid_fwd_spilled_ptr to
+ * load_ptr_deref_grandchild. The leaf loads the pointer from main's
+ * fp-8, then reads main's fp-24 through the loaded pointer.
+ *
+ * fill_from_stack() in arg_track_xfer() only handles local-frame
+ * FP-derived loads (src_is_local_fp check requires frame == depth).
+ * When a callee loads from a parent-frame pointer (frame < depth),
+ * the loaded value gets ARG_NONE instead of being recognized as
+ * fp-derived. Subsequent reads through that loaded pointer are
+ * invisible to liveness — nonlocal_use is never set for fp-24.
+ *
+ * clean_live_states() cleans the current state at every prune point.
+ * Because liveness misses fp-24, refined_caller_live_stack() tells
+ * __clean_func_state() that fp-24 is dead, which destroys the
+ * PTR_TO_MAP_VALUE spill before the grandchild can read it.
+ * The grandchild then reads STACK_INVALID → scalar, and the deref
+ * is rejected with "R0 invalid mem access 'scalar'" — even though
+ * fp-24 is genuinely live and holds a valid map pointer.
+ *
+ * This is a false positive: a valid program incorrectly rejected.
+ */
+SEC("socket")
+__flag(BPF_F_TEST_STATE_FREQ)
+__success
+__naked void spilled_fp_cross_frame_deref(void)
+{
+ asm volatile (
+ /* Map lookup to get PTR_TO_MAP_VALUE */
+ "r1 = %[map] ll;"
+ "*(u32 *)(r10 - 32) = 0;"
+ "r2 = r10;"
+ "r2 += -32;"
+ "call %[bpf_map_lookup_elem];"
+ "if r0 == 0 goto l_exit%=;"
+ /* fp-24 = PTR_TO_MAP_VALUE */
+ "*(u64 *)(r10 - 24) = r0;"
+ /* Store pointer to fp-24 at fp-8 */
+ "r1 = r10;"
+ "r1 += -24;"
+ "*(u64 *)(r10 - 8) = r1;"
+ /* R1 = &fp-8: pointer to the spilled ptr */
+ "r1 = r10;"
+ "r1 += -8;"
+ "call mid_fwd_spilled_ptr;"
+ "r0 = 0;"
+ "exit;"
+"l_exit%=:"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map)
+ : __clobber_all);
+}
+
+/* Forwards R1 (ptr to main's fp-8, which holds &main_fp-24) to leaf */
+static __used __naked void mid_fwd_spilled_ptr(void)
+{
+ asm volatile (
+ "call load_ptr_deref_grandchild;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/*
+ * R1 = &main_fp-8 (where main stored ptr to fp-24)
+ * Loads the ptr from main's fp-8, reads main's fp-24 through it,
+ * then dereferences the result.
+ */
+static __used __naked void load_ptr_deref_grandchild(void)
+{
+ asm volatile (
+ /* Load ptr from main's fp-8 → r2 = &main_fp-24 */
+ "r2 = *(u64 *)(r1 + 0);"
+ /* Read main's fp-24 through loaded ptr */
+ "r0 = *(u64 *)(r2 + 0);"
+ /* Dereference: safe for map_ptr */
+ "r0 = *(u64 *)(r0 + 0);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/*
+ * Exercise merge_nonlocal_live().
+ *
+ * merge_shared_mid is analyzed twice (once from each wrapper), so the
+ * callsite within merge_shared_mid that calls merge_leaf_read gets its
+ * nonlocal_live info merged twice via merge_nonlocal_live().
+ */
+SEC("socket")
+__log_level(2)
+__success
+__msg("14: (85) call pc+2 r1: fp0-16")
+__msg("17: (79) r0 = *(u64 *)(r1 +0) // r1=fp0-16")
+__msg("14: (85) call pc+2 r1: fp0-8")
+__msg("17: (79) r0 = *(u64 *)(r1 +0) // r1=fp0-8")
+__msg("5: (85) call pc+{{.*}} ; use: fp0-8 fp0-16")
+__naked void test_merge_nonlocal_live(void)
+{
+ asm volatile (
+ "r1 = 0;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "*(u64 *)(r10 - 16) = r1;"
+ "r1 = r10;"
+ "r1 += -8;"
+ "call merge_wrapper_a;"
+ "r1 = r10;"
+ "r1 += -16;"
+ "call merge_wrapper_b;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void merge_wrapper_a(void)
+{
+ asm volatile (
+ "call merge_shared_mid;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void merge_wrapper_b(void)
+{
+ asm volatile (
+ "call merge_shared_mid;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void merge_shared_mid(void)
+{
+ asm volatile (
+ "call merge_leaf_read;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void merge_leaf_read(void)
+{
+ asm volatile (
+ "r0 = *(u64 *)(r1 + 0);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* Same bpf_loop instruction calls different callbacks depending on branch. */
+SEC("socket")
+__log_level(2)
+__success
+__msg("call bpf_loop#181 ; use: fp2-8..-512 fp1-8..-512 fp0-8..-512")
+__naked void bpf_loop_two_callbacks(void)
+{
+ asm volatile (
+ "r1 = 0;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "*(u64 *)(r10 - 16) = r1;"
+ "r1 = r10;"
+ "r1 += -8;"
+ "call dyn_wrapper_a;"
+ "r1 = r10;"
+ "r1 += -16;"
+ "call dyn_wrapper_b;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void dyn_wrapper_a(void)
+{
+ asm volatile (
+ "call mid_dynamic_cb;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void dyn_wrapper_b(void)
+{
+ asm volatile (
+ "call mid_dynamic_cb;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void mid_dynamic_cb(void)
+{
+ asm volatile (
+ "r6 = r1;"
+ "call %[bpf_get_prandom_u32];"
+ "if r0 == 0 goto 1f;"
+ "r2 = dyn_cb_a ll;"
+ "goto 2f;"
+ "1:"
+ "r2 = dyn_cb_b ll;"
+ "2:"
+ "r1 = 1;"
+ "r3 = r6;" /* ctx = fp-derived ptr from parent */
+ "r4 = 0;"
+ "call %[bpf_loop];"
+ "r0 = 0;"
+ "exit;"
+ :: __imm(bpf_get_prandom_u32),
+ __imm(bpf_loop)
+ : __clobber_all);
+}
+
+/* Callback A/B: read parent stack through ctx */
+static __used __naked void dyn_cb_a(void)
+{
+ asm volatile (
+ "r0 = *(u64 *)(r2 + 0);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void dyn_cb_b(void)
+{
+ asm volatile (
+ "r0 = *(u64 *)(r2 + 0);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/*
+ * Path A: r0 = map_lookup result (non-FP, ARG_NONE for stack tracking)
+ * Path B: r0 = fp-8 (FP-derived, frame=0, off=-8)
+ * At the join: r0 is not guaranteed to be a frame pointer.
+ */
+SEC("socket")
+__log_level(2)
+__msg("10: (79) r0 = *(u64 *)(r10 -8) // r0=fp0-8|fp0+0")
+__naked void stack_or_non_stack_write(void)
+{
+ asm volatile (
+ /* initial write to fp-8 */
+ "*(u64 *)(r10 - 8) = 0;"
+ /* map lookup to get a non-FP pointer */
+ "r2 = r10;"
+ "r2 += -4;"
+ "r1 = %[map] ll;"
+ "call %[bpf_map_lookup_elem];"
+ /* r0 = map_value (ARG_NONE) */
+ "if r0 != 0 goto 1f;"
+ /* path B: r0 = fp-8 */
+ "r0 = r10;"
+ "r0 += -8;"
+"1:"
+ /* join: the write is not a def for fp[0]-8 */
+ "*(u64 *)(r0 + 0) = 7;"
+ /* read fp-8: should be non-poisoned */
+ "r0 = *(u64 *)(r10 - 8);"
+ "exit;"
+ :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map)
+ : __clobber_all);
+}
+
+SEC("socket")
+__log_level(2)
+__flag(BPF_F_TEST_STATE_FREQ)
+__msg("subprog#2 write_first_read_second:")
+__msg("17: (7a) *(u64 *)(r1 +0) = 42{{$}}")
+__msg("18: (79) r0 = *(u64 *)(r2 +0) // r1=fp0-8 r2=fp0-16{{$}}")
+__msg("stack use/def subprog#2 write_first_read_second (d2,cs15):")
+__msg("17: (7a) *(u64 *)(r1 +0) = 42{{$}}")
+__msg("18: (79) r0 = *(u64 *)(r2 +0) ; use: fp0-8 fp0-16")
+__naked void shared_instance_must_write_overwrite(void)
+{
+ asm volatile (
+ "r1 = 1;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "*(u64 *)(r10 - 16) = r1;"
+ /* Call 1: write_first_read_second(&fp[-8], &fp[-16]) */
+ "r1 = r10;"
+ "r1 += -8;"
+ "r2 = r10;"
+ "r2 += -16;"
+ "call forwarding_rw;"
+ /* Call 2: write_first_read_second(&fp[-16], &fp[-8]) */
+ "r1 = r10;"
+ "r1 += -16;"
+ "r2 = r10;"
+ "r2 += -8;"
+ "call forwarding_rw;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void forwarding_rw(void)
+{
+ asm volatile (
+ "call write_first_read_second;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void write_first_read_second(void)
+{
+ asm volatile (
+ "*(u64 *)(r1 + 0) = 42;"
+ "r0 = *(u64 *)(r2 + 0);"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/*
+ * Shared must_write when (callsite, depth) instance is reused.
+ * Main calls fwd_to_stale_wr at two sites. fwd_to_stale_wr calls
+ * stale_wr_leaf at a single internal callsite. Both calls share
+ * stale_wr_leaf's (callsite, depth) instance.
+ *
+ * Call 1: stale_wr_leaf(map_value, fp-8) writes map, reads fp-8.
+ * Call 2: stale_wr_leaf(fp-8, fp-8) writes fp-8, reads fp-8.
+ *
+ * The analysis can't presume that stale_wr_leaf() always writes fp-8,
+ * it must conservatively join must_write masks computed for both calls.
+ */
+SEC("socket")
+__success
+__naked void stale_must_write_cross_callsite(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 8) = 0;"
+ /* Call 1: map_value write, fp-8 read (processed second in PO) */
+ "*(u32 *)(r10 - 16) = 0;"
+ "r1 = %[map] ll;"
+ "r2 = r10;"
+ "r2 += -16;"
+ "call %[bpf_map_lookup_elem];"
+ "if r0 == 0 goto 1f;"
+ "r1 = r0;"
+ "r2 = r10;"
+ "r2 += -8;"
+ "call fwd_to_stale_wr;"
+ /* Call 2: fp-8 write, fp-8 read (processed first in PO) */
+ "r1 = r10;"
+ "r1 += -8;"
+ "r2 = r1;"
+ "call fwd_to_stale_wr;"
+"1:"
+ "r0 = 0;"
+ "exit;"
+ :: __imm_addr(map),
+ __imm(bpf_map_lookup_elem)
+ : __clobber_all);
+}
+
+static __used __naked void fwd_to_stale_wr(void)
+{
+ asm volatile (
+ "call stale_wr_leaf;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void stale_wr_leaf(void)
+{
+ asm volatile (
+ "*(u64 *)(r1 + 0) = 42;"
+ "r0 = *(u64 *)(r2 + 0);"
+ "exit;"
+ ::: __clobber_all);
+}
+
+#ifdef CAN_USE_LOAD_ACQ_STORE_REL
+
+SEC("socket")
+__log_level(2)
+__success
+__msg("*(u64 *)(r0 +0) = 42 ; def: fp0-16")
+__naked void load_acquire_dont_clear_dst(void)
+{
+ asm volatile (
+ "r0 = r10;"
+ "r0 += -16;"
+ "*(u64 *)(r0 + 0) = r0;" /* fp[-16] == &fp[-16] */
+ ".8byte %[load_acquire_insn];" /* load_acquire is a special case for BPF_STX, */
+ "r0 = *(u64 *)(r10 - 16);" /* it shouldn't clear tracking info for */
+ "*(u64 *)(r0 + 0) = 42;" /* dst register, r0 in this case. */
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(load_acquire_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_LOAD_ACQ, BPF_REG_0, BPF_REG_0, 0))
+ : __clobber_all);
+}
+
+#endif /* CAN_USE_LOAD_ACQ_STORE_REL */
+
+SEC("socket")
+__success
+__naked void imprecise_fill_loses_cross_frame(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 8) = 0;"
+ "r1 = r10;"
+ "r1 += -8;"
+ "call imprecise_fill_cross_frame;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+static __used __naked void imprecise_fill_cross_frame(void)
+{
+ asm volatile (
+ /* spill &caller_fp-8 to callee's fp-8 */
+ "*(u64 *)(r10 - 8) = r1;"
+ /* imprecise FP pointer in r1 */
+ "r1 = r10;"
+ "r2 = -8;"
+ "r1 += r2;"
+ /* load from imprecise offset. fill_from_stack returns
+ * ARG_IMPRECISE{mask=BIT(1)}, losing frame 0
+ */
+ "r1 = *(u64 *)(r1 + 0);"
+ /* read caller's fp-8 through loaded pointer, should mark fp0-8 live */
+ "r0 = *(u64 *)(r1 + 0);"
+ "r0 = 0;"
+ "exit;"
+ :: __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* Test that spill_to_stack with multi-offset dst (sz=8) joins instead
+ * of overwriting. r1 has offsets [-8, -16]. Both slots hold FP-derived
+ * pointers. Writing through r1 should join *val with existing values,
+ * not destroy them.
+ *
+ * fp-8 = &fp-24
+ * fp-16 = &fp-32
+ * r1 = fp-8 or fp-16 (two offsets from branch)
+ * *(u64 *)(r1 + 0) = &fp-24 -- writes to one slot, other untouched
+ * r0 = *(u64 *)(r10 - 16) -- fill from fp-16
+ * r0 = *(u64 *)(r0 + 0) -- deref: should produce use
+ */
+SEC("socket")
+__log_level(2)
+__success
+__msg("20: (79) r0 = *(u64 *)(r10 -16)")
+__msg("21: (79) r0 = *(u64 *)(r0 +0) ; use: fp0-24 fp0-32")
+__naked void spill_join_with_multi_off(void)
+{
+ asm volatile (
+ /* fp-8 = &fp-24, fp-16 = &fp-32 (different pointers) */
+ "*(u64 *)(r10 - 24) = 0;"
+ "*(u64 *)(r10 - 32) = 0;"
+ "r1 = r10;"
+ "r1 += -24;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r1 = r10;"
+ "r1 += -32;"
+ "*(u64 *)(r10 - 16) = r1;"
+ /* create r1 with two candidate offsets: fp-8 or fp-16 */
+ "call %[bpf_get_prandom_u32];"
+ "if r0 == 0 goto 1f;"
+ "r1 = r10;"
+ "r1 += -8;"
+ "goto 2f;"
+"1:"
+ "r1 = r10;"
+ "r1 += -16;"
+"2:"
+ /* write &fp-24 through multi-offset r1: hits one slot, other untouched */
+ "r2 = r10;"
+ "r2 += -24;"
+ "*(u64 *)(r1 + 0) = r2;"
+ /* read back *fp-8 and *fp-16 */
+ "r0 = *(u64 *)(r10 - 8);"
+ "r0 = *(u64 *)(r0 + 0);"
+ "r0 = *(u64 *)(r10 - 16);"
+ "r0 = *(u64 *)(r0 + 0);"
+ "exit;"
+ :: __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* Test that spill_to_stack with imprecise dst (off_cnt == 0, sz=8)
+ * joins instead of overwriting. Use "r2 = -8; r1 += r2" to make
+ * arg tracking lose offset precision while the main verifier keeps
+ * r1 as PTR_TO_STACK with fixed offset. Both slots hold FP-derived
+ * pointers. Writing through r1 should join *val with existing
+ * values, not destroy them.
+ *
+ * fp-8 = &fp-24
+ * fp-16 = &fp-32
+ * r1 = fp-8 (imprecise to arg tracking)
+ * *(u64 *)(r1 + 0) = &fp-24 -- since r1 is imprecise, this adds &fp-24
+ * to the set of possible values for all slots,
+ * hence the values at fp-16 become [fp-24, fp-32]
+ * r0 = *(u64 *)(r10 - 16)
+ * r0 = *(u64 *)(r0 + 0) -- deref: should produce use of fp-24 or fp-32
+ */
+SEC("socket")
+__log_level(2)
+__success
+__msg("15: (79) r0 = *(u64 *)(r0 +0) ; use: fp0-24 fp0-32")
+__naked void spill_join_with_imprecise_off(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 24) = 0;"
+ "*(u64 *)(r10 - 32) = 0;"
+ "r1 = r10;"
+ "r1 += -24;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r1 = r10;"
+ "r1 += -32;"
+ "*(u64 *)(r10 - 16) = r1;"
+ /* r1 = fp-8 but arg tracking sees off_cnt == 0 */
+ "r1 = r10;"
+ "r2 = -8;"
+ "r1 += r2;"
+ /* write through imprecise r1 */
+ "r3 = r10;"
+ "r3 += -24;"
+ "*(u64 *)(r1 + 0) = r3;"
+ /* read back fp-16: at_stack should still track &fp-32 */
+ "r0 = *(u64 *)(r10 - 16);"
+ /* deref: should produce use for fp-32 */
+ "r0 = *(u64 *)(r0 + 0);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/*
+ * Same as spill_join_with_multi_off but the write is BPF_ST (store
+ * immediate) instead of BPF_STX. BPF_ST goes through
+ * clear_stack_for_all_offs() rather than spill_to_stack(), and that
+ * path also needs to join instead of overwriting.
+ *
+ * fp-8 = &fp-24
+ * fp-16 = &fp-32
+ * r1 = fp-8 or fp-16 (two offsets from branch)
+ * *(u64 *)(r1 + 0) = 0 -- BPF_ST with immediate
+ * r0 = *(u64 *)(r10 - 16) -- fill from fp-16
+ * r0 = *(u64 *)(r0 + 0) -- deref: should produce use
+ */
+SEC("socket")
+__log_level(2)
+__failure
+__msg("15: (7a) *(u64 *)(r1 +0) = 0 fp-8: fp0-24 -> fp0-24|fp0+0 fp-16: fp0-32 -> fp0-32|fp0+0")
+__msg("17: (79) r0 = *(u64 *)(r0 +0) ; use: fp0-32")
+__naked void st_imm_join_with_multi_off(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 24) = 0;"
+ "*(u64 *)(r10 - 32) = 0;"
+ "r1 = r10;"
+ "r1 += -24;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r1 = r10;"
+ "r1 += -32;"
+ "*(u64 *)(r10 - 16) = r1;"
+ /* create r1 with two candidate offsets: fp-8 or fp-16 */
+ "call %[bpf_get_prandom_u32];"
+ "if r0 == 0 goto 1f;"
+ "r1 = r10;"
+ "r1 += -8;"
+ "goto 2f;"
+"1:"
+ "r1 = r10;"
+ "r1 += -16;"
+"2:"
+ /* BPF_ST: store immediate through multi-offset r1 */
+ "*(u64 *)(r1 + 0) = 0;"
+ /* read back fp-16 and deref */
+ "r0 = *(u64 *)(r10 - 16);"
+ "r0 = *(u64 *)(r0 + 0);"
+ "r0 = 0;"
+ "exit;"
+ :: __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * Check that BPF_ST with a known offset fully overwrites stack slot
+ * from the arg tracking point of view.
+ */
+SEC("socket")
+__log_level(2)
+__success
+__msg("5: (7a) *(u64 *)(r1 +0) = 0 fp-8: fp0-16 -> _{{$}}")
+__naked void st_imm_join_with_single_off(void)
+{
+ asm volatile (
+ "r2 = r10;"
+ "r2 += -16;"
+ "*(u64 *)(r10 - 8) = r2;"
+ "r1 = r10;"
+ "r1 += -8;"
+ "*(u64 *)(r1 + 0) = 0;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/*
+ * Same as spill_join_with_imprecise_off but the write is BPF_ST.
+ * Use "r2 = -8; r1 += r2" to make arg tracking lose offset
+ * precision while the main verifier keeps r1 as fixed-offset.
+ *
+ * fp-8 = &fp-24
+ * fp-16 = &fp-32
+ * r1 = fp-8 (imprecise to arg tracking)
+ * *(u64 *)(r1 + 0) = 0 -- BPF_ST with immediate
+ * r0 = *(u64 *)(r10 - 16) -- fill from fp-16
+ * r0 = *(u64 *)(r0 + 0) -- deref: should produce use
+ */
+SEC("socket")
+__log_level(2)
+__success
+__msg("13: (79) r0 = *(u64 *)(r0 +0) ; use: fp0-32")
+__naked void st_imm_join_with_imprecise_off(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 24) = 0;"
+ "*(u64 *)(r10 - 32) = 0;"
+ "r1 = r10;"
+ "r1 += -24;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r1 = r10;"
+ "r1 += -32;"
+ "*(u64 *)(r10 - 16) = r1;"
+ /* r1 = fp-8 but arg tracking sees off_cnt == 0 */
+ "r1 = r10;"
+ "r2 = -8;"
+ "r1 += r2;"
+ /* store immediate through imprecise r1 */
+ "*(u64 *)(r1 + 0) = 0;"
+ /* read back fp-16 */
+ "r0 = *(u64 *)(r10 - 16);"
+ /* deref: should produce use */
+ "r0 = *(u64 *)(r0 + 0);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/*
+ * Test that spilling through an ARG_IMPRECISE pointer joins with
+ * existing at_stack values. Subprog receives r1 = fp0-24 and
+ * r2 = map_value, creates an ARG_IMPRECISE pointer by joining caller
+ * and callee FP on two branches.
+ *
+ * Setup: callee spills &fp1-16 to fp1-8 (precise, tracked).
+ * Then writes map_value through ARG_IMPRECISE r1 — on path A
+ * this hits fp1-8, on path B it hits caller stack.
+ * Since spill_to_stack is skipped for ARG_IMPRECISE dst,
+ * fp1-8 tracking isn't joined with none.
+ *
+ * Expected after the imprecise write:
+ * - arg tracking should show fp1-8 = fp1-16|fp1+0 (joined with none)
+ * - read from fp1-8 and deref should produce use for fp1-16
+ * - write through it should NOT produce def for fp1-16
+ */
+SEC("socket")
+__log_level(2)
+__success
+__msg("26: (79) r0 = *(u64 *)(r10 -8) // r1=IMP3 r6=fp0-24 r7=fp1-16 fp-8=fp1-16|fp1+0")
+__naked void imprecise_dst_spill_join(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 24) = 0;"
+ /* map lookup for a valid non-FP pointer */
+ "*(u32 *)(r10 - 32) = 0;"
+ "r1 = %[map] ll;"
+ "r2 = r10;"
+ "r2 += -32;"
+ "call %[bpf_map_lookup_elem];"
+ "if r0 == 0 goto 1f;"
+ /* r1 = &caller_fp-24, r2 = map_value */
+ "r1 = r10;"
+ "r1 += -24;"
+ "r2 = r0;"
+ "call imprecise_dst_spill_join_sub;"
+"1:"
+ "r0 = 0;"
+ "exit;"
+ :: __imm_addr(map),
+ __imm(bpf_map_lookup_elem)
+ : __clobber_all);
+}
+
+static __used __naked void imprecise_dst_spill_join_sub(void)
+{
+ asm volatile (
+ /* r6 = &caller_fp-24 (frame=0), r8 = map_value */
+ "r6 = r1;"
+ "r8 = r2;"
+ /* spill &fp1-16 to fp1-8: at_stack[0] = fp1-16 */
+ "*(u64 *)(r10 - 16) = 0;"
+ "r7 = r10;"
+ "r7 += -16;"
+ "*(u64 *)(r10 - 8) = r7;"
+ /* branch to create ARG_IMPRECISE pointer */
+ "call %[bpf_get_prandom_u32];"
+ /* path B: r1 = caller fp-24 (frame=0) */
+ "r1 = r6;"
+ "if r0 == 0 goto 1f;"
+ /* path A: r1 = callee fp-8 (frame=1) */
+ "r1 = r10;"
+ "r1 += -8;"
+"1:"
+ /* r1 = ARG_IMPRECISE{mask=BIT(0)|BIT(1)}.
+ * Write map_value (non-FP) through r1. On path A this overwrites fp1-8.
+ * Should join at_stack[0] with none: fp1-16|fp1+0.
+ */
+ "*(u64 *)(r1 + 0) = r8;"
+ /* read fp1-8: should be fp1-16|fp1+0 (joined) */
+ "r0 = *(u64 *)(r10 - 8);"
+ "*(u64 *)(r0 + 0) = 42;"
+ "r0 = 0;"
+ "exit;"
+ :: __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
diff --git a/tools/testing/selftests/bpf/progs/verifier_liveness_exp.c b/tools/testing/selftests/bpf/progs/verifier_liveness_exp.c
new file mode 100644
index 000000000000..72646fa2745e
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_liveness_exp.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+/*
+ * Exponential complexity in analyze_subprog() liveness analysis.
+ *
+ * analyze_subprog() recurses into each call site that passes FP-derived
+ * arguments, creating a unique func_instance per (callsite, depth).
+ * There is no memoization for callees reached with equivalent entry args.
+ * Even if memoization were added, it can be defeated by passing a distinct
+ * FP offset at each call site. arg_track keys on (frame, off[]), so
+ * r1=fp-8, r1=fp-16, ... r1=fp-400 produce 50 unique cache keys per level.
+ *
+ * This test chains 8 subprograms (within the MAX_CALL_FRAMES limit). Each
+ * intermediate function calls the next one 50 times, each time with a
+ * different FP-relative offset in r1.
+ *
+ * Without complexity limits in analyze_subprog() the resulting 50^7 ~ 7.8 * 10^11
+ * recursive analyze_subprog() calls will cause a CPU soft lockup or OOM.
+ *
+ * The BPF program itself is ~1200 instructions and perfectly valid.
+ */
+
+char _license[] SEC("license") = "GPL";
+
+/* Call fn with r1 = r10 + off (a unique FP-derived arg per call site) */
+#define C(fn, off) "r1 = r10;" \
+ "r1 += -" #off ";" \
+ "call " #fn ";"
+
+/* 50 calls, each with a distinct FP offset: -8, -16, ... -400 */
+#define CALLS_50(fn) \
+ C(fn, 8) C(fn, 16) C(fn, 24) C(fn, 32) C(fn, 40) \
+ C(fn, 48) C(fn, 56) C(fn, 64) C(fn, 72) C(fn, 80) \
+ C(fn, 88) C(fn, 96) C(fn, 104) C(fn, 112) C(fn, 120) \
+ C(fn, 128) C(fn, 136) C(fn, 144) C(fn, 152) C(fn, 160) \
+ C(fn, 168) C(fn, 176) C(fn, 184) C(fn, 192) C(fn, 200) \
+ C(fn, 208) C(fn, 216) C(fn, 224) C(fn, 232) C(fn, 240) \
+ C(fn, 248) C(fn, 256) C(fn, 264) C(fn, 272) C(fn, 280) \
+ C(fn, 288) C(fn, 296) C(fn, 304) C(fn, 312) C(fn, 320) \
+ C(fn, 328) C(fn, 336) C(fn, 344) C(fn, 352) C(fn, 360) \
+ C(fn, 368) C(fn, 376) C(fn, 384) C(fn, 392) C(fn, 400)
+
+/* Leaf: depth 7, no further calls */
+__naked __noinline __used
+static unsigned long exp_sub7(void)
+{
+ asm volatile (
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* depth 6 -> calls exp_sub7 x50 with distinct offsets */
+__naked __noinline __used
+static unsigned long exp_sub6(void)
+{
+ asm volatile (
+ CALLS_50(exp_sub7)
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* depth 5 -> calls exp_sub6 x50 */
+__naked __noinline __used
+static unsigned long exp_sub5(void)
+{
+ asm volatile (
+ CALLS_50(exp_sub6)
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* depth 4 -> calls exp_sub5 x50 */
+__naked __noinline __used
+static unsigned long exp_sub4(void)
+{
+ asm volatile (
+ CALLS_50(exp_sub5)
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* depth 3 -> calls exp_sub4 x50 */
+__naked __noinline __used
+static unsigned long exp_sub3(void)
+{
+ asm volatile (
+ CALLS_50(exp_sub4)
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* depth 2 -> calls exp_sub3 x50 */
+__naked __noinline __used
+static unsigned long exp_sub2(void)
+{
+ asm volatile (
+ CALLS_50(exp_sub3)
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/* depth 1 -> calls exp_sub2 x50 */
+__naked __noinline __used
+static unsigned long exp_sub1(void)
+{
+ asm volatile (
+ CALLS_50(exp_sub2)
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+/*
+ * Entry: depth 0. Calls exp_sub1 50 times, each with a distinct
+ * FP offset in r1. Every call site produces a unique arg_track,
+ * defeating any memoization keyed on entry args.
+ */
+SEC("?raw_tp")
+__failure __log_level(2)
+__msg("liveness analysis exceeded complexity limit")
+__naked int liveness_exponential_complexity(void)
+{
+ asm volatile (
+ CALLS_50(exp_sub1)
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
diff --git a/tools/testing/selftests/bpf/progs/verifier_load_acquire.c b/tools/testing/selftests/bpf/progs/verifier_load_acquire.c
index 74f4f19c10b8..d17026d7480d 100644
--- a/tools/testing/selftests/bpf/progs/verifier_load_acquire.c
+++ b/tools/testing/selftests/bpf/progs/verifier_load_acquire.c
@@ -3,6 +3,7 @@
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_core_read.h>
#include "../../../include/linux/filter.h"
#include "bpf_misc.h"
@@ -148,6 +149,22 @@ __naked void load_acquire_from_ctx_pointer(void)
: __clobber_all);
}
+SEC("socket")
+__description("load-acquire from ctx pointer, same dst and src register")
+__failure __failure_unpriv __msg("BPF_ATOMIC loads from R6 ctx is not allowed")
+__naked void load_acquire_ctx_same_dst_src(void)
+{
+ asm volatile (
+ "r6 = r1;"
+ ".8byte %[load_acquire_insn];" // w6 = load_acquire((u32 *)(r6 + 0));
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(load_acquire_insn,
+ BPF_ATOMIC_OP(BPF_W, BPF_LOAD_ACQ, BPF_REG_6, BPF_REG_6, 0))
+ : __clobber_all);
+}
+
SEC("xdp")
__description("load-acquire from pkt pointer")
__failure __msg("BPF_ATOMIC loads from R2 pkt is not allowed")
@@ -206,6 +223,33 @@ __naked void load_acquire_from_sock_pointer(void)
}
SEC("socket")
+__description("load-acquire from rdonly_untrusted_mem pointer")
+__failure __msg("BPF_ATOMIC loads from R{{[0-9]+}} rdonly_untrusted_mem is not allowed")
+int load_acquire_from_rdonly_untrusted_mem(void *ctx)
+{
+ __u64 val = 0;
+ void *p;
+
+ /*
+ * bpf_rdonly_cast(x, 0) yields PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED.
+ * A regular BPF_LDX from it is rewritten to BPF_PROBE_MEM, but a
+ * load-acquire is not, so it must be rejected, otherwise the JIT emits
+ * a plain load with no exception table entry and a fault would crash
+ * the kernel.
+ */
+ p = bpf_rdonly_cast(&val, 0);
+ asm volatile (
+ "r1 = %[p];"
+ ".8byte %[load_acquire_insn];" // r0 = load_acquire((u64 *)(r1 + 0));
+ :
+ : [p] "r" (p),
+ __imm_insn(load_acquire_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_LOAD_ACQ, BPF_REG_0, BPF_REG_1, 0))
+ : "r0", "r1");
+ return 0;
+}
+
+SEC("socket")
__description("load-acquire with invalid register R15")
__failure __failure_unpriv __msg("R15 is invalid")
__naked void load_acquire_with_invalid_reg(void)
diff --git a/tools/testing/selftests/bpf/progs/verifier_loops1.c b/tools/testing/selftests/bpf/progs/verifier_loops1.c
index fbdde80e7b90..48a966cda199 100644
--- a/tools/testing/selftests/bpf/progs/verifier_loops1.c
+++ b/tools/testing/selftests/bpf/progs/verifier_loops1.c
@@ -138,8 +138,7 @@ l0_%=: exit; \
SEC("tracepoint")
__description("bounded recursion")
__failure
-/* verifier limitation in detecting max stack depth */
-__msg("the call stack of 8 frames is too deep !")
+__msg("recursive call from")
__naked void bounded_recursion(void)
{
asm volatile (" \
@@ -304,4 +303,40 @@ __naked void maybe_exit_scc_bug1(void)
::: __clobber_all);
}
+/*
+ * The loop reads zero from the caller's stack on its first iteration and
+ * one from the callee's stack on its second iteration. At the loop header,
+ * only the frame number of the pointer in r1 changes.
+ */
+static __naked __noinline __used
+void loop_stack_frames_reg(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 8) = 1;"
+"1:"
+ "r0 = *(u64 *)(r1 + 0);"
+ "if r0 != 0 goto 2f;"
+ "r1 = r10;"
+ "r1 += -8;"
+ "goto 1b;"
+"2:"
+ "exit;"
+ ::: __clobber_all);
+}
+
+SEC("xdp")
+__description("bounded loop changing stack frame in a register")
+__success __retval(1)
+__flag(BPF_F_TEST_STATE_FREQ)
+__naked void bounded_loop_stack_frames_reg(void)
+{
+ asm volatile (
+ "*(u64 *)(r10 - 8) = 0;"
+ "r1 = r10;"
+ "r1 += -8;"
+ "call loop_stack_frames_reg;"
+ "exit;"
+ ::: __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_lsm.c b/tools/testing/selftests/bpf/progs/verifier_lsm.c
index 6af9100a37ff..c724bf389f5c 100644
--- a/tools/testing/selftests/bpf/progs/verifier_lsm.c
+++ b/tools/testing/selftests/bpf/progs/verifier_lsm.c
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
-#include <linux/bpf.h>
+#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
#include "bpf_misc.h"
SEC("lsm/file_permission")
@@ -159,4 +160,56 @@ __naked int disabled_hook_test3(void *ctx)
::: __clobber_all);
}
+SEC("lsm/mmap_file")
+__description("not null checking nullable pointer in bpf_lsm_mmap_file")
+__failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'")
+int BPF_PROG(no_null_check, struct file *file)
+{
+ struct inode *inode;
+
+ inode = file->f_inode;
+ __sink(inode);
+
+ return 0;
+}
+
+SEC("lsm/mmap_file")
+__description("null checking nullable pointer in bpf_lsm_mmap_file")
+__success
+int BPF_PROG(null_check, struct file *file)
+{
+ struct inode *inode;
+
+ if (file) {
+ inode = file->f_inode;
+ __sink(inode);
+ }
+
+ return 0;
+}
+
+SEC("lsm_cgroup/file_open")
+__description("sleepable lsm_cgroup program is rejected")
+__failure __msg("Program of this type cannot be sleepable")
+__flag(BPF_F_SLEEPABLE)
+int BPF_PROG(sleepable_lsm_cgroup)
+{
+ return 0;
+}
+
+SEC("lsm/file_mprotect")
+__description("lsm retval load must reset stale register bounds")
+__failure __msg("div by zero")
+__naked int retval_load_resets_bounds(void *ctx)
+{
+ asm volatile (
+ "r6 = 0;"
+ "r6 = *(u64 *)(r1 + 24);"
+ "if r6 == 0 goto +1;"
+ "r6 /= 0;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_map_in_map.c b/tools/testing/selftests/bpf/progs/verifier_map_in_map.c
index 16b761e510f0..d3be69a9a755 100644
--- a/tools/testing/selftests/bpf/progs/verifier_map_in_map.c
+++ b/tools/testing/selftests/bpf/progs/verifier_map_in_map.c
@@ -18,6 +18,20 @@ struct {
});
} map_in_map SEC(".maps");
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
+ __uint(max_entries, 1);
+ __type(key, int);
+ __type(value, int);
+ __array(values, struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(map_flags, BPF_F_INNER_MAP);
+ __uint(max_entries, 8);
+ __type(key, int);
+ __type(value, long);
+ });
+} map_in_map_dyn SEC(".maps");
+
SEC("socket")
__description("map in map access")
__success __success_unpriv __retval(0)
@@ -45,6 +59,32 @@ l0_%=: r0 = 0; \
: __clobber_all);
}
+SEC("socket")
+__description("map in map dynamic inner array lookup is nullable")
+__failure __msg("invalid mem access 'map_value_or_null'")
+__naked void map_in_map_dynamic_inner_array_lookup_is_nullable(void)
+{
+ asm volatile (" \
+ r1 = 0; \
+ *(u32*)(r10 - 4) = r1; \
+ r2 = r10; \
+ r2 += -4; \
+ r1 = %[map_in_map_dyn] ll; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == 0 goto l0_%=; \
+ *(u32*)(r10 - 8) = 4; \
+ r2 = r10; \
+ r2 += -8; \
+ r1 = r0; \
+ call %[bpf_map_lookup_elem]; \
+ r0 = *(u64 *)(r0 + 0); \
+l0_%=: exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_in_map_dyn)
+ : __clobber_all);
+}
+
SEC("xdp")
__description("map in map state pruning")
__success __msg("processed 15 insns")
@@ -114,7 +154,8 @@ l0_%=: r0 = 0; \
SEC("socket")
__description("forgot null checking on the inner map pointer")
-__failure __msg("R1 type=map_value_or_null expected=map_ptr")
+__failure __msg("R1 type=map_ptr_or_null expected=map_ptr")
+__msg("map_ptr_or_null, but this argument accepts map_ptr")
__failure_unpriv
__naked void on_the_inner_map_pointer(void)
{
diff --git a/tools/testing/selftests/bpf/progs/verifier_map_lookup_refine.c b/tools/testing/selftests/bpf/progs/verifier_map_lookup_refine.c
new file mode 100644
index 000000000000..c01abf54923d
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_map_lookup_refine.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+#include "bpf_kfuncs.h"
+
+char _license[] SEC("license") = "GPL";
+
+struct inner_map {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, int);
+ __type(value, int);
+} inner_map SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
+ __uint(max_entries, 1);
+ __type(key, int);
+ __array(values, struct inner_map);
+} outer_map SEC(".maps") = {
+ .values = { [0] = &inner_map },
+};
+
+SEC("?tc")
+__failure __msg("type=map_ptr_or_null expected=fp")
+int mapofmaps_value_as_kfunc_mem_buf(struct __sk_buff *skb)
+{
+ struct bpf_dynptr dptr;
+ __u32 key = 0;
+ void *inner;
+ char *p;
+
+ inner = bpf_map_lookup_elem(&outer_map, &key);
+ /* intentionally NOT NULL-checked: type is map_ptr_or_null */
+
+ bpf_dynptr_from_skb(skb, 0, &dptr);
+ /* arg3 is mem+size */
+ p = bpf_dynptr_slice(&dptr, 0, inner, 4);
+ if (p)
+ return p[0];
+ return 0;
+}
+
+SEC("?tc")
+__failure __msg("type=map_ptr_or_null expected=fp")
+int mapofmaps_value_as_helper_mem_buf(struct __sk_buff *skb)
+{
+ __u32 key = 0;
+ void *inner;
+
+ inner = bpf_map_lookup_elem(&outer_map, &key);
+ /* intentionally NOT NULL-checked: type is map_ptr_or_null */
+
+ /* arg1 is mem+size */
+ return bpf_csum_diff(inner, 4, NULL, 0, 0) + skb->len;
+}
+
+SEC("?tc")
+__failure __msg("type=map_ptr_or_null expected=fp")
+int mapofmaps_value_as_helper_fixed_mem(struct __sk_buff *skb)
+{
+ char th[sizeof(struct tcphdr)] = {};
+ __u32 key = 0;
+ void *inner;
+
+ inner = bpf_map_lookup_elem(&outer_map, &key);
+ /* intentionally NOT NULL-checked: type is map_ptr_or_null */
+
+ /* arg1 is fixed-sized mem */
+ return bpf_tcp_raw_check_syncookie_ipv4(inner, (void *)th);
+}
diff --git a/tools/testing/selftests/bpf/progs/verifier_map_ptr.c b/tools/testing/selftests/bpf/progs/verifier_map_ptr.c
index e2767d27d8aa..e0a65835c861 100644
--- a/tools/testing/selftests/bpf/progs/verifier_map_ptr.c
+++ b/tools/testing/selftests/bpf/progs/verifier_map_ptr.c
@@ -70,13 +70,17 @@ __naked void bpf_map_ptr_write_rejected(void)
: __clobber_all);
}
-/* The first element of struct bpf_map is a SHA256 hash of 32 bytes, accessing
- * into this array is valid. The opts field is now at offset 33.
+/*
+ * struct bpf_map starts with the SHA256 hash sha[32] at offset 0 (a readable
+ * byte array), followed by the ops pointer at offset 32 and the inner_map_meta
+ * pointer at offset 40. Reading a u32 at offset 41 reaches into the middle of
+ * the inner_map_meta pointer, i.e. a partial pointer access, which is
+ * rejected.
*/
SEC("socket")
__description("bpf_map_ptr: read non-existent field rejected")
__failure
-__msg("cannot access ptr member ops with moff 32 in struct bpf_map with off 33 size 4")
+__msg("cannot access ptr member inner_map_meta with moff 40 in struct bpf_map with off 41 size 4")
__failure_unpriv
__msg_unpriv("access is allowed only to CAP_PERFMON and CAP_SYS_ADMIN")
__flag(BPF_F_ANY_ALIGNMENT)
@@ -85,7 +89,32 @@ __naked void read_non_existent_field_rejected(void)
asm volatile (" \
r6 = 0; \
r1 = %[map_array_48b] ll; \
- r6 = *(u32*)(r1 + 33); \
+ r6 = *(u32*)(r1 + 41); \
+ r0 = 1; \
+ exit; \
+" :
+ : __imm_addr(map_array_48b)
+ : __clobber_all);
+}
+
+/*
+ * The sha byte array spans offsets 0..31 (mend 32). Reading a u32 at offset
+ * 30 starts inside sha but extends past its end, which the verifier rejects
+ * as an out-of-bounds scalar access.
+ */
+SEC("socket")
+__description("bpf_map_ptr: read beyond sha field rejected")
+__failure
+__msg("access beyond the end of member sha (mend:32) in struct bpf_map with off 30 size 4")
+__failure_unpriv
+__msg_unpriv("access is allowed only to CAP_PERFMON and CAP_SYS_ADMIN")
+__flag(BPF_F_ANY_ALIGNMENT)
+__naked void read_beyond_sha_field_rejected(void)
+{
+ asm volatile (" \
+ r6 = 0; \
+ r1 = %[map_array_48b] ll; \
+ r6 = *(u32*)(r1 + 30); \
r0 = 1; \
exit; \
" :
@@ -103,7 +132,7 @@ __naked void ptr_read_ops_field_accepted(void)
asm volatile (" \
r6 = 0; \
r1 = %[map_array_48b] ll; \
- r6 = *(u64*)(r1 + 0); \
+ r6 = *(u64*)(r1 + 32); \
r0 = 1; \
exit; \
" :
diff --git a/tools/testing/selftests/bpf/progs/verifier_may_goto_1.c b/tools/testing/selftests/bpf/progs/verifier_may_goto_1.c
index 6d1edaef9213..db7e30da234f 100644
--- a/tools/testing/selftests/bpf/progs/verifier_may_goto_1.c
+++ b/tools/testing/selftests/bpf/progs/verifier_may_goto_1.c
@@ -11,6 +11,8 @@ __description("may_goto 0")
__arch_x86_64
__arch_s390x
__arch_arm64
+__arch_riscv64
+__arch_loongarch
__xlated("0: r0 = 1")
__xlated("1: exit")
__success
@@ -31,6 +33,8 @@ __description("batch 2 of may_goto 0")
__arch_x86_64
__arch_s390x
__arch_arm64
+__arch_riscv64
+__arch_loongarch
__xlated("0: r0 = 1")
__xlated("1: exit")
__success
@@ -53,6 +57,8 @@ __description("may_goto batch with offsets 2/1/0")
__arch_x86_64
__arch_s390x
__arch_arm64
+__arch_riscv64
+__arch_loongarch
__xlated("0: r0 = 1")
__xlated("1: exit")
__success
@@ -79,15 +85,17 @@ __description("may_goto batch with offsets 2/0")
__arch_x86_64
__arch_s390x
__arch_arm64
+__arch_riscv64
+__arch_loongarch
__xlated("0: *(u64 *)(r10 -16) = 65535")
__xlated("1: *(u64 *)(r10 -8) = 0")
-__xlated("2: r11 = *(u64 *)(r10 -16)")
-__xlated("3: if r11 == 0x0 goto pc+6")
-__xlated("4: r11 -= 1")
-__xlated("5: if r11 != 0x0 goto pc+2")
-__xlated("6: r11 = -16")
+__xlated("2: r12 = *(u64 *)(r10 -16)")
+__xlated("3: if r12 == 0x0 goto pc+6")
+__xlated("4: r12 -= 1")
+__xlated("5: if r12 != 0x0 goto pc+2")
+__xlated("6: r12 = -16")
__xlated("7: call unknown")
-__xlated("8: *(u64 *)(r10 -16) = r11")
+__xlated("8: *(u64 *)(r10 -16) = r12")
__xlated("9: r0 = 1")
__xlated("10: r0 = 2")
__xlated("11: exit")
@@ -106,4 +114,63 @@ __naked void may_goto_batch_2(void)
: __clobber_all);
}
+/*
+ * Use bpf_get_prandom_u32() to prevent DCE from removing the checks.
+ * retval: 0=all ok, 1-6=R0-R5 clobbered.
+ */
+SEC("syscall")
+__description("timed may_goto preserves R0-R5")
+__arch_x86_64
+__arch_s390x
+__arch_arm64
+__arch_riscv64
+__arch_loongarch
+__success
+__retval(0)
+__naked void timed_may_goto_preserves_regs(void)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "r6 = r0;"
+ "r0 = 0x1111;"
+ "r0 += r6;"
+ "r1 = 0x2222;"
+ "r1 += r6;"
+ "r2 = 0x3333;"
+ "r2 += r6;"
+ "r3 = 0x4444;"
+ "r3 += r6;"
+ "r4 = 0x5555;"
+ "r4 += r6;"
+ "r5 = 0x6666;"
+ "r5 += r6;"
+ ".8byte %[may_goto];"
+ ".8byte %[loop];"
+ "r0 -= r6;"
+ "r1 -= r6;"
+ "r2 -= r6;"
+ "r3 -= r6;"
+ "r4 -= r6;"
+ "r5 -= r6;"
+ "if r0 != 0x1111 goto 1f;"
+ "if r1 != 0x2222 goto 2f;"
+ "if r2 != 0x3333 goto 3f;"
+ "if r3 != 0x4444 goto 4f;"
+ "if r4 != 0x5555 goto 5f;"
+ "if r5 != 0x6666 goto 6f;"
+ "r0 = 0;"
+ "exit;"
+ "1: r0 = 1; exit;"
+ "2: r0 = 2; exit;"
+ "3: r0 = 3; exit;"
+ "4: r0 = 4; exit;"
+ "5: r0 = 5; exit;"
+ "6: r0 = 6; exit;"
+ :
+ : __imm(bpf_get_prandom_u32),
+ __imm_insn(may_goto, BPF_RAW_INSN(BPF_JMP | BPF_JCOND, 0, 0, 1, 0)),
+ __imm_insn(loop, BPF_RAW_INSN(BPF_JMP | BPF_JA, 0, 0, -2, 0))
+ : __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_mem_size_reg.c b/tools/testing/selftests/bpf/progs/verifier_mem_size_reg.c
new file mode 100644
index 000000000000..7e24706a764e
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_mem_size_reg.c
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+#include "bpf_kfuncs.h"
+
+char _license[] SEC("license") = "GPL";
+
+/*
+ * The __szk size of a kfunc memory/size pair must be marked precise even when
+ * the nullable buffer is passed as NULL.
+ */
+SEC("?tc")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r4 stack= before")
+int dynptr_slice_null_buf_size_precise(struct __sk_buff *skb)
+{
+ struct bpf_dynptr dptr;
+ char *p;
+
+ bpf_dynptr_from_skb(skb, 0, &dptr);
+
+ p = bpf_dynptr_slice(&dptr, 0, NULL, 8);
+ if (p)
+ return p[0];
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/verifier_meta_access.c b/tools/testing/selftests/bpf/progs/verifier_meta_access.c
index d81722fb5f19..62235f032ffe 100644
--- a/tools/testing/selftests/bpf/progs/verifier_meta_access.c
+++ b/tools/testing/selftests/bpf/progs/verifier_meta_access.c
@@ -27,7 +27,7 @@ l0_%=: r0 = 0; \
SEC("xdp")
__description("meta access, test2")
-__failure __msg("invalid access to packet, off=-8")
+__failure __msg("R0 min value is negative")
__naked void meta_access_test2(void)
{
asm volatile (" \
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_percpu_addr.c b/tools/testing/selftests/bpf/progs/verifier_percpu_addr.c
new file mode 100644
index 000000000000..967f4e6e3a49
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_percpu_addr.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+#if defined(__TARGET_ARCH_x86)
+
+int percpu_data SEC(".percpu");
+
+/*
+ * An ld_imm64 of a per-CPU map value is followed by a mov_percpu_addr that
+ * reuses the same register, so check that the add resolves into the register
+ * the address was loaded into, for every register.
+ */
+SEC("raw_tp")
+__description("per-CPU address resolution")
+__success
+__arch_x86_64
+__jited(" movabsq $0x{{.*}}, %rax")
+__jited(" addq %gs:{{.*}}, %rax")
+__jited(" movabsq $0x{{.*}}, %rdi")
+__jited(" addq %gs:{{.*}}, %rdi")
+__jited(" movabsq $0x{{.*}}, %rsi")
+__jited(" addq %gs:{{.*}}, %rsi")
+__jited(" movabsq $0x{{.*}}, %rdx")
+__jited(" addq %gs:{{.*}}, %rdx")
+__jited(" movabsq $0x{{.*}}, %rcx")
+__jited(" addq %gs:{{.*}}, %rcx")
+__jited(" movabsq $0x{{.*}}, %r8")
+__jited(" addq %gs:{{.*}}, %r8")
+__jited(" movabsq $0x{{.*}}, %rbx")
+__jited(" addq %gs:{{.*}}, %rbx")
+__jited(" movabsq $0x{{.*}}, %r13")
+__jited(" addq %gs:{{.*}}, %r13")
+__jited(" movabsq $0x{{.*}}, %r14")
+__jited(" addq %gs:{{.*}}, %r14")
+__jited(" movabsq $0x{{.*}}, %r15")
+__jited(" addq %gs:{{.*}}, %r15")
+__naked void percpu_addr(void)
+{
+ asm volatile (" \
+ r0 = %[percpu_data] ll; \
+ r1 = %[percpu_data] ll; \
+ r2 = %[percpu_data] ll; \
+ r3 = %[percpu_data] ll; \
+ r4 = %[percpu_data] ll; \
+ r5 = %[percpu_data] ll; \
+ r6 = %[percpu_data] ll; \
+ r7 = %[percpu_data] ll; \
+ r8 = %[percpu_data] ll; \
+ r9 = %[percpu_data] ll; \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm_addr(percpu_data)
+ : __clobber_all);
+}
+
+#else
+
+SEC("raw_tp")
+__description("percpu addr dummy")
+__success
+int dummy_test(void)
+{
+ return 0;
+}
+
+#endif
+
+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 1fe090cd6744..f4459561bf39 100644
--- a/tools/testing/selftests/bpf/progs/verifier_precision.c
+++ b/tools/testing/selftests/bpf/progs/verifier_precision.c
@@ -2,8 +2,17 @@
/* 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);
+ __uint(max_entries, 1);
+ __type(key, __u32);
+ __type(value, __u64);
+} precision_map SEC(".maps");
SEC("?raw_tp")
__success __log_level(2)
@@ -68,8 +77,8 @@ __naked int bpf_end_to_be(void)
#if (defined(__TARGET_ARCH_arm64) || defined(__TARGET_ARCH_x86) || \
(defined(__TARGET_ARCH_riscv) && __riscv_xlen == 64) || \
- defined(__TARGET_ARCH_arm) || defined(__TARGET_ARCH_s390)) && \
- __clang_major__ >= 18
+ defined(__TARGET_ARCH_arm) || defined(__TARGET_ARCH_s390) || \
+ defined(__TARGET_ARCH_loongarch)) && __clang_major__ >= 18
SEC("?raw_tp")
__success __log_level(2)
@@ -301,4 +310,436 @@ __naked int bpf_neg_5(void)
::: __clobber_all);
}
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r2 stack= before 4: (bf) r3 = r10")
+__msg("mark_precise: frame0: regs=r2 stack= before 3: (db) r2 = atomic64_fetch_add((u64 *)(r10 -8), r2)")
+__msg("mark_precise: frame0: regs= stack=-8 before 2: (b7) r2 = 0")
+__msg("mark_precise: frame0: regs= stack=-8 before 1: (7b) *(u64 *)(r10 -8) = r1")
+__msg("mark_precise: frame0: regs=r1 stack= before 0: (b7) r1 = 8")
+__naked int bpf_atomic_fetch_add_precision(void)
+{
+ asm volatile (
+ "r1 = 8;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r2 = 0;"
+ ".8byte %[fetch_add_insn];" /* r2 = atomic_fetch_add(*(u64 *)(r10 - 8), r2) */
+ "r3 = r10;"
+ "r3 += r2;" /* mark_precise */
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(fetch_add_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_ADD | BPF_FETCH, BPF_REG_10, BPF_REG_2, -8))
+ : __clobber_all);
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r2 stack= before 4: (bf) r3 = r10")
+__msg("mark_precise: frame0: regs=r2 stack= before 3: (db) r2 = atomic64_xchg((u64 *)(r10 -8), r2)")
+__msg("mark_precise: frame0: regs= stack=-8 before 2: (b7) r2 = 0")
+__msg("mark_precise: frame0: regs= stack=-8 before 1: (7b) *(u64 *)(r10 -8) = r1")
+__msg("mark_precise: frame0: regs=r1 stack= before 0: (b7) r1 = 8")
+__naked int bpf_atomic_xchg_precision(void)
+{
+ asm volatile (
+ "r1 = 8;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r2 = 0;"
+ ".8byte %[xchg_insn];" /* r2 = atomic_xchg(*(u64 *)(r10 - 8), r2) */
+ "r3 = r10;"
+ "r3 += r2;" /* mark_precise */
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(xchg_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_XCHG, BPF_REG_10, BPF_REG_2, -8))
+ : __clobber_all);
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r2 stack= before 4: (bf) r3 = r10")
+__msg("mark_precise: frame0: regs=r2 stack= before 3: (db) r2 = atomic64_fetch_or((u64 *)(r10 -8), r2)")
+__msg("mark_precise: frame0: regs= stack=-8 before 2: (b7) r2 = 0")
+__msg("mark_precise: frame0: regs= stack=-8 before 1: (7b) *(u64 *)(r10 -8) = r1")
+__msg("mark_precise: frame0: regs=r1 stack= before 0: (b7) r1 = 8")
+__naked int bpf_atomic_fetch_or_precision(void)
+{
+ asm volatile (
+ "r1 = 8;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r2 = 0;"
+ ".8byte %[fetch_or_insn];" /* r2 = atomic_fetch_or(*(u64 *)(r10 - 8), r2) */
+ "r3 = r10;"
+ "r3 += r2;" /* mark_precise */
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(fetch_or_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_OR | BPF_FETCH, BPF_REG_10, BPF_REG_2, -8))
+ : __clobber_all);
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r2 stack= before 4: (bf) r3 = r10")
+__msg("mark_precise: frame0: regs=r2 stack= before 3: (db) r2 = atomic64_fetch_and((u64 *)(r10 -8), r2)")
+__msg("mark_precise: frame0: regs= stack=-8 before 2: (b7) r2 = 0")
+__msg("mark_precise: frame0: regs= stack=-8 before 1: (7b) *(u64 *)(r10 -8) = r1")
+__msg("mark_precise: frame0: regs=r1 stack= before 0: (b7) r1 = 8")
+__naked int bpf_atomic_fetch_and_precision(void)
+{
+ asm volatile (
+ "r1 = 8;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r2 = 0;"
+ ".8byte %[fetch_and_insn];" /* r2 = atomic_fetch_and(*(u64 *)(r10 - 8), r2) */
+ "r3 = r10;"
+ "r3 += r2;" /* mark_precise */
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(fetch_and_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_AND | BPF_FETCH, BPF_REG_10, BPF_REG_2, -8))
+ : __clobber_all);
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r2 stack= before 4: (bf) r3 = r10")
+__msg("mark_precise: frame0: regs=r2 stack= before 3: (db) r2 = atomic64_fetch_xor((u64 *)(r10 -8), r2)")
+__msg("mark_precise: frame0: regs= stack=-8 before 2: (b7) r2 = 0")
+__msg("mark_precise: frame0: regs= stack=-8 before 1: (7b) *(u64 *)(r10 -8) = r1")
+__msg("mark_precise: frame0: regs=r1 stack= before 0: (b7) r1 = 8")
+__naked int bpf_atomic_fetch_xor_precision(void)
+{
+ asm volatile (
+ "r1 = 8;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r2 = 0;"
+ ".8byte %[fetch_xor_insn];" /* r2 = atomic_fetch_xor(*(u64 *)(r10 - 8), r2) */
+ "r3 = r10;"
+ "r3 += r2;" /* mark_precise */
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(fetch_xor_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_XOR | BPF_FETCH, BPF_REG_10, BPF_REG_2, -8))
+ : __clobber_all);
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r0 stack= before 5: (bf) r3 = r10")
+__msg("mark_precise: frame0: regs=r0 stack= before 4: (db) r0 = atomic64_cmpxchg((u64 *)(r10 -8), r0, r2)")
+__msg("mark_precise: frame0: regs= stack=-8 before 3: (b7) r2 = 0")
+__msg("mark_precise: frame0: regs= stack=-8 before 2: (b7) r0 = 0")
+__msg("mark_precise: frame0: regs= stack=-8 before 1: (7b) *(u64 *)(r10 -8) = r1")
+__msg("mark_precise: frame0: regs=r1 stack= before 0: (b7) r1 = 8")
+__naked int bpf_atomic_cmpxchg_precision(void)
+{
+ asm volatile (
+ "r1 = 8;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r0 = 0;"
+ "r2 = 0;"
+ ".8byte %[cmpxchg_insn];" /* r0 = atomic_cmpxchg(*(u64 *)(r10 - 8), r0, r2) */
+ "r3 = r10;"
+ "r3 += r0;" /* mark_precise */
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(cmpxchg_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_CMPXCHG, BPF_REG_10, BPF_REG_2, -8))
+ : __clobber_all);
+}
+
+/* Regression test for dual precision: Both the fetched value (r2) and
+ * a reread of the same stack slot (r3) are tracked for precision. After
+ * the atomic operation, the stack slot is STACK_MISC. Thus, the ldx at
+ * insn 4 does NOT set INSN_F_STACK_ACCESS. Precision for the stack slot
+ * propagates solely through the atomic fetch's load side (insn 3).
+ */
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r2,r3 stack= before 4: (79) r3 = *(u64 *)(r10 -8)")
+__msg("mark_precise: frame0: regs=r2 stack= before 3: (db) r2 = atomic64_fetch_add((u64 *)(r10 -8), r2)")
+__msg("mark_precise: frame0: regs= stack=-8 before 2: (b7) r2 = 0")
+__msg("mark_precise: frame0: regs= stack=-8 before 1: (7b) *(u64 *)(r10 -8) = r1")
+__msg("mark_precise: frame0: regs=r1 stack= before 0: (b7) r1 = 8")
+__naked int bpf_atomic_fetch_add_dual_precision(void)
+{
+ asm volatile (
+ "r1 = 8;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r2 = 0;"
+ ".8byte %[fetch_add_insn];" /* r2 = atomic_fetch_add(*(u64 *)(r10 - 8), r2) */
+ "r3 = *(u64 *)(r10 - 8);"
+ "r4 = r2;"
+ "r4 += r3;"
+ "r4 &= 7;"
+ "r5 = r10;"
+ "r5 += r4;" /* mark_precise */
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(fetch_add_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_ADD | BPF_FETCH, BPF_REG_10, BPF_REG_2, -8))
+ : __clobber_all);
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r0,r3 stack= before 5: (79) r3 = *(u64 *)(r10 -8)")
+__msg("mark_precise: frame0: regs=r0 stack= before 4: (db) r0 = atomic64_cmpxchg((u64 *)(r10 -8), r0, r2)")
+__msg("mark_precise: frame0: regs= stack=-8 before 3: (b7) r2 = 0")
+__msg("mark_precise: frame0: regs= stack=-8 before 2: (b7) r0 = 8")
+__msg("mark_precise: frame0: regs= stack=-8 before 1: (7b) *(u64 *)(r10 -8) = r1")
+__msg("mark_precise: frame0: regs=r1 stack= before 0: (b7) r1 = 8")
+__naked int bpf_atomic_cmpxchg_dual_precision(void)
+{
+ asm volatile (
+ "r1 = 8;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r0 = 8;"
+ "r2 = 0;"
+ ".8byte %[cmpxchg_insn];" /* r0 = atomic_cmpxchg(*(u64 *)(r10 - 8), r0, r2) */
+ "r3 = *(u64 *)(r10 - 8);"
+ "r4 = r0;"
+ "r4 += r3;"
+ "r4 &= 7;"
+ "r5 = r10;"
+ "r5 += r4;" /* mark_precise */
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(cmpxchg_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_CMPXCHG, BPF_REG_10, BPF_REG_2, -8))
+ : __clobber_all);
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r1 stack= before 10: (57) r1 &= 7")
+__msg("mark_precise: frame0: regs=r1 stack= before 9: (db) r1 = atomic64_fetch_add((u64 *)(r0 +0), r1)")
+__not_msg("falling back to forcing all scalars precise")
+__naked int bpf_atomic_fetch_add_map_precision(void)
+{
+ asm volatile (
+ "r1 = 0;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r2 = r10;"
+ "r2 += -8;"
+ "r1 = %[precision_map] ll;"
+ "call %[bpf_map_lookup_elem];"
+ "if r0 == 0 goto 1f;"
+ "r1 = 0;"
+ ".8byte %[fetch_add_insn];" /* r1 = atomic_fetch_add(*(u64 *)(r0 + 0), r1) */
+ "r1 &= 7;"
+ "r2 = r10;"
+ "r2 += r1;" /* mark_precise */
+ "1: r0 = 0;"
+ "exit;"
+ :
+ : __imm_addr(precision_map),
+ __imm(bpf_map_lookup_elem),
+ __imm_insn(fetch_add_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_ADD | BPF_FETCH, BPF_REG_0, BPF_REG_1, 0))
+ : __clobber_all);
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r0 stack= before 12: (57) r0 &= 7")
+__msg("mark_precise: frame0: regs=r0 stack= before 11: (db) r0 = atomic64_cmpxchg((u64 *)(r6 +0), r0, r1)")
+__not_msg("falling back to forcing all scalars precise")
+__naked int bpf_atomic_cmpxchg_map_precision(void)
+{
+ asm volatile (
+ "r1 = 0;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r2 = r10;"
+ "r2 += -8;"
+ "r1 = %[precision_map] ll;"
+ "call %[bpf_map_lookup_elem];"
+ "if r0 == 0 goto 1f;"
+ "r6 = r0;"
+ "r0 = 0;"
+ "r1 = 0;"
+ ".8byte %[cmpxchg_insn];" /* r0 = atomic_cmpxchg(*(u64 *)(r6 + 0), r0, r1) */
+ "r0 &= 7;"
+ "r2 = r10;"
+ "r2 += r0;" /* mark_precise */
+ "1: r0 = 0;"
+ "exit;"
+ :
+ : __imm_addr(precision_map),
+ __imm(bpf_map_lookup_elem),
+ __imm_insn(cmpxchg_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_CMPXCHG, BPF_REG_6, BPF_REG_1, 0))
+ : __clobber_all);
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r1 stack= before 10: (57) r1 &= 7")
+__msg("mark_precise: frame0: regs=r1 stack= before 9: (c3) r1 = atomic_fetch_add((u32 *)(r0 +0), r1)")
+__not_msg("falling back to forcing all scalars precise")
+__naked int bpf_atomic_fetch_add_32bit_precision(void)
+{
+ asm volatile (
+ "r1 = 0;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r2 = r10;"
+ "r2 += -8;"
+ "r1 = %[precision_map] ll;"
+ "call %[bpf_map_lookup_elem];"
+ "if r0 == 0 goto 1f;"
+ "r1 = 0;"
+ ".8byte %[fetch_add_insn];" /* r1 = atomic_fetch_add(*(u32 *)(r0 + 0), r1) */
+ "r1 &= 7;"
+ "r2 = r10;"
+ "r2 += r1;" /* mark_precise */
+ "1: r0 = 0;"
+ "exit;"
+ :
+ : __imm_addr(precision_map),
+ __imm(bpf_map_lookup_elem),
+ __imm_insn(fetch_add_insn,
+ BPF_ATOMIC_OP(BPF_W, BPF_ADD | BPF_FETCH, BPF_REG_0, BPF_REG_1, 0))
+ : __clobber_all);
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r0 stack= before 12: (57) r0 &= 7")
+__msg("mark_precise: frame0: regs=r0 stack= before 11: (c3) r0 = atomic_cmpxchg((u32 *)(r6 +0), r0, r1)")
+__not_msg("falling back to forcing all scalars precise")
+__naked int bpf_atomic_cmpxchg_32bit_precision(void)
+{
+ asm volatile (
+ "r1 = 0;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r2 = r10;"
+ "r2 += -8;"
+ "r1 = %[precision_map] ll;"
+ "call %[bpf_map_lookup_elem];"
+ "if r0 == 0 goto 1f;"
+ "r6 = r0;"
+ "r0 = 0;"
+ "r1 = 0;"
+ ".8byte %[cmpxchg_insn];" /* r0 = atomic_cmpxchg(*(u32 *)(r6 + 0), r0, r1) */
+ "r0 &= 7;"
+ "r2 = r10;"
+ "r2 += r0;" /* mark_precise */
+ "1: r0 = 0;"
+ "exit;"
+ :
+ : __imm_addr(precision_map),
+ __imm(bpf_map_lookup_elem),
+ __imm_insn(cmpxchg_insn,
+ BPF_ATOMIC_OP(BPF_W, BPF_CMPXCHG, BPF_REG_6, BPF_REG_1, 0))
+ : __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_private_stack.c b/tools/testing/selftests/bpf/progs/verifier_private_stack.c
index 1ecd34ebde19..ea0a7e73331d 100644
--- a/tools/testing/selftests/bpf/progs/verifier_private_stack.c
+++ b/tools/testing/selftests/bpf/progs/verifier_private_stack.c
@@ -86,6 +86,9 @@ __naked static void cumulative_stack_depth_subprog(void)
SEC("kprobe")
__description("Private stack, subtree > MAX_BPF_STACK")
__success
+__log_level(4) __msg("stack depth max 512")
+__msg("subprog 0 (private_stack_nested_1) main {{.*}} stack 512")
+__msg("subprog 1 (cumulative_stack_depth_subprog) static {{.*}} stack 32")
__arch_x86_64
/* private stack fp for the main prog */
__jited(" movabsq $0x{{.*}}, %r9")
@@ -93,6 +96,7 @@ __jited(" addq %gs:{{.*}}, %r9")
__jited(" movl $0x2a, %edi")
__jited(" movq %rdi, -0x200(%r9)")
__jited(" pushq %r9")
+__jited("...")
__jited(" callq 0x{{.*}}")
__jited(" popq %r9")
__jited(" xorl %eax, %eax")
@@ -152,11 +156,13 @@ __jited(" endbr64")
__jited(" movabsq $0x{{.*}}, %r9")
__jited(" addq %gs:{{.*}}, %r9")
__jited(" pushq %r9")
+__jited("...")
__jited(" callq")
__jited(" popq %r9")
__jited(" movl $0x2a, %edi")
__jited(" movq %rdi, -0x200(%r9)")
__jited(" pushq %r9")
+__jited("...")
__jited(" callq")
__jited(" popq %r9")
__arch_arm64
@@ -170,12 +176,12 @@ __jited(" mrs x10, TPIDR_EL{{[0-1]}}")
__jited(" add x27, x27, x10")
__jited(" add x25, x27, {{.*}}")
__jited(" bl 0x{{.*}}")
-__jited(" add x7, x0, #0x0")
+__jited(" mov x8, x0")
__jited(" mov x0, #0x2a")
__jited(" str x0, [x27]")
__jited(" bl 0x{{.*}}")
-__jited(" add x7, x0, #0x0")
-__jited(" mov x7, #0x0")
+__jited(" mov x8, x0")
+__jited(" mov x8, #0x0")
__jited(" ldp x25, x27, [sp], {{.*}}")
__naked void private_stack_callback(void)
{
@@ -198,6 +204,7 @@ __description("Private stack, exception in main prog")
__success __retval(0)
__arch_x86_64
__jited(" pushq %r9")
+__jited("...")
__jited(" callq")
__jited(" popq %r9")
__arch_arm64
@@ -220,7 +227,7 @@ __jited(" mov x0, #0x2a")
__jited(" str x0, [x27]")
__jited(" mov x0, #0x0")
__jited(" bl 0x{{.*}}")
-__jited(" add x7, x0, #0x0")
+__jited(" mov x8, x0")
__jited(" ldp x27, x28, [sp], #0x10")
int private_stack_exception_main_prog(void)
{
@@ -245,6 +252,7 @@ __success __retval(0)
__arch_x86_64
__jited(" movq %rdi, -0x200(%r9)")
__jited(" pushq %r9")
+__jited("...")
__jited(" callq")
__jited(" popq %r9")
__arch_arm64
@@ -258,7 +266,7 @@ __jited(" add x25, x27, {{.*}}")
__jited(" mov x0, #0x2a")
__jited(" str x0, [x27]")
__jited(" bl 0x{{.*}}")
-__jited(" add x7, x0, #0x0")
+__jited(" mov x8, x0")
__jited(" ldp x27, x28, [sp], #0x10")
int private_stack_exception_sub_prog(void)
{
@@ -324,6 +332,12 @@ int private_stack_async_callback_1(void)
SEC("fentry/bpf_fentry_test9")
__description("Private stack, async callback, potential nesting")
__success __retval(0)
+__load_if_JITed()
+__log_level(4) __msg("stack depth max 272")
+__msg("subprog 0 (private_stack_async_callback_2) main {{.*}} stack 8")
+__msg("subprog 1 (timer_cb1) static {{.*}} stack 0")
+__msg("subprog 2 (subprog1) static {{.*}} stack 256")
+__msg("subprog 3 (subprog2) static {{.*}} stack 0")
__arch_x86_64
__jited(" subq $0x100, %rsp")
__arch_arm64
@@ -344,6 +358,21 @@ int private_stack_async_callback_2(void)
return 0;
}
+SEC("fentry/bpf_fentry_test9")
+__description("private stack, max stack depth is private stack")
+__success
+__log_level(4) __msg("stack depth max 256")
+__msg("subprog 0 (private_stack_max_depth) main {{.*}} stack 8")
+__msg("subprog 1 (subprog1) static insns_self {{[0-9]+}} insns_total {{[0-9]+}} stack 256")
+__msg("subprog 2 (subprog2) static insns_self {{[0-9]+}} insns_total {{[0-9]+}} stack 0")
+int private_stack_max_depth(void)
+{
+ int x = 0;
+
+ subprog1(&x);
+ return 0;
+}
+
#else
SEC("kprobe")
diff --git a/tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c b/tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c
new file mode 100644
index 000000000000..12cf24db46a0
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+SEC("iter/bpf_map_elem")
+__description("PTR_TO_BUF: reject negative const offset")
+__failure
+__msg("invalid negative rdwr buffer offset")
+__naked void ptr_to_buf_reject_negative_const_offset(void)
+{
+ asm volatile ("r0 = 0; \
+ r2 = *(u64 *)(r1 + %[value_off]); \
+ if r2 == 0 goto l0_%=; \
+ r2 += -8; \
+ r0 = *(u64 *)(r2 + 0); \
+l0_%=: \
+ exit; \
+ "
+ :
+ : __imm_const(value_off,
+ offsetof(struct bpf_iter__bpf_map_elem, value))
+ : __clobber_all);
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c b/tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c
index 14a0172e2141..4055a6443bc2 100644
--- a/tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c
+++ b/tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c
@@ -47,4 +47,20 @@ l0_%=: /* shift the buffer pointer to a variable location */\
: __clobber_all);
}
+SEC("raw_tracepoint.w")
+__description("raw_tracepoint_writable: reject negative const offset")
+__failure
+__msg("invalid negative tracepoint buffer offset")
+__naked void tracepoint_writable_reject_negative_const_offset(void)
+{
+ asm volatile (" \
+ r6 = *(u64 *)(r1 + 0); \
+ r6 += -8; \
+ r0 = *(u64 *)(r6 + 0); \
+ exit; \
+" :
+ :
+ : __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_ref_tracking.c b/tools/testing/selftests/bpf/progs/verifier_ref_tracking.c
index 910365201f68..199ad18f8eb5 100644
--- a/tools/testing/selftests/bpf/progs/verifier_ref_tracking.c
+++ b/tools/testing/selftests/bpf/progs/verifier_ref_tracking.c
@@ -263,7 +263,7 @@ l0_%=: r0 = 0; \
SEC("lsm.s/bpf")
__description("reference tracking: release user key reference without check")
-__failure __msg("Possibly NULL pointer passed to trusted arg0")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
__naked void user_key_reference_without_check(void)
{
asm volatile (" \
@@ -282,7 +282,7 @@ __naked void user_key_reference_without_check(void)
SEC("lsm.s/bpf")
__description("reference tracking: release system key reference without check")
-__failure __msg("Possibly NULL pointer passed to trusted arg0")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
__naked void system_key_reference_without_check(void)
{
asm volatile (" \
@@ -300,7 +300,7 @@ __naked void system_key_reference_without_check(void)
SEC("lsm.s/bpf")
__description("reference tracking: release with NULL key pointer")
-__failure __msg("Possibly NULL pointer passed to trusted arg0")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
__naked void release_with_null_key_pointer(void)
{
asm volatile (" \
@@ -1288,7 +1288,7 @@ l1_%=: r1 = r6; \
SEC("tc")
__description("reference tracking: bpf_sk_release(listen_sk)")
-__failure __msg("R1 must be referenced when passed to release function")
+__failure __msg("release helper bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
__naked void bpf_sk_release_listen_sk(void)
{
asm volatile (
diff --git a/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c b/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c
index c0ce690ddb68..256547048cc4 100644
--- a/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c
+++ b/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c
@@ -4,6 +4,13 @@
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(max_entries, 1);
+ __type(key, long long);
+ __type(value, long long);
+} map_hash_8b SEC(".maps");
+
/* Check that precision marks propagate through scalar IDs.
* Registers r{0,1,2} have the same scalar ID.
* Range information is propagated for scalars sharing same ID.
@@ -40,6 +47,9 @@ __naked void linked_regs_bpf_k(void)
*/
"r3 = r10;"
"r3 += r0;"
+ /* Mark r1 and r2 as alive. */
+ "r1 = r1;"
+ "r2 = r2;"
"r0 = 0;"
"exit;"
:
@@ -73,6 +83,9 @@ __naked void linked_regs_bpf_x_src(void)
*/
"r4 = r10;"
"r4 += r0;"
+ /* Mark r1 and r2 as alive. */
+ "r1 = r1;"
+ "r2 = r2;"
"r0 = 0;"
"exit;"
:
@@ -106,6 +119,10 @@ __naked void linked_regs_bpf_x_dst(void)
*/
"r4 = r10;"
"r4 += r3;"
+ /* Mark r1 and r2 as alive. */
+ "r0 = r0;"
+ "r1 = r1;"
+ "r2 = r2;"
"r0 = 0;"
"exit;"
:
@@ -143,6 +160,9 @@ __naked void linked_regs_broken_link(void)
*/
"r3 = r10;"
"r3 += r0;"
+ /* Mark r1 and r2 as alive. */
+ "r1 = r1;"
+ "r2 = r2;"
"r0 = 0;"
"exit;"
:
@@ -156,16 +176,16 @@ __naked void linked_regs_broken_link(void)
*/
SEC("socket")
__success __log_level(2)
-__msg("12: (0f) r2 += r1")
+__msg("17: (0f) r2 += r1")
/* Current state */
-__msg("frame2: last_idx 12 first_idx 11 subseq_idx -1 ")
-__msg("frame2: regs=r1 stack= before 11: (bf) r2 = r10")
+__msg("frame2: last_idx 17 first_idx 14 subseq_idx -1 ")
+__msg("frame2: regs=r1 stack= before 16: (bf) r2 = r10")
__msg("frame2: parent state regs=r1 stack=")
__msg("frame1: parent state regs= stack=")
__msg("frame0: parent state regs= stack=")
/* Parent state */
-__msg("frame2: last_idx 10 first_idx 10 subseq_idx 11 ")
-__msg("frame2: regs=r1 stack= before 10: (25) if r1 > 0x7 goto pc+0")
+__msg("frame2: last_idx 13 first_idx 13 subseq_idx 14 ")
+__msg("frame2: regs=r1 stack= before 13: (25) if r1 > 0x7 goto pc+0")
__msg("frame2: parent state regs=r1 stack=")
/* frame1.r{6,7} are marked because mark_precise_scalar_ids()
* looks for all registers with frame2.r1.id in the current state
@@ -173,20 +193,20 @@ __msg("frame2: parent state regs=r1 stack=")
__msg("frame1: parent state regs=r6,r7 stack=")
__msg("frame0: parent state regs=r6 stack=")
/* Parent state */
-__msg("frame2: last_idx 8 first_idx 8 subseq_idx 10")
-__msg("frame2: regs=r1 stack= before 8: (85) call pc+1")
+__msg("frame2: last_idx 9 first_idx 9 subseq_idx 13")
+__msg("frame2: regs=r1 stack= before 9: (85) call pc+3")
/* frame1.r1 is marked because of backtracking of call instruction */
__msg("frame1: parent state regs=r1,r6,r7 stack=")
__msg("frame0: parent state regs=r6 stack=")
/* Parent state */
-__msg("frame1: last_idx 7 first_idx 6 subseq_idx 8")
-__msg("frame1: regs=r1,r6,r7 stack= before 7: (bf) r7 = r1")
-__msg("frame1: regs=r1,r6 stack= before 6: (bf) r6 = r1")
+__msg("frame1: last_idx 8 first_idx 7 subseq_idx 9")
+__msg("frame1: regs=r1,r6,r7 stack= before 8: (bf) r7 = r1")
+__msg("frame1: regs=r1,r6 stack= before 7: (bf) r6 = r1")
__msg("frame1: parent state regs=r1 stack=")
__msg("frame0: parent state regs=r6 stack=")
/* Parent state */
-__msg("frame1: last_idx 4 first_idx 4 subseq_idx 6")
-__msg("frame1: regs=r1 stack= before 4: (85) call pc+1")
+__msg("frame1: last_idx 4 first_idx 4 subseq_idx 7")
+__msg("frame1: regs=r1 stack= before 4: (85) call pc+2")
__msg("frame0: parent state regs=r1,r6 stack=")
/* Parent state */
__msg("frame0: last_idx 3 first_idx 1 subseq_idx 4")
@@ -204,6 +224,7 @@ __naked void precision_many_frames(void)
"r1 = r0;"
"r6 = r0;"
"call precision_many_frames__foo;"
+ "r6 = r6;" /* mark r6 as live */
"exit;"
:
: __imm(bpf_ktime_get_ns)
@@ -220,6 +241,8 @@ void precision_many_frames__foo(void)
"r6 = r1;"
"r7 = r1;"
"call precision_many_frames__bar;"
+ "r6 = r6;" /* mark r6 as live */
+ "r7 = r7;" /* mark r7 as live */
"exit"
::: __clobber_all);
}
@@ -229,6 +252,8 @@ void precision_many_frames__bar(void)
{
asm volatile (
"if r1 > 7 goto +0;"
+ "r6 = 0;" /* mark r6 as live */
+ "r7 = 0;" /* mark r7 as live */
/* force r1 to be precise, this eventually marks:
* - bar frame r1
* - foo frame r{1,6,7}
@@ -246,13 +271,13 @@ void precision_many_frames__bar(void)
*/
SEC("socket")
__success __log_level(2)
-__msg("11: (0f) r2 += r1")
+__msg("12: (0f) r2 += r1")
/* foo frame */
-__msg("frame1: regs=r1 stack= before 10: (bf) r2 = r10")
-__msg("frame1: regs=r1 stack= before 9: (25) if r1 > 0x7 goto pc+0")
-__msg("frame1: regs=r1 stack=-8,-16 before 8: (7b) *(u64 *)(r10 -16) = r1")
-__msg("frame1: regs=r1 stack=-8 before 7: (7b) *(u64 *)(r10 -8) = r1")
-__msg("frame1: regs=r1 stack= before 4: (85) call pc+2")
+__msg("frame1: regs=r1 stack= before 11: (bf) r2 = r10")
+__msg("frame1: regs=r1 stack= before 10: (25) if r1 > 0x7 goto pc+0")
+__msg("frame1: regs=r1 stack=-8,-16 before 9: (7b) *(u64 *)(r10 -16) = r1")
+__msg("frame1: regs=r1 stack=-8 before 8: (7b) *(u64 *)(r10 -8) = r1")
+__msg("frame1: regs=r1 stack= before 4: (85) call pc+3")
/* main frame */
__msg("frame0: regs=r1 stack=-8 before 3: (7b) *(u64 *)(r10 -8) = r1")
__msg("frame0: regs=r1 stack= before 2: (bf) r1 = r0")
@@ -268,6 +293,7 @@ __naked void precision_stack(void)
"r1 = r0;"
"*(u64*)(r10 - 8) = r1;"
"call precision_stack__foo;"
+ "r0 = *(u64*)(r10 - 8);"
"r0 = 0;"
"exit;"
:
@@ -291,6 +317,8 @@ void precision_stack__foo(void)
*/
"r2 = r10;"
"r2 += r1;"
+ "r0 = *(u64*)(r10 - 8);"
+ "r0 = *(u64*)(r10 - 16);"
"exit"
::: __clobber_all);
}
@@ -340,6 +368,8 @@ __naked void precision_two_ids(void)
"r3 += r7;"
/* force r9 to be precise, this also marks r8 */
"r3 += r9;"
+ "r6 = r6;" /* mark r6 as live */
+ "r8 = r8;" /* mark r8 as live */
"exit;"
:
: __imm(bpf_ktime_get_ns)
@@ -349,31 +379,37 @@ __naked void precision_two_ids(void)
SEC("socket")
__success __log_level(2)
__flag(BPF_F_TEST_STATE_FREQ)
-/* check that r0 and r6 have different IDs after 'if',
- * collect_linked_regs() can't tie more than 6 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("8: (25) if r0 > 0x7 goto pc+0 ; R0=scalar(id=1")
-__msg("9: (bf) r6 = r6 ; R6=scalar(id=2")
-/* check that r{0-5} are marked precise after 'if' */
-__msg("frame0: regs=r0 stack= before 8: (25) if r0 > 0x7 goto pc+0")
-__msg("frame0: parent state regs=r0,r1,r2,r3,r4,r5 stack=:")
+__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 r5 > 0x7 goto pc+0")
+__msg("frame0: parent state regs=r0,r1,r2,r3,r4 stack=:")
__naked void linked_regs_too_many_regs(void)
{
asm volatile (
/* r0 = random number up to 0xff */
"call %[bpf_ktime_get_ns];"
"r0 &= 0xff;"
- /* tie r{0-6} IDs */
+ /* tie r{0-5} IDs */
"r1 = r0;"
"r2 = r0;"
"r3 = r0;"
"r4 = r0;"
"r5 = r0;"
- "r6 = r0;"
- /* propagate range for r{0-6} */
- "if r0 > 7 goto +0;"
- /* make r6 appear in the log */
- "r6 = r6;"
+ /* 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;"
+ "r3 = r3;"
+ "r4 = r4;"
+ /* make r5 appear in the log */
+ "r5 = r5;"
/* force r0 to be precise,
* this would cause r{0-4} to be precise because of shared IDs
*/
@@ -517,7 +553,7 @@ __naked void check_ids_in_regsafe_2(void)
"*(u64*)(r10 - 8) = r1;"
/* r9 = pointer to stack */
"r9 = r10;"
- "r9 += -8;"
+ "r9 += -16;"
/* r8 = ktime_get_ns() */
"call %[bpf_ktime_get_ns];"
"r8 = r0;"
@@ -538,6 +574,8 @@ __naked void check_ids_in_regsafe_2(void)
"if r7 > 4 goto l2_%=;"
/* Access memory at r9[r6] */
"r9 += r6;"
+ "r9 += r7;"
+ "r9 += r8;"
"r0 = *(u8*)(r9 + 0);"
"l2_%=:"
"r0 = 0;"
@@ -564,10 +602,10 @@ __naked void check_ids_in_regsafe_2(void)
*/
SEC("socket")
__success __log_level(2)
-__msg("11: (1d) if r3 == r4 goto pc+0")
+__msg("14: (1d) if r3 == r4 goto pc+0")
__msg("frame 0: propagating r3,r4")
-__msg("11: safe")
-__msg("processed 15 insns")
+__msg("14: safe")
+__msg("processed 18 insns")
__flag(BPF_F_TEST_STATE_FREQ)
__naked void no_scalar_id_for_const(void)
{
@@ -577,6 +615,7 @@ __naked void no_scalar_id_for_const(void)
"if r0 > 7 goto l0_%=;"
/* possibly generate same scalar ids for r3 and r4 */
"r1 = 0;"
+ "r1 ^= r1;" /* prevent bpf_prune_dead_branches from folding the branch */
"r1 = r1;"
"r3 = r1;"
"r4 = r1;"
@@ -584,7 +623,9 @@ __naked void no_scalar_id_for_const(void)
"l0_%=:"
/* possibly generate different scalar ids for r3 and r4 */
"r1 = 0;"
+ "r1 ^= r1;"
"r2 = 0;"
+ "r2 ^= r2;"
"r3 = r1;"
"r4 = r2;"
"l1_%=:"
@@ -600,10 +641,10 @@ __naked void no_scalar_id_for_const(void)
/* Same as no_scalar_id_for_const() but for 32-bit values */
SEC("socket")
__success __log_level(2)
-__msg("11: (1e) if w3 == w4 goto pc+0")
+__msg("14: (1e) if w3 == w4 goto pc+0")
__msg("frame 0: propagating r3,r4")
-__msg("11: safe")
-__msg("processed 15 insns")
+__msg("14: safe")
+__msg("processed 18 insns")
__flag(BPF_F_TEST_STATE_FREQ)
__naked void no_scalar_id_for_const32(void)
{
@@ -613,6 +654,7 @@ __naked void no_scalar_id_for_const32(void)
"if r0 > 7 goto l0_%=;"
/* possibly generate same scalar ids for r3 and r4 */
"w1 = 0;"
+ "w1 ^= w1;" /* prevent bpf_prune_dead_branches from folding the branch */
"w1 = w1;"
"w3 = w1;"
"w4 = w1;"
@@ -620,11 +662,13 @@ __naked void no_scalar_id_for_const32(void)
"l0_%=:"
/* possibly generate different scalar ids for r3 and r4 */
"w1 = 0;"
+ "w1 ^= w1;"
"w2 = 0;"
+ "w2 ^= w2;"
"w3 = w1;"
"w4 = w2;"
"l1_%=:"
- /* predictable jump, marks r1 and r2 precise */
+ /* predictable jump, marks r3 and r4 precise */
"if w3 == w4 goto +0;"
"r0 = 0;"
"exit;"
@@ -715,6 +759,51 @@ __naked void ignore_unique_scalar_ids_old(void)
: __clobber_all);
}
+/* Check that two registers with 0 scalar IDs in a verified state can be mapped
+ * to the same scalar ID in current state.
+ */
+SEC("socket")
+__success __log_level(2)
+/* The states should be equivalent on reaching insn 12.
+ */
+__msg("12: safe")
+__msg("processed 17 insns")
+__flag(BPF_F_TEST_STATE_FREQ)
+__naked void two_nil_old_ids_one_cur_id(void)
+{
+ asm volatile (
+ /* Give unique scalar IDs to r{6,7} */
+ "call %[bpf_ktime_get_ns];"
+ "r0 &= 0xff;"
+ "r6 = r0;"
+ "r6 *= 1;"
+ "call %[bpf_ktime_get_ns];"
+ "r0 &= 0xff;"
+ "r7 = r0;"
+ "r7 *= 1;"
+ "r0 = 0;"
+ /* Maybe make r{6,7} IDs identical */
+ "if r6 > r7 goto l0_%=;"
+ "goto l1_%=;"
+"l0_%=:"
+ "r6 = r7;"
+"l1_%=:"
+ /* Mark r{6,7} precise.
+ * Get here in two states:
+ * - first: r6{.id=0}, r7{.id=0} (cached state)
+ * - second: r6{.id=A}, r7{.id=A}
+ * Verifier considers such states equivalent.
+ * Thus "exit;" would be verified only once.
+ */
+ "r2 = r10;"
+ "r2 += r6;"
+ "r2 += r7;"
+ "exit;"
+ :
+ : __imm(bpf_ktime_get_ns)
+ : __clobber_all);
+}
+
/* Check that two different scalar IDs in a verified state can't be
* mapped to the same scalar ID in current state.
*/
@@ -723,9 +812,9 @@ __success __log_level(2)
/* The exit instruction should be reachable from two states,
* use two matches and "processed .. insns" to ensure this.
*/
-__msg("13: (95) exit")
-__msg("13: (95) exit")
-__msg("processed 18 insns")
+__msg("16: (95) exit")
+__msg("16: (95) exit")
+__msg("processed 22 insns")
__flag(BPF_F_TEST_STATE_FREQ)
__naked void two_old_ids_one_cur_id(void)
{
@@ -734,9 +823,11 @@ __naked void two_old_ids_one_cur_id(void)
"call %[bpf_ktime_get_ns];"
"r0 &= 0xff;"
"r6 = r0;"
+ "r8 = r0;"
"call %[bpf_ktime_get_ns];"
"r0 &= 0xff;"
"r7 = r0;"
+ "r9 = r0;"
"r0 = 0;"
/* Maybe make r{6,7} IDs identical */
"if r6 > r7 goto l0_%=;"
@@ -754,6 +845,11 @@ __naked void two_old_ids_one_cur_id(void)
"r2 = r10;"
"r2 += r6;"
"r2 += r7;"
+ /*
+ * keep r8 and r9 live, otherwise r6->id and r7->id
+ * will become singular and reset to zero before if r6 > r7
+ */
+ "r9 += r8;"
"exit;"
:
: __imm(bpf_ktime_get_ns)
@@ -827,4 +923,53 @@ __naked void linked_regs_and_subreg_def(void)
: __clobber_all);
}
+/*
+ * A scalar is spilled to the stack and then filled twice: once via a
+ * sign-extending load (BPF_MEMSX) into r4 and once via a zero-extending
+ * load (BPF_MEM) into r5. coerce_reg_to_size_sx() gives r4 a different
+ * value than the spilled/zero-extended siblings, so r4 must not keep the
+ * shared scalar id. Otherwise the later 'if r5 == 0x80000000' refines r4
+ * through sync_linked_regs() to a known 0x80000000, while at runtime r4
+ * is the sign-extended 0xffffffff80000000. The test turns that discrepancy
+ * into an out-of-bounds map value access (r4 >> 63 is believed 0 but is 1
+ * at runtime), which must be rejected.
+ */
+SEC("socket")
+__failure __msg("R0 max value is outside of the allowed memory range")
+__naked void ldsx_fill_scalar_id_not_shared(void)
+{
+ asm volatile (" \
+ 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_%=; \
+ /* r7 = unknown u32, keep only bit 31 */ \
+ r7 = *(u32*)(r0 + 0); \
+ r2 = 0x80000000 ll; \
+ r7 &= r2; \
+ /* link r6 and r7 via a fresh scalar id */ \
+ r6 = r7; \
+ /* spill r7 (u32) to the stack */ \
+ *(u32*)(r10 - 8) = r7; \
+ /* sign-extending fill: must drop the id */ \
+ r4 = *(s32*)(r10 - 8); \
+ /* zero-extending fill: keeps the id */ \
+ r5 = *(u32*)(r10 - 8); \
+ /* r5 becomes known 0x80000000 on fall-through */\
+ if r5 != r2 goto l0_%=; \
+ /* verifier believes r4 == 0 here, runtime is 1 */\
+ r4 >>= 63; \
+ r0 += r4; \
+ r0 = *(u8*)(r0 + 7); \
+l0_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_8b)
+ : __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_sdiv.c b/tools/testing/selftests/bpf/progs/verifier_sdiv.c
index 148d2299e5b4..95f3239ce228 100644
--- a/tools/testing/selftests/bpf/progs/verifier_sdiv.c
+++ b/tools/testing/selftests/bpf/progs/verifier_sdiv.c
@@ -778,10 +778,10 @@ __arch_x86_64
__xlated("0: r2 = 0x8000000000000000")
__xlated("2: r3 = -1")
__xlated("3: r4 = r2")
-__xlated("4: r11 = r3")
-__xlated("5: r11 += 1")
-__xlated("6: if r11 > 0x1 goto pc+4")
-__xlated("7: if r11 == 0x0 goto pc+1")
+__xlated("4: r12 = r3")
+__xlated("5: r12 += 1")
+__xlated("6: if r12 > 0x1 goto pc+4")
+__xlated("7: if r12 == 0x0 goto pc+1")
__xlated("8: r2 = 0")
__xlated("9: r2 = -r2")
__xlated("10: goto pc+1")
@@ -812,10 +812,10 @@ __success __retval(-5)
__arch_x86_64
__xlated("0: r2 = 5")
__xlated("1: r3 = -1")
-__xlated("2: r11 = r3")
-__xlated("3: r11 += 1")
-__xlated("4: if r11 > 0x1 goto pc+4")
-__xlated("5: if r11 == 0x0 goto pc+1")
+__xlated("2: r12 = r3")
+__xlated("3: r12 += 1")
+__xlated("4: if r12 > 0x1 goto pc+4")
+__xlated("5: if r12 == 0x0 goto pc+1")
__xlated("6: r2 = 0")
__xlated("7: r2 = -r2")
__xlated("8: goto pc+1")
@@ -890,10 +890,10 @@ __arch_x86_64
__xlated("0: w2 = -2147483648")
__xlated("1: w3 = -1")
__xlated("2: w4 = w2")
-__xlated("3: r11 = r3")
-__xlated("4: w11 += 1")
-__xlated("5: if w11 > 0x1 goto pc+4")
-__xlated("6: if w11 == 0x0 goto pc+1")
+__xlated("3: r12 = r3")
+__xlated("4: w12 += 1")
+__xlated("5: if w12 > 0x1 goto pc+4")
+__xlated("6: if w12 == 0x0 goto pc+1")
__xlated("7: w2 = 0")
__xlated("8: w2 = -w2")
__xlated("9: goto pc+1")
@@ -925,10 +925,10 @@ __arch_x86_64
__xlated("0: w2 = -5")
__xlated("1: w3 = -1")
__xlated("2: w4 = w2")
-__xlated("3: r11 = r3")
-__xlated("4: w11 += 1")
-__xlated("5: if w11 > 0x1 goto pc+4")
-__xlated("6: if w11 == 0x0 goto pc+1")
+__xlated("3: r12 = r3")
+__xlated("4: w12 += 1")
+__xlated("5: if w12 > 0x1 goto pc+4")
+__xlated("6: if w12 == 0x0 goto pc+1")
__xlated("7: w2 = 0")
__xlated("8: w2 = -w2")
__xlated("9: goto pc+1")
@@ -1004,10 +1004,10 @@ __arch_x86_64
__xlated("0: r2 = 0x8000000000000000")
__xlated("2: r3 = -1")
__xlated("3: r4 = r2")
-__xlated("4: r11 = r3")
-__xlated("5: r11 += 1")
-__xlated("6: if r11 > 0x1 goto pc+3")
-__xlated("7: if r11 == 0x1 goto pc+3")
+__xlated("4: r12 = r3")
+__xlated("5: r12 += 1")
+__xlated("6: if r12 > 0x1 goto pc+3")
+__xlated("7: if r12 == 0x1 goto pc+3")
__xlated("8: w2 = 0")
__xlated("9: goto pc+1")
__xlated("10: r2 s%= r3")
@@ -1034,10 +1034,10 @@ __arch_x86_64
__xlated("0: r2 = 5")
__xlated("1: r3 = -1")
__xlated("2: r4 = r2")
-__xlated("3: r11 = r3")
-__xlated("4: r11 += 1")
-__xlated("5: if r11 > 0x1 goto pc+3")
-__xlated("6: if r11 == 0x1 goto pc+3")
+__xlated("3: r12 = r3")
+__xlated("4: r12 += 1")
+__xlated("5: if r12 > 0x1 goto pc+3")
+__xlated("6: if r12 == 0x1 goto pc+3")
__xlated("7: w2 = 0")
__xlated("8: goto pc+1")
__xlated("9: r2 s%= r3")
@@ -1108,10 +1108,10 @@ __arch_x86_64
__xlated("0: w2 = -2147483648")
__xlated("1: w3 = -1")
__xlated("2: w4 = w2")
-__xlated("3: r11 = r3")
-__xlated("4: w11 += 1")
-__xlated("5: if w11 > 0x1 goto pc+3")
-__xlated("6: if w11 == 0x1 goto pc+4")
+__xlated("3: r12 = r3")
+__xlated("4: w12 += 1")
+__xlated("5: if w12 > 0x1 goto pc+3")
+__xlated("6: if w12 == 0x1 goto pc+4")
__xlated("7: w2 = 0")
__xlated("8: goto pc+1")
__xlated("9: w2 s%= w3")
@@ -1140,10 +1140,10 @@ __arch_x86_64
__xlated("0: w2 = -5")
__xlated("1: w3 = -1")
__xlated("2: w4 = w2")
-__xlated("3: r11 = r3")
-__xlated("4: w11 += 1")
-__xlated("5: if w11 > 0x1 goto pc+3")
-__xlated("6: if w11 == 0x1 goto pc+4")
+__xlated("3: r12 = r3")
+__xlated("4: w12 += 1")
+__xlated("5: if w12 > 0x1 goto pc+3")
+__xlated("6: if w12 == 0x1 goto pc+4")
__xlated("7: w2 = 0")
__xlated("8: goto pc+1")
__xlated("9: w2 s%= w3")
@@ -1209,6 +1209,64 @@ __naked void smod32_ri_divisor_neg_1(void)
: __clobber_all);
}
+SEC("socket")
+__description("SDIV32, INT_MIN divided by 2, imm")
+__success __success_unpriv __retval(-1073741824)
+__naked void sdiv32_int_min_div_2_imm(void)
+{
+ asm volatile (" \
+ w0 = %[int_min]; \
+ w0 s/= 2; \
+ exit; \
+" :
+ : __imm_const(int_min, INT_MIN)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SDIV32, INT_MIN divided by 2, reg")
+__success __success_unpriv __retval(-1073741824)
+__naked void sdiv32_int_min_div_2_reg(void)
+{
+ asm volatile (" \
+ w0 = %[int_min]; \
+ w1 = 2; \
+ w0 s/= w1; \
+ exit; \
+" :
+ : __imm_const(int_min, INT_MIN)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD32, INT_MIN modulo 2, imm")
+__success __success_unpriv __retval(0)
+__naked void smod32_int_min_mod_2_imm(void)
+{
+ asm volatile (" \
+ w0 = %[int_min]; \
+ w0 s%%= 2; \
+ exit; \
+" :
+ : __imm_const(int_min, INT_MIN)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("SMOD32, INT_MIN modulo -2, imm")
+__success __success_unpriv __retval(0)
+__naked void smod32_int_min_mod_neg2_imm(void)
+{
+ asm volatile (" \
+ w0 = %[int_min]; \
+ w0 s%%= -2; \
+ exit; \
+" :
+ : __imm_const(int_min, INT_MIN)
+ : __clobber_all);
+}
+
+
#else
SEC("socket")
diff --git a/tools/testing/selftests/bpf/progs/verifier_set_retval.c b/tools/testing/selftests/bpf/progs/verifier_set_retval.c
new file mode 100644
index 000000000000..1415cd15cede
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_set_retval.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+
+SEC("lsm_cgroup/socket_create")
+__description("lsm_cgroup bpf_set_retval success")
+__success
+int BPF_PROG(lsm_cgroup_set_retval_zero_valid, int family, int type, int protocol, int kern)
+{
+ bpf_set_retval(0);
+ return 0;
+}
+
+SEC("lsm_cgroup/socket_create")
+__description("lsm_cgroup bpf_set_retval valid errno")
+__success
+int BPF_PROG(lsm_cgroup_set_retval_negative_valid, int family, int type, int protocol, int kern)
+{
+ bpf_set_retval(-12);
+ return 0;
+}
+
+SEC("lsm_cgroup/socket_create")
+__description("lsm_cgroup bpf_set_retval invalid negative value")
+__failure __msg("should have been in [-4095, 0]")
+int BPF_PROG(lsm_cgroup_set_retval_negative_invalid, int family, int type, int protocol, int kern)
+{
+ bpf_set_retval(-4096);
+ return 0;
+}
+
+SEC("lsm_cgroup/socket_create")
+__description("lsm_cgroup bpf_set_retval invalid positive value")
+__failure __msg("should have been in [-4095, 0]")
+int BPF_PROG(lsm_cgroup_set_retval_positive_invalid, int family, int type, int protocol, int kern)
+{
+ bpf_set_retval(1);
+ return 0;
+}
+
+SEC("cgroup/dev")
+__description("cgroup_device bpf_set_retval success")
+__success
+int cgroup_dev_set_retval_0(struct bpf_cgroup_dev_ctx *ctx)
+{
+ bpf_set_retval(0);
+ return 1;
+}
+
+SEC("cgroup/dev")
+__description("cgroup_device bpf_set_retval valid errno")
+__success
+int cgroup_dev_set_retval_neg_maxerrno(struct bpf_cgroup_dev_ctx *ctx)
+{
+ bpf_set_retval(-4095);
+ return 1;
+}
+
+SEC("cgroup/dev")
+__description("cgroup_device bpf_set_retval invalid positive value")
+__failure __msg("should have been in [-4095, 0]")
+int cgroup_dev_set_retval_1(struct bpf_cgroup_dev_ctx *ctx)
+{
+ bpf_set_retval(1);
+ return 1;
+}
+
+SEC("cgroup/dev")
+__description("cgroup_device bpf_set_retval invalid negative value")
+__failure __msg("should have been in [-4095, 0]")
+int cgroup_dev_set_retval_neg_4096(struct bpf_cgroup_dev_ctx *ctx)
+{
+ bpf_set_retval(-4096);
+ return 1;
+}
+
+SEC("cgroup/dev")
+__description("bpf_set_retval bounds check survives state pruning")
+__failure __msg("should have been in [-4095, 0]")
+__naked int cgroup_dev_set_retval_pruning_bypass(struct bpf_cgroup_dev_ctx *ctx)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "if r0 != 0 goto 1f;"
+ "r0 = r0;"
+ "r0 = r0;"
+ "r0 = r0;"
+ "r0 = r0;"
+ "goto 2f;"
+ "1:"
+ "call %[bpf_get_prandom_u32];"
+ "2:"
+ "r1 = r0;"
+ "call %[bpf_set_retval];"
+ "r0 = 1;"
+ "exit;"
+ :
+ : __imm(bpf_get_prandom_u32),
+ __imm(bpf_set_retval)
+ : __clobber_common
+ );
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_sock.c b/tools/testing/selftests/bpf/progs/verifier_sock.c
index a2132c72d3b8..bf9f6fb6582c 100644
--- a/tools/testing/selftests/bpf/progs/verifier_sock.c
+++ b/tools/testing/selftests/bpf/progs/verifier_sock.c
@@ -88,6 +88,44 @@ l0_%=: r0 = *(u32*)(r1 + %[bpf_sock_family]); \
: __clobber_all);
}
+SEC("socket")
+__description("skb->sk: sk->rx_queue_mapping [no sign extension]")
+__success __success_unpriv __retval(0)
+__naked void sk_rx_queue_mapping_no_sign_ext(void)
+{
+ asm volatile (" \
+ r1 = *(u64*)(r1 + %[__sk_buff_sk]); \
+ if r1 != 0 goto l0_%=; \
+ r0 = 0xdead; \
+ exit; \
+l0_%=: r0 = *(u32*)(r1 + %[bpf_sock_rx_queue_mapping]); \
+ r0 >>= 32; \
+ exit; \
+" :
+ : __imm_const(__sk_buff_sk, offsetof(struct __sk_buff, sk)),
+ __imm_const(bpf_sock_rx_queue_mapping, offsetof(struct bpf_sock, rx_queue_mapping))
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("skb->sk: sk->rx_queue_mapping [narrow load mask]")
+__success __success_unpriv __retval(0)
+__naked void sk_rx_queue_mapping_narrow_load_mask(void)
+{
+ asm volatile (" \
+ r1 = *(u64*)(r1 + %[__sk_buff_sk]); \
+ if r1 != 0 goto l0_%=; \
+ r0 = 0xdead; \
+ exit; \
+l0_%=: r0 = *(u16*)(r1 + %[bpf_sock_rx_queue_mapping]); \
+ r0 >>= 16; \
+ exit; \
+" :
+ : __imm_const(__sk_buff_sk, offsetof(struct __sk_buff, sk)),
+ __imm_const(bpf_sock_rx_queue_mapping, offsetof(struct bpf_sock, rx_queue_mapping))
+ : __clobber_all);
+}
+
SEC("cgroup/skb")
__description("skb->sk: sk->type [fullsock field]")
__failure __msg("invalid sock_common access")
@@ -603,7 +641,7 @@ l2_%=: r0 = *(u32*)(r0 + %[bpf_tcp_sock_snd_cwnd]); \
SEC("tc")
__description("bpf_sk_release(skb->sk)")
-__failure __msg("R1 must be referenced when passed to release function")
+__failure __msg("release helper bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
__naked void bpf_sk_release_skb_sk(void)
{
asm volatile (" \
@@ -620,7 +658,7 @@ l0_%=: r0 = 0; \
SEC("tc")
__description("bpf_sk_release(bpf_sk_fullsock(skb->sk))")
-__failure __msg("R1 must be referenced when passed to release function")
+__failure __msg("release helper bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
__naked void bpf_sk_fullsock_skb_sk(void)
{
asm volatile (" \
@@ -644,7 +682,7 @@ l1_%=: r1 = r0; \
SEC("tc")
__description("bpf_sk_release(bpf_tcp_sock(skb->sk))")
-__failure __msg("R1 must be referenced when passed to release function")
+__failure __msg("release helper bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
__naked void bpf_tcp_sock_skb_sk(void)
{
asm volatile (" \
@@ -1120,8 +1158,11 @@ int tail_call(struct __sk_buff *sk)
static __noinline
int static_tail_call(struct __sk_buff *sk)
{
+ int ret = 0;
+
bpf_tail_call_static(sk, &jmp_table, 0);
- return 0;
+ barrier_var(ret);
+ return ret;
}
/* Tail calls in sub-programs invalidate packet pointers. */
@@ -1144,10 +1185,12 @@ __failure __msg("invalid mem access")
int invalidate_pkt_pointers_by_static_tail_call(struct __sk_buff *sk)
{
int *p = (void *)(long)sk->data;
+ int ret;
if ((void *)(p + 1) > (void *)(long)sk->data_end)
return TCX_DROP;
- static_tail_call(sk);
+ ret = static_tail_call(sk);
+ __sink(ret);
*p = 42; /* this is unsafe */
return TCX_PASS;
}
diff --git a/tools/testing/selftests/bpf/progs/verifier_sockmap_mutate.c b/tools/testing/selftests/bpf/progs/verifier_sockmap_mutate.c
index fe4b123187b8..20332a731d4e 100644
--- a/tools/testing/selftests/bpf/progs/verifier_sockmap_mutate.c
+++ b/tools/testing/selftests/bpf/progs/verifier_sockmap_mutate.c
@@ -74,7 +74,7 @@ static __always_inline void test_sockmap_lookup_and_mutate(void)
}
SEC("action")
-__success
+__failure __msg("cannot update sockmap in this context")
int test_sched_act(struct __sk_buff *skb)
{
test_sockmap_mutate(skb->sk);
@@ -82,7 +82,7 @@ int test_sched_act(struct __sk_buff *skb)
}
SEC("classifier")
-__success
+__failure __msg("cannot update sockmap in this context")
int test_sched_cls(struct __sk_buff *skb)
{
test_sockmap_mutate(skb->sk);
@@ -90,7 +90,7 @@ int test_sched_cls(struct __sk_buff *skb)
}
SEC("flow_dissector")
-__success
+__failure __msg("cannot update sockmap in this context")
int test_flow_dissector_delete(struct __sk_buff *skb __always_unused)
{
test_sockmap_delete();
@@ -98,7 +98,7 @@ int test_flow_dissector_delete(struct __sk_buff *skb __always_unused)
}
SEC("flow_dissector")
-__failure __msg("program of this type cannot use helper bpf_sk_release")
+__failure __msg("cannot update sockmap in this context")
int test_flow_dissector_update(struct __sk_buff *skb __always_unused)
{
test_sockmap_lookup_and_update(); /* no access to skb->sk */
@@ -146,7 +146,7 @@ int test_sk_reuseport(struct sk_reuseport_md *ctx)
}
SEC("socket")
-__success
+__failure __msg("cannot update sockmap in this context")
int test_socket_filter(struct __sk_buff *skb)
{
test_sockmap_mutate(skb->sk);
@@ -179,7 +179,7 @@ int test_sockops_update_dedicated(struct bpf_sock_ops *ctx)
}
SEC("xdp")
-__success
+__failure __msg("cannot update sockmap in this context")
int test_xdp(struct xdp_md *ctx __always_unused)
{
test_sockmap_lookup_and_mutate();
diff --git a/tools/testing/selftests/bpf/progs/verifier_spill_fill.c b/tools/testing/selftests/bpf/progs/verifier_spill_fill.c
index 7a13dbd794b2..39a1766dae3f 100644
--- a/tools/testing/selftests/bpf/progs/verifier_spill_fill.c
+++ b/tools/testing/selftests/bpf/progs/verifier_spill_fill.c
@@ -634,6 +634,32 @@ __naked void partial_stack_load_preserves_partial_zeros(void)
: __clobber_common);
}
+SEC("raw_tp")
+__log_level(2)
+__success
+__msg("mark_precise: frame0: regs= stack=-8")
+__msg("R2=0")
+__naked void stack_load_preserves_mixed_zero_and_zero_spill(void)
+{
+ asm volatile (
+ /* fp-8 has scalar const-zero spill bytes and STACK_ZERO bytes. */
+ ".8byte %[fp4_st_zero];" /* LLVM-18+: *(u32 *)(r10 -4) = 0; */
+ "r0 = 0;"
+ "*(u32 *)(r10 -8) = r0;"
+
+ "r1 = %[single_byte_buf];"
+ "r2 = *(u64 *)(r10 -8);"
+ "r1 += r2;"
+ "*(u8 *)(r1 + 0) = r2;" /* this should be fine */
+
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_ptr(single_byte_buf),
+ __imm_insn(fp4_st_zero, BPF_ST_MEM(BPF_W, BPF_REG_FP, -4, 0))
+ : __clobber_common);
+}
+
char two_byte_buf[2] SEC(".data.two_byte_buf");
SEC("raw_tp")
@@ -650,13 +676,13 @@ __msg("mark_precise: frame0: last_idx 9 first_idx 7 subseq_idx -1")
__msg("mark_precise: frame0: regs=r2 stack= before 8: (79) r2 = *(u64 *)(r10 -8)")
__msg("mark_precise: frame0: regs= stack=-8 before 7: (bf) r1 = r6")
/* note, fp-8 is precise, fp-16 is not yet precise, we'll get there */
-__msg("mark_precise: frame0: parent state regs= stack=-8: R0=1 R1=ctx() R6=map_value(map=.data.two_byte_,ks=4,vs=2) R10=fp0 fp-8=P1 fp-16=1")
+__msg("mark_precise: frame0: parent state regs= stack=-8: R6=map_value(map=.data.two_byte_,ks=4,vs=2) R10=fp0 fp-8=P1 fp-16=1")
__msg("mark_precise: frame0: last_idx 6 first_idx 3 subseq_idx 7")
__msg("mark_precise: frame0: regs= stack=-8 before 6: (05) goto pc+0")
__msg("mark_precise: frame0: regs= stack=-8 before 5: (7b) *(u64 *)(r10 -16) = r0")
__msg("mark_precise: frame0: regs= stack=-8 before 4: (b7) r0 = 1")
__msg("mark_precise: frame0: regs= stack=-8 before 3: (7a) *(u64 *)(r10 -8) = 1")
-__msg("10: R1=map_value(map=.data.two_byte_,ks=4,vs=2,off=1) R2=1")
+__msg("10: R1=map_value(map=.data.two_byte_,ks=4,vs=2,imm=1) R2=1")
/* validate load from fp-16, which was initialized using BPF_STX_MEM */
__msg("12: (79) r2 = *(u64 *)(r10 -16) ; R2=1 R10=fp0 fp-16=1")
__msg("13: (0f) r1 += r2")
@@ -668,12 +694,12 @@ __msg("mark_precise: frame0: regs= stack=-16 before 9: (0f) r1 += r2")
__msg("mark_precise: frame0: regs= stack=-16 before 8: (79) r2 = *(u64 *)(r10 -8)")
__msg("mark_precise: frame0: regs= stack=-16 before 7: (bf) r1 = r6")
/* now both fp-8 and fp-16 are precise, very good */
-__msg("mark_precise: frame0: parent state regs= stack=-16: R0=1 R1=ctx() R6=map_value(map=.data.two_byte_,ks=4,vs=2) R10=fp0 fp-8=P1 fp-16=P1")
+__msg("mark_precise: frame0: parent state regs= stack=-16: R6=map_value(map=.data.two_byte_,ks=4,vs=2) R10=fp0 fp-8=P1 fp-16=P1")
__msg("mark_precise: frame0: last_idx 6 first_idx 3 subseq_idx 7")
__msg("mark_precise: frame0: regs= stack=-16 before 6: (05) goto pc+0")
__msg("mark_precise: frame0: regs= stack=-16 before 5: (7b) *(u64 *)(r10 -16) = r0")
__msg("mark_precise: frame0: regs=r0 stack= before 4: (b7) r0 = 1")
-__msg("14: R1=map_value(map=.data.two_byte_,ks=4,vs=2,off=1) R2=1")
+__msg("14: R1=map_value(map=.data.two_byte_,ks=4,vs=2,imm=1) R2=1")
__naked void stack_load_preserves_const_precision(void)
{
asm volatile (
@@ -726,13 +752,13 @@ __msg("9: (0f) r1 += r2")
__msg("mark_precise: frame0: last_idx 9 first_idx 7 subseq_idx -1")
__msg("mark_precise: frame0: regs=r2 stack= before 8: (61) r2 = *(u32 *)(r10 -8)")
__msg("mark_precise: frame0: regs= stack=-8 before 7: (bf) r1 = r6")
-__msg("mark_precise: frame0: parent state regs= stack=-8: R0=1 R1=ctx() R6=map_value(map=.data.two_byte_,ks=4,vs=2) R10=fp0 fp-8=????P1 fp-16=????1")
+__msg("mark_precise: frame0: parent state regs= stack=-8: R6=map_value(map=.data.two_byte_,ks=4,vs=2) R10=fp0 fp-8=????P1 fp-16=????1")
__msg("mark_precise: frame0: last_idx 6 first_idx 3 subseq_idx 7")
__msg("mark_precise: frame0: regs= stack=-8 before 6: (05) goto pc+0")
__msg("mark_precise: frame0: regs= stack=-8 before 5: (63) *(u32 *)(r10 -16) = r0")
__msg("mark_precise: frame0: regs= stack=-8 before 4: (b7) r0 = 1")
__msg("mark_precise: frame0: regs= stack=-8 before 3: (62) *(u32 *)(r10 -8) = 1")
-__msg("10: R1=map_value(map=.data.two_byte_,ks=4,vs=2,off=1) R2=1")
+__msg("10: R1=map_value(map=.data.two_byte_,ks=4,vs=2,imm=1) R2=1")
/* validate load from fp-16, which was initialized using BPF_STX_MEM */
__msg("12: (61) r2 = *(u32 *)(r10 -16) ; R2=1 R10=fp0 fp-16=????1")
__msg("13: (0f) r1 += r2")
@@ -743,12 +769,12 @@ __msg("mark_precise: frame0: regs= stack=-16 before 10: (73) *(u8 *)(r1 +0) = r2
__msg("mark_precise: frame0: regs= stack=-16 before 9: (0f) r1 += r2")
__msg("mark_precise: frame0: regs= stack=-16 before 8: (61) r2 = *(u32 *)(r10 -8)")
__msg("mark_precise: frame0: regs= stack=-16 before 7: (bf) r1 = r6")
-__msg("mark_precise: frame0: parent state regs= stack=-16: R0=1 R1=ctx() R6=map_value(map=.data.two_byte_,ks=4,vs=2) R10=fp0 fp-8=????P1 fp-16=????P1")
+__msg("mark_precise: frame0: parent state regs= stack=-16: R6=map_value(map=.data.two_byte_,ks=4,vs=2) R10=fp0 fp-8=????P1 fp-16=????P1")
__msg("mark_precise: frame0: last_idx 6 first_idx 3 subseq_idx 7")
__msg("mark_precise: frame0: regs= stack=-16 before 6: (05) goto pc+0")
__msg("mark_precise: frame0: regs= stack=-16 before 5: (63) *(u32 *)(r10 -16) = r0")
__msg("mark_precise: frame0: regs=r0 stack= before 4: (b7) r0 = 1")
-__msg("14: R1=map_value(map=.data.two_byte_,ks=4,vs=2,off=1) R2=1")
+__msg("14: R1=map_value(map=.data.two_byte_,ks=4,vs=2,imm=1) R2=1")
__naked void stack_load_preserves_const_precision_subreg(void)
{
asm volatile (
@@ -780,6 +806,8 @@ __naked void stack_load_preserves_const_precision_subreg(void)
"r1 += r2;"
"*(u8 *)(r1 + 0) = r2;" /* this should be fine */
+ "r2 = *(u64 *)(r10 -8);" /* keep slots alive */
+ "r2 = *(u64 *)(r10 -16);"
"r0 = 0;"
"exit;"
:
@@ -1279,4 +1307,182 @@ __naked void stack_noperfmon_spill_32bit_onto_64bit_slot(void)
: __clobber_all);
}
+/*
+ * stacksafe(): check if 32-bit scalar spill in old state is considered
+ * equivalent to STACK_MISC in cur state.
+ * 32-bit scalar spill creates slot[0-3] = STACK_MISC, slot[4-7] = STACK_SPILL.
+ * Without 32-bit spill support in stacksafe(), the STACK_SPILL vs STACK_MISC
+ * mismatch at slot[4] causes pruning to fail.
+ */
+SEC("socket")
+__success __log_level(2)
+__msg("8: (79) r1 = *(u64 *)(r10 -8)")
+__msg("8: safe")
+__msg("processed 11 insns")
+__flag(BPF_F_TEST_STATE_FREQ)
+__naked void old_imprecise_scalar32_vs_cur_stack_misc(void)
+{
+ asm volatile(
+ /* get a random value for branching */
+ "call %[bpf_ktime_get_ns];"
+ "if r0 == 0 goto 1f;"
+ /* conjure 32-bit scalar spill at fp-8 */
+ "r0 = 42;"
+ "*(u32*)(r10 - 8) = r0;"
+ "goto 2f;"
+"1:"
+ /* conjure STACK_MISC at fp-8 */
+ "call %[bpf_ktime_get_ns];"
+ "*(u16*)(r10 - 8) = r0;"
+ "*(u16*)(r10 - 6) = r0;"
+"2:"
+ /* read fp-8, should be considered safe on second visit */
+ "r1 = *(u64*)(r10 - 8);"
+ "exit;"
+ :
+ : __imm(bpf_ktime_get_ns)
+ : __clobber_all);
+}
+
+SEC("raw_tp")
+__success
+__naked void var_off_write_over_scalar_spill(void)
+{
+ asm volatile (
+ /* Get an unknown value bounded to {0, 4} */
+ "call %[bpf_ktime_get_ns];"
+ "r6 = r0;"
+ "r6 &= 4;"
+
+ /* Spill a scalar to fp-16 */
+ "r7 = 0xdeadbeef00000000 ll;"
+ "*(u64 *)(r10 - 16) = r7;"
+
+ /*
+ * Variable-offset 4-byte write covering [fp-12, fp-4).
+ * This touches stype[3..0] of the spill slot at fp-16 but
+ * leaves stype[7..4] as STACK_SPILL. check_stack_write_var_off()
+ * must scrub the entire slot when setting spilled_ptr to NOT_INIT,
+ * otherwise a subsequent sub-register fill sees a non-scalar
+ * spilled_ptr and is rejected.
+ */
+ "r8 = r10;"
+ "r8 += r6;"
+ "r8 += -12;"
+ "r9 = 0;"
+ "*(u32 *)(r8 + 0) = r9;"
+
+ /*
+ * 4-byte read from fp-16. Without the fix this fails with
+ * "invalid size of register fill" because is_spilled_reg()
+ * sees STACK_SPILL while spilled_ptr.type == NOT_INIT.
+ */
+ "r0 = *(u32 *)(r10 - 16);"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_ktime_get_ns)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("partial fill from cleaned pointer spill")
+__failure
+__log_level(2)
+__msg("1: (05) goto pc+0")
+__msg("2: (61) r0 = *(u32 *)(r10 -4)")
+__msg("invalid size of register fill")
+__flag(BPF_F_TEST_STATE_FREQ)
+__naked void partial_fill_from_cleaned_pointer_spill(void)
+{
+ /* Spill R1(ctx), then force a checkpoint and half-slot cleanup. */
+ asm volatile ("*(u64 *)(r10 - 8) = r1;"
+ "goto +0;"
+ "r0 = *(u32 *)(r10 - 4);"
+ "exit;"
+ ::: __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
+__naked void spill_fill_ptr_to_tp_buffer(void)
+{
+ asm volatile (
+ "r6 = *(u64*)(r1 + 0);" /* r6 is the writable tracepoint buffer */
+ "*(u64*)(r10 - 8) = r6;"
+ "r7 = *(u64*)(r10 - 8);"
+ "r0 = 0;"
+ "*(u64*)(r7 + 0) = r0;" /* should be able to write through the buffer */
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
+__noinline int spill_fill_dynptr_subprog(struct bpf_dynptr *dptr)
+{
+ long *p;
+
+ asm volatile ("*(u64 *)(r10 - 8) = %[dptr];" /* spill the CONST_PTR_TO_DYNPTR argument */
+ "%[dptr] = *(u64 *)(r10 - 8);"
+ : [dptr] "+r"(dptr) :: "memory");
+ p = bpf_dynptr_data(dptr, 0, sizeof(*p));
+ if (!p)
+ return 0;
+ return 0;
+}
+
+static char dptr_mem_buf[16];
+
+/* check valid spill/fill, const ptr to dynptr */
+SEC("socket")
+__success
+int spill_fill_const_ptr_to_dynptr(void)
+{
+ struct bpf_dynptr ptr;
+
+ bpf_dynptr_from_mem(dptr_mem_buf, sizeof(dptr_mem_buf), 0, &ptr);
+ return spill_fill_dynptr_subprog(&ptr);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_stack_arg.c b/tools/testing/selftests/bpf/progs/verifier_stack_arg.c
new file mode 100644
index 000000000000..7e0ce5db28a0
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_stack_arg.c
@@ -0,0 +1,447 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(max_entries, 1);
+ __type(key, long long);
+ __type(value, long long);
+} map_hash_8b SEC(".maps");
+
+#if (defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) && \
+ defined(__BPF_FEATURE_STACK_ARGUMENT)
+
+__noinline __used
+static int subprog_6args(int a, int b, int c, int d, int e, int f)
+{
+ return a + b + c + d + e + f;
+}
+
+__noinline __used
+static int subprog_7args(int a, int b, int c, int d, int e, int f, int g)
+{
+ return a + b + c + d + e + f + g;
+}
+
+__noinline __used
+static long subprog_deref_arg6(long a, long b, long c, long d, long e, long *f)
+{
+ return *f;
+}
+
+SEC("tc")
+__description("stack_arg: subprog with 6 args")
+__success __retval(21)
+__naked void stack_arg_6args(void)
+{
+ asm volatile (
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ "*(u64 *)(r11 - 8) = 6;"
+ "call subprog_6args;"
+ "exit;"
+ ::: __clobber_all
+ );
+}
+
+SEC("tc")
+__description("stack_arg: two subprogs with >5 args")
+__success __retval(90)
+__naked void stack_arg_two_subprogs(void)
+{
+ asm volatile (
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ "*(u64 *)(r11 - 8) = 10;"
+ "call subprog_6args;"
+ "r6 = r0;"
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ "*(u64 *)(r11 - 16) = 30;"
+ "*(u64 *)(r11 - 8) = 20;"
+ "call subprog_7args;"
+ "r0 += r6;"
+ "exit;"
+ ::: __clobber_all
+ );
+}
+
+SEC("tc")
+__description("stack_arg: read from uninitialized stack arg slot")
+__failure
+__msg("invalid read from stack arg off 8 depth 0")
+__naked void stack_arg_read_uninitialized(void)
+{
+ asm volatile (
+ "r0 = *(u64 *)(r11 + 8);"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all
+ );
+}
+
+SEC("tc")
+__description("stack_arg: gap at offset -8, only wrote -16")
+__failure
+__msg("callee expects 7 args, stack arg1 is not initialized")
+__naked void stack_arg_gap_at_minus8(void)
+{
+ asm volatile (
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ "*(u64 *)(r11 - 16) = 30;"
+ "call subprog_7args;"
+ "exit;"
+ ::: __clobber_all
+ );
+}
+
+SEC("tc")
+__description("stack_arg: pruning with different stack arg types")
+__failure __log_level(2)
+__flag(BPF_F_TEST_STATE_FREQ)
+__msg("arg JOIN insn 9 -> 10 r1: fp0-8 + _ => fp0-8|fp0+0")
+__msg("arg JOIN insn 9 -> 10 sa0: fp0-8 + _ => fp0-8|fp0+0")
+__msg("R{{[0-9]}} invalid mem access 'scalar'")
+__naked void stack_arg_pruning_type_mismatch(void)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "r6 = r0;"
+ /* local = 0 on program stack */
+ "r7 = 0;"
+ "*(u64 *)(r10 - 8) = r7;"
+ /* Branch based on random value */
+ "if r6 s> 3 goto l0_%=;"
+ /* Path 1: store stack pointer to outgoing arg6 */
+ "r1 = r10;"
+ "r1 += -8;"
+ "*(u64 *)(r11 - 8) = r1;"
+ "goto l1_%=;"
+ "l0_%=:"
+ /* Path 2: store scalar to outgoing arg6 */
+ "*(u64 *)(r11 - 8) = 42;"
+ "l1_%=:"
+ /* Call subprog that dereferences arg6 */
+ "r1 = r6;"
+ "r2 = 0;"
+ "r3 = 0;"
+ "r4 = 0;"
+ "r5 = 0;"
+ "call subprog_deref_arg6;"
+ "exit;"
+ :: __imm(bpf_get_prandom_u32)
+ : __clobber_all
+ );
+}
+
+SEC("tc")
+__description("stack_arg: release_reference invalidates stack arg slot")
+__failure
+__msg("callee expects 6 args, stack arg1 is not initialized")
+__naked void stack_arg_release_ref(void)
+{
+ asm volatile (
+ "r6 = r1;"
+ /* struct bpf_sock_tuple tuple = {} */
+ "r2 = 0;"
+ "*(u32 *)(r10 - 8) = r2;"
+ "*(u64 *)(r10 - 16) = r2;"
+ "*(u64 *)(r10 - 24) = r2;"
+ "*(u64 *)(r10 - 32) = r2;"
+ "*(u64 *)(r10 - 40) = r2;"
+ "*(u64 *)(r10 - 48) = r2;"
+ /* sk = bpf_sk_lookup_tcp(ctx, &tuple, sizeof(tuple), 0, 0) */
+ "r1 = r6;"
+ "r2 = r10;"
+ "r2 += -48;"
+ "r3 = %[sizeof_bpf_sock_tuple];"
+ "r4 = 0;"
+ "r5 = 0;"
+ "call %[bpf_sk_lookup_tcp];"
+ /* r0 = sk (PTR_TO_SOCK_OR_NULL) */
+ "if r0 == 0 goto l0_%=;"
+ /* Store sock ref to outgoing arg6 slot */
+ "*(u64 *)(r11 - 8) = r0;"
+ /* Release the reference — invalidates the stack arg slot */
+ "r1 = r0;"
+ "call %[bpf_sk_release];"
+ /* Call subprog that dereferences arg6 — should fail */
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ "call subprog_deref_arg6;"
+ "l0_%=:"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_sk_lookup_tcp),
+ __imm(bpf_sk_release),
+ __imm_const(sizeof_bpf_sock_tuple, sizeof(struct bpf_sock_tuple))
+ : __clobber_all
+ );
+}
+
+SEC("tc")
+__description("stack_arg: pkt pointer in stack arg slot invalidated after pull_data")
+__failure
+__msg("callee expects 6 args, stack arg1 is not initialized")
+__naked void stack_arg_stale_pkt_ptr(void)
+{
+ asm volatile (
+ "r6 = r1;"
+ "r7 = *(u32 *)(r6 + %[__sk_buff_data]);"
+ "r8 = *(u32 *)(r6 + %[__sk_buff_data_end]);"
+ /* check pkt has at least 1 byte */
+ "r0 = r7;"
+ "r0 += 8;"
+ "if r0 > r8 goto l0_%=;"
+ /* Store valid pkt pointer to outgoing arg6 slot */
+ "*(u64 *)(r11 - 8) = r7;"
+ /* bpf_skb_pull_data invalidates all pkt pointers */
+ "r1 = r6;"
+ "r2 = 0;"
+ "call %[bpf_skb_pull_data];"
+ /* Call subprog that dereferences arg6 — should fail */
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ "call subprog_deref_arg6;"
+ "l0_%=:"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_skb_pull_data),
+ __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)),
+ __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end))
+ : __clobber_all
+ );
+}
+
+SEC("tc")
+__description("stack_arg: null propagation rejects deref on null branch")
+__failure
+__msg("R{{[0-9]}} invalid mem access 'scalar'")
+__naked void stack_arg_null_propagation_fail(void)
+{
+ asm volatile (
+ "r1 = 0;"
+ "*(u64 *)(r10 - 8) = r1;"
+ /* r0 = bpf_map_lookup_elem(&map_hash_8b, &key) */
+ "r2 = r10;"
+ "r2 += -8;"
+ "r1 = %[map_hash_8b] ll;"
+ "call %[bpf_map_lookup_elem];"
+ /* Store PTR_TO_MAP_VALUE_OR_NULL to outgoing arg6 slot */
+ "*(u64 *)(r11 - 8) = r0;"
+ /* null check on r0 */
+ "if r0 != 0 goto l0_%=;"
+ /*
+ * On null branch, outgoing slot is SCALAR(0).
+ * Call subprog that dereferences arg6 — should fail.
+ */
+ "r1 = 0;"
+ "r2 = 0;"
+ "r3 = 0;"
+ "r4 = 0;"
+ "r5 = 0;"
+ "call subprog_deref_arg6;"
+ "l0_%=:"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_8b)
+ : __clobber_all
+ );
+}
+
+SEC("tc")
+__description("stack_arg: missing store on one branch")
+__failure
+__msg("callee expects 7 args, stack arg1 is not initialized")
+__naked void stack_arg_missing_store_one_branch(void)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ /* Write arg7 (r11-16) before branch */
+ "*(u64 *)(r11 - 16) = 20;"
+ "if r0 > 0 goto l0_%=;"
+ /* Path 1: write arg6 and call */
+ "*(u64 *)(r11 - 8) = 10;"
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ "call subprog_7args;"
+ "goto l1_%=;"
+ "l0_%=:"
+ /* Path 2: missing arg6 store, call should fail */
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ "call subprog_7args;"
+ "l1_%=:"
+ "r0 = 0;"
+ "exit;"
+ :: __imm(bpf_get_prandom_u32)
+ : __clobber_all
+ );
+}
+
+SEC("tc")
+__description("stack_arg: share a store for both branches")
+__success __retval(0)
+__naked void stack_arg_shared_store(void)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ /* Write arg7 (r11-16) before branch */
+ "*(u64 *)(r11 - 16) = 20;"
+ "if r0 > 0 goto l0_%=;"
+ /* Path 1: write arg6 and call */
+ "*(u64 *)(r11 - 8) = 10;"
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ "call subprog_7args;"
+ "goto l1_%=;"
+ "l0_%=:"
+ /* Path 2: also write arg6 and call */
+ "*(u64 *)(r11 - 8) = 30;"
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ "call subprog_7args;"
+ "l1_%=:"
+ "r0 = 0;"
+ "exit;"
+ :: __imm(bpf_get_prandom_u32)
+ : __clobber_all
+ );
+}
+
+SEC("tc")
+__description("stack_arg: write beyond max outgoing depth")
+__failure
+__msg("stack arg write offset -80 exceeds max 7 stack args")
+__naked void stack_arg_write_beyond_max(void)
+{
+ asm volatile (
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ /* Write to offset -80, way beyond any callee's needs */
+ "*(u64 *)(r11 - 80) = 99;"
+ "*(u64 *)(r11 - 16) = 20;"
+ "*(u64 *)(r11 - 8) = 10;"
+ "call subprog_7args;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all
+ );
+}
+
+SEC("tc")
+__description("stack_arg: write unused stack arg slot")
+__failure
+__msg("func#0 writes 5 stack arg slots, but calls only require 2")
+__naked void stack_arg_write_unused_slot(void)
+{
+ asm volatile (
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ /* Write to offset -40, unused for the callee */
+ "*(u64 *)(r11 - 40) = 99;"
+ "*(u64 *)(r11 - 16) = 20;"
+ "*(u64 *)(r11 - 8) = 10;"
+ "call subprog_7args;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all
+ );
+}
+
+SEC("tc")
+__description("stack_arg: sequential calls reuse slots")
+__failure
+__msg("callee expects 7 args, stack arg1 is not initialized")
+__naked void stack_arg_sequential_calls(void)
+{
+ asm volatile (
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ "*(u64 *)(r11 - 8) = 6;"
+ "*(u64 *)(r11 - 16) = 7;"
+ "call subprog_7args;"
+ "r6 = r0;"
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ "call subprog_7args;"
+ "r0 += r6;"
+ "exit;"
+ ::: __clobber_all
+ );
+}
+
+#else
+
+SEC("socket")
+__description("stack_arg is not supported by compiler or jit, use a dummy test")
+__success
+int dummy_test(void)
+{
+ return 0;
+}
+
+#endif
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_stack_arg_order.c b/tools/testing/selftests/bpf/progs/verifier_stack_arg_order.c
new file mode 100644
index 000000000000..c9fe4857da3f
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_stack_arg_order.c
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+#if (defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64)) && \
+ defined(__BPF_FEATURE_STACK_ARGUMENT)
+
+__noinline __used __naked
+static int subprog_bad_order_6args(int a, int b, int c, int d, int e, int f)
+{
+ asm volatile (
+ "*(u64 *)(r11 - 8) = r1;"
+ "r0 = *(u64 *)(r11 + 8);"
+ "exit;"
+ ::: __clobber_all
+ );
+}
+
+SEC("tc")
+__description("stack_arg: r11 load after r11 store")
+__failure
+__msg("r11 load must be before any r11 store or call insn")
+__btf_func_path("btf__verifier_stack_arg_order.bpf.o")
+__naked void stack_arg_load_after_store(void)
+{
+ asm volatile (
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ "*(u64 *)(r11 - 8) = 6;"
+ "call subprog_bad_order_6args;"
+ "exit;"
+ ::: __clobber_all
+ );
+}
+
+__noinline __used __naked
+static int subprog_call_before_load_6args(int a, int b, int c, int d, int e,
+ int f)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "r0 = *(u64 *)(r11 + 8);"
+ "exit;"
+ :: __imm(bpf_get_prandom_u32)
+ : __clobber_all
+ );
+}
+
+SEC("tc")
+__description("stack_arg: r11 load after a call")
+__failure
+__msg("r11 load must be before any r11 store or call insn")
+__btf_func_path("btf__verifier_stack_arg_order.bpf.o")
+__naked void stack_arg_load_after_call(void)
+{
+ asm volatile (
+ "r1 = 1;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ "*(u64 *)(r11 - 8) = 6;"
+ "call subprog_call_before_load_6args;"
+ "exit;"
+ ::: __clobber_all
+ );
+}
+
+__noinline __used __naked
+static int subprog_pruning_call_before_load_6args(int a, int b, int c, int d,
+ int e, int f)
+{
+ asm volatile (
+ "if r1 s> 0 goto l0_%=;"
+ "goto l1_%=;"
+ "l0_%=:"
+ "call %[bpf_get_prandom_u32];"
+ "l1_%=:"
+ "r0 = *(u64 *)(r11 + 8);"
+ "exit;"
+ :: __imm(bpf_get_prandom_u32)
+ : __clobber_all
+ );
+}
+
+SEC("tc")
+__description("stack_arg: pruning keeps r11 load ordering")
+__failure
+__flag(BPF_F_TEST_STATE_FREQ)
+__msg("r11 load must be before any r11 store or call insn")
+__btf_func_path("btf__verifier_stack_arg_order.bpf.o")
+__naked void stack_arg_pruning_load_after_call(void)
+{
+ asm volatile (
+ "call %[bpf_get_prandom_u32];"
+ "r1 = r0;"
+ "r2 = 2;"
+ "r3 = 3;"
+ "r4 = 4;"
+ "r5 = 5;"
+ "*(u64 *)(r11 - 8) = 6;"
+ "call subprog_pruning_call_before_load_6args;"
+ "exit;"
+ :: __imm(bpf_get_prandom_u32)
+ : __clobber_all
+ );
+}
+
+/*
+ * "bad_ptr": the first arg is 'long *', which is not a recognized pointer
+ * type for static subprogs (not ctx, dynptr, or tagged). btf_prepare_func_args()
+ * sets arg_cnt = 7 / stack_arg_cnt = 2, then fails with -EINVAL. The subprog
+ * is marked unreliable but the call still proceeds for static subprogs.
+ */
+__noinline __used __naked
+static void subprog_bad_ptr_7args(long *a, int b, int c, int d, int e, int f, int g)
+{
+ asm volatile (
+ "r0 = *(u64 *)(r11 + 8);"
+ "r1 = *(u64 *)(r11 + 16);"
+ "exit;"
+ ::: __clobber_all
+ );
+}
+
+SEC("tc")
+__description("stack_arg: read without caller write")
+__failure
+__msg("callee expects 7 args, stack arg1 is not initialized")
+__btf_func_path("btf__verifier_stack_arg_order.bpf.o")
+__naked void stack_arg_read_without_write_1(void)
+{
+ asm volatile (
+ "r1 = 0;"
+ "r2 = 0;"
+ "r3 = 0;"
+ "r4 = 0;"
+ "r5 = 0;"
+ "call subprog_bad_ptr_7args;"
+ "exit;"
+ ::: __clobber_all
+ );
+}
+
+SEC("tc")
+__description("stack_arg: read with not-initialized caller write")
+__failure
+__msg("R0 !read_ok")
+__btf_func_path("btf__verifier_stack_arg_order.bpf.o")
+__naked void stack_arg_read_without_write_2(void)
+{
+ asm volatile (
+ "r1 = 0;"
+ "r2 = 0;"
+ "r3 = 0;"
+ "r4 = 0;"
+ "r5 = 0;"
+ "*(u64 *)(r11 - 8) = 0;"
+ "*(u64 *)(r11 - 16) = 0;"
+ "call subprog_bad_ptr_7args;"
+ "call subprog_bad_ptr_7args;"
+ "exit;"
+ ::: __clobber_all
+ );
+}
+
+#else
+
+SEC("socket")
+__description("stack_arg order is not supported by compiler or jit, use a dummy test")
+__success
+int dummy_test(void)
+{
+ return 0;
+}
+
+#endif
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_stack_ptr.c b/tools/testing/selftests/bpf/progs/verifier_stack_ptr.c
index 24aabc6083fd..8e8cf8232255 100644
--- a/tools/testing/selftests/bpf/progs/verifier_stack_ptr.c
+++ b/tools/testing/selftests/bpf/progs/verifier_stack_ptr.c
@@ -37,7 +37,7 @@ __naked void ptr_to_stack_store_load(void)
SEC("socket")
__description("PTR_TO_STACK store/load - bad alignment on off")
-__failure __msg("misaligned stack access off 0+-8+2 size 8")
+__failure __msg("misaligned stack access off -8+2 size 8")
__failure_unpriv
__naked void load_bad_alignment_on_off(void)
{
@@ -53,7 +53,7 @@ __naked void load_bad_alignment_on_off(void)
SEC("socket")
__description("PTR_TO_STACK store/load - bad alignment on reg")
-__failure __msg("misaligned stack access off 0+-10+8 size 8")
+__failure __msg("misaligned stack access off -10+8 size 8")
__failure_unpriv
__naked void load_bad_alignment_on_reg(void)
{
diff --git a/tools/testing/selftests/bpf/progs/verifier_subprog_insn_stats.c b/tools/testing/selftests/bpf/progs/verifier_subprog_insn_stats.c
new file mode 100644
index 000000000000..8f6082fdb5c8
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_subprog_insn_stats.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+struct timer_value {
+ struct bpf_timer timer;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, __u32);
+ __type(value, struct timer_value);
+} timer_map SEC(".maps");
+
+SEC("?raw_tp")
+__success __log_level(4)
+__msg("subprog 0 (stats_main_only) main insns_self 2 insns_total 2 stack 0")
+__msg("processed 2 insns")
+__naked int stats_main_only(void)
+{
+ asm volatile (
+ "r0 = 0;"
+ "exit;"
+ );
+}
+
+__naked __noinline __used
+static int stats_chain_leaf(void)
+{
+ asm volatile (
+ "r0 = 0;"
+ "exit;"
+ );
+}
+
+__naked __noinline __used
+static int stats_chain_parent(void)
+{
+ asm volatile (
+ "call stats_chain_leaf;"
+ "exit;"
+ );
+}
+
+SEC("?raw_tp")
+__success __log_level(4)
+/*
+ * self: 2 + 2 + 2 = 6
+ * totals: leaf 2, parent 2 + 2 = 4, main 2 + 4 = 6
+ */
+__msg("subprog 0 (stats_static_chain) main insns_self 2 insns_total 6 stack 0")
+__msg("subprog {{[0-9]+}} (stats_chain_parent) static insns_self 2 insns_total 4 stack 0")
+__msg("subprog {{[0-9]+}} (stats_chain_leaf) static insns_self 2 insns_total 2 stack 0")
+__msg("processed 6 insns")
+__naked int stats_static_chain(void)
+{
+ asm volatile (
+ "call stats_chain_parent;"
+ "exit;"
+ );
+}
+
+__naked __noinline __used
+static int stats_shared_leaf(void)
+{
+ asm volatile (
+ "r0 = 0;"
+ "exit;"
+ );
+}
+
+__naked __noinline __used
+int stats_global_root(void)
+{
+ asm volatile (
+ "call stats_shared_leaf;"
+ "exit;"
+ );
+}
+
+SEC("?raw_tp")
+__success __log_level(4)
+/*
+ * stats_shared_leaf is explored once under each independent root.
+ * self: main 3 + leaf 4 + global 2 = 9
+ * root totals: main 5 + global 4 = 9
+ */
+__msg("subprog 0 (stats_shared_roots) main insns_self 3 insns_total 5 stack 0")
+__msg("subprog {{[0-9]+}} (stats_shared_leaf) static insns_self 4 insns_total 4 stack 0")
+__msg("subprog {{[0-9]+}} (stats_global_root) global insns_self 2 insns_total 4 stack 0")
+__msg("processed 9 insns")
+__naked int stats_shared_roots(void)
+{
+ asm volatile (
+ "call stats_shared_leaf;"
+ "call stats_global_root;"
+ "exit;"
+ );
+}
+
+__noinline __used
+static int stats_async_leaf(void *map, __u32 *key, struct bpf_timer *timer)
+{
+ return 0;
+}
+
+__noinline __used
+static __u64 stats_async_schedule(struct bpf_map *map, __u32 *key,
+ struct timer_value *value, void *ctx)
+{
+ asm volatile (
+ "r1 = %[timer];"
+ "r2 = %[stats_async_leaf];"
+ "call %[bpf_timer_set_callback];"
+ :
+ : [timer] "r" (value),
+ __imm_ptr(stats_async_leaf),
+ __imm(bpf_timer_set_callback)
+ : __clobber_common
+ );
+ return 0;
+}
+
+SEC("?raw_tp")
+__success __log_level(4)
+/*
+ * self: 9 + 7 + 2 = 18
+ * totals: leaf 2, scheduler 7, main root 18
+ */
+__msg("subprog 0 (stats_async_direct) main insns_self 9 insns_total 18 stack 0")
+__msg("subprog {{[0-9]+}} (stats_async_schedule) static insns_self 7 insns_total 7 stack 0")
+__msg("subprog {{[0-9]+}} (stats_async_leaf) static insns_self 2 insns_total 2 stack 0")
+__msg("processed 18 insns")
+__naked int stats_async_direct(void)
+{
+ asm volatile (
+ "r1 = %[timer_map] ll;"
+ "r2 = %[stats_async_schedule];"
+ "r3 = 0;"
+ "r4 = 0;"
+ "call %[bpf_for_each_map_elem];"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_addr(timer_map),
+ __imm_ptr(stats_async_schedule),
+ __imm(bpf_for_each_map_elem)
+ : __clobber_common
+ );
+}
+
+__noinline __used
+static int stats_async_nested_leaf(void *map, __u32 *key, struct bpf_timer *timer)
+{
+ return 0;
+}
+
+__noinline __used
+static int stats_async_outer(void *map, __u32 *key, struct bpf_timer *timer)
+{
+ asm volatile (
+ "r1 = %[timer];"
+ "r2 = %[stats_async_nested_leaf];"
+ "call %[bpf_timer_set_callback];"
+ :
+ : [timer] "r" (timer),
+ __imm_ptr(stats_async_nested_leaf),
+ __imm(bpf_timer_set_callback)
+ : __clobber_common
+ );
+ return 0;
+}
+
+__noinline __used
+static __u64 stats_async_nested_schedule(struct bpf_map *map, __u32 *key,
+ struct timer_value *value, void *ctx)
+{
+ asm volatile (
+ "r1 = %[timer];"
+ "r2 = %[stats_async_outer];"
+ "call %[bpf_timer_set_callback];"
+ :
+ : [timer] "r" (value),
+ __imm_ptr(stats_async_outer),
+ __imm(bpf_timer_set_callback)
+ : __clobber_common
+ );
+ return 0;
+}
+
+SEC("?raw_tp")
+__success __log_level(4)
+/*
+ * self: 9 + 7 + 7 + 2 = 25
+ * totals: leaf 2, outer 7, scheduler 7, main root 25
+ */
+__msg("subprog 0 (stats_async_nested) main insns_self 9 insns_total 25 stack 0")
+__msg("subprog {{[0-9]+}} (stats_async_nested_schedule) static insns_self 7 insns_total 7 stack 0")
+__msg("subprog {{[0-9]+}} (stats_async_outer) static insns_self 7 insns_total 7 stack 0")
+__msg("subprog {{[0-9]+}} (stats_async_nested_leaf) static insns_self 2 insns_total 2 stack 0")
+__msg("processed 25 insns")
+__naked int stats_async_nested(void)
+{
+ asm volatile (
+ "r1 = %[timer_map] ll;"
+ "r2 = %[stats_async_nested_schedule];"
+ "r3 = 0;"
+ "r4 = 0;"
+ "call %[bpf_for_each_map_elem];"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_addr(timer_map),
+ __imm_ptr(stats_async_nested_schedule),
+ __imm(bpf_for_each_map_elem)
+ : __clobber_common
+ );
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c b/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c
index 61886ed554de..dc0c7034c04f 100644
--- a/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c
+++ b/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c
@@ -282,14 +282,14 @@ __msg("mark_precise: frame0: regs=r0,r6 stack= before 10: (bf) r6 = r0")
__msg("mark_precise: frame0: regs=r0 stack= before 9: (85) call bpf_loop")
/* State entering callback body popped from states stack */
__msg("from 9 to 17: frame1:")
-__msg("17: frame1: R1=scalar() R2=0 R10=fp0 cb")
+__msg("17: frame1: R10=fp0 cb")
__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)
{
@@ -411,7 +411,7 @@ __msg("mark_precise: frame0: regs=r6 stack= before 5: (b7) r1 = 1")
__msg("mark_precise: frame0: regs=r6 stack= before 4: (b7) r6 = 3")
/* State entering callback body popped from states stack */
__msg("from 9 to 15: frame1:")
-__msg("15: frame1: R1=scalar() R2=0 R10=fp0 cb")
+__msg("15: frame1: R10=fp0 cb")
__msg("15: (b7) r0 = 0")
__msg("16: (95) exit")
__msg("returning from callee:")
@@ -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=:")
@@ -567,7 +567,7 @@ __msg("mark_precise: frame0: regs= stack=-8 before 5: (7b) *(u64 *)(r10 -8) = r6
__msg("mark_precise: frame0: regs=r6 stack= before 4: (b7) r6 = 3")
/* State entering callback body popped from states stack */
__msg("from 10 to 17: frame1:")
-__msg("17: frame1: R1=scalar() R2=0 R10=fp0 cb")
+__msg("17: frame1: R10=fp0 cb")
__msg("17: (b7) r0 = 0")
__msg("18: (95) exit")
__msg("returning from callee:")
@@ -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=:")
@@ -681,7 +681,7 @@ __msg("mark_precise: frame0: last_idx 10 first_idx 7 subseq_idx -1")
__msg("mark_precise: frame0: regs=r7 stack= before 9: (bf) r1 = r8")
__msg("mark_precise: frame0: regs=r7 stack= before 8: (27) r7 *= 4")
__msg("mark_precise: frame0: regs=r7 stack= before 7: (79) r7 = *(u64 *)(r10 -8)")
-__msg("mark_precise: frame0: parent state regs= stack=-8: R0=2 R6=1 R8=map_value(map=.data.vals,ks=4,vs=16) R10=fp0 fp-8=P1")
+__msg("mark_precise: frame0: parent state regs= stack=-8: R8=map_value(map=.data.vals,ks=4,vs=16) R10=fp0 fp-8=P1")
__msg("mark_precise: frame0: last_idx 18 first_idx 0 subseq_idx 7")
__msg("mark_precise: frame0: regs= stack=-8 before 18: (95) exit")
__msg("mark_precise: frame1: regs= stack= before 17: (0f) r0 += r2")
@@ -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_subprog_topo.c b/tools/testing/selftests/bpf/progs/verifier_subprog_topo.c
new file mode 100644
index 000000000000..e2b9d14bbc3d
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_subprog_topo.c
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+/* linear chain main -> A -> B */
+__naked __noinline __used
+static unsigned long linear_b(void)
+{
+ asm volatile (
+ "r0 = 42;"
+ "exit;"
+ );
+}
+
+__naked __noinline __used
+static unsigned long linear_a(void)
+{
+ asm volatile (
+ "call linear_b;"
+ "exit;"
+ );
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("topo_order[0] = linear_b")
+__msg("topo_order[1] = linear_a")
+__msg("topo_order[2] = topo_linear")
+__naked int topo_linear(void)
+{
+ asm volatile (
+ "call linear_a;"
+ "exit;"
+ );
+}
+
+/* diamond main -> A, main -> B, A -> C, B -> C */
+__naked __noinline __used
+static unsigned long diamond_c(void)
+{
+ asm volatile (
+ "r0 = 1;"
+ "exit;"
+ );
+}
+
+__naked __noinline __used
+static unsigned long diamond_b(void)
+{
+ asm volatile (
+ "call diamond_c;"
+ "exit;"
+ );
+}
+
+__naked __noinline __used
+static unsigned long diamond_a(void)
+{
+ asm volatile (
+ "call diamond_c;"
+ "exit;"
+ );
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("topo_order[0] = diamond_c")
+__msg("topo_order[3] = topo_diamond")
+__naked int topo_diamond(void)
+{
+ asm volatile (
+ "call diamond_a;"
+ "call diamond_b;"
+ "exit;"
+ );
+}
+
+/* main -> global_a (global) -> static_leaf (static, leaf) */
+__naked __noinline __used
+static unsigned long static_leaf(void)
+{
+ asm volatile (
+ "r0 = 7;"
+ "exit;"
+ );
+}
+
+__noinline __used
+int global_a(int x)
+{
+ return static_leaf();
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("topo_order[0] = static_leaf")
+__msg("topo_order[1] = global_a")
+__msg("topo_order[2] = topo_mixed")
+__naked int topo_mixed(void)
+{
+ asm volatile (
+ "r1 = 0;"
+ "call global_a;"
+ "exit;"
+ );
+}
+
+/*
+ * shared static callee from global and main:
+ * main -> shared_leaf (static)
+ * main -> global_b (global) -> shared_leaf (static)
+ */
+__naked __noinline __used
+static unsigned long shared_leaf(void)
+{
+ asm volatile (
+ "r0 = 99;"
+ "exit;"
+ );
+}
+
+__noinline __used
+int global_b(int x)
+{
+ return shared_leaf();
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("topo_order[0] = shared_leaf")
+__msg("topo_order[1] = global_b")
+__msg("topo_order[2] = topo_shared")
+__naked int topo_shared(void)
+{
+ asm volatile (
+ "call shared_leaf;"
+ "r1 = 0;"
+ "call global_b;"
+ "exit;"
+ );
+}
+
+/* duplicate calls to the same subprog */
+__naked __noinline __used
+static unsigned long dup_leaf(void)
+{
+ asm volatile (
+ "r0 = 0;"
+ "exit;"
+ );
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("topo_order[0] = dup_leaf")
+__msg("topo_order[1] = topo_dup_calls")
+__naked int topo_dup_calls(void)
+{
+ asm volatile (
+ "call dup_leaf;"
+ "call dup_leaf;"
+ "exit;"
+ );
+}
+
+/* main calls bpf_loop() with loop_cb as the callback */
+static int loop_cb(int idx, void *ctx)
+{
+ return 0;
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("topo_order[0] = loop_cb")
+__msg("topo_order[1] = topo_loop_cb")
+int topo_loop_cb(void)
+{
+ bpf_loop(1, loop_cb, NULL, 0);
+ return 0;
+}
+
+/*
+ * bpf_loop callback calling another subprog
+ * main -> bpf_loop(callback=loop_cb2) -> loop_cb2 -> loop_cb2_leaf
+ */
+__naked __noinline __used
+static unsigned long loop_cb2_leaf(void)
+{
+ asm volatile (
+ "r0 = 0;"
+ "exit;"
+ );
+}
+
+static int loop_cb2(int idx, void *ctx)
+{
+ return loop_cb2_leaf();
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("topo_order[0] = loop_cb2_leaf")
+__msg("topo_order[1] = loop_cb2")
+__msg("topo_order[2] = topo_loop_cb_chain")
+int topo_loop_cb_chain(void)
+{
+ bpf_loop(1, loop_cb2, NULL, 0);
+ return 0;
+}
+
+/* no calls (single subprog) */
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("topo_order[0] = topo_no_calls")
+__naked int topo_no_calls(void)
+{
+ asm volatile (
+ "r0 = 0;"
+ "exit;"
+ );
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_subreg.c b/tools/testing/selftests/bpf/progs/verifier_subreg.c
index 8613ea160dcd..73b5b0cf6706 100644
--- a/tools/testing/selftests/bpf/progs/verifier_subreg.c
+++ b/tools/testing/selftests/bpf/progs/verifier_subreg.c
@@ -532,6 +532,76 @@ __naked void arsh32_imm_zero_extend_check(void)
}
SEC("socket")
+__description("arsh32 imm sign positive extend check")
+__success __retval(0)
+__log_level(2)
+__msg("2: (57) r6 &= 4095 ; R6=scalar(smin=smin32=0,smax=umax=smax32=umax32=4095,var_off=(0x0; 0xfff))")
+__msg("3: (67) r6 <<= 32 ; R6=scalar(smin=smin32=0,smax=umax=0xfff00000000,smax32=umax32=0,var_off=(0x0; 0xfff00000000))")
+__msg("4: (c7) r6 s>>= 32 ; R6=scalar(smin=smin32=0,smax=umax=smax32=umax32=4095,var_off=(0x0; 0xfff))")
+__naked void arsh32_imm_sign_extend_positive_check(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r6 = r0; \
+ r6 &= 4095; \
+ r6 <<= 32; \
+ r6 s>>= 32; \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("arsh32 imm sign negative extend check")
+__success __retval(0)
+__log_level(2)
+__msg("3: (17) r6 -= 4095 ; R6=scalar(smin=smin32=-4095,smax=smax32=0)")
+__msg("4: (67) r6 <<= 32 ; R6=scalar(smin=0xfffff00100000000,smax=smax32=umax32=0,smin32=0,var_off=(0x0; 0xffffffff00000000))")
+/* represents shorter of signed / unsigned 64-bit ranges */
+__msg("5: (c7) r6 s>>= 32 ; R6=scalar(smin=smin32=-4095,smax=smax32=0)")
+__naked void arsh32_imm_sign_extend_negative_check(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r6 = r0; \
+ r6 &= 4095; \
+ r6 -= 4095; \
+ r6 <<= 32; \
+ r6 s>>= 32; \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("arsh32 imm sign extend check")
+__success __retval(0)
+__log_level(2)
+__msg("3: (17) r6 -= 2047 ; R6=scalar(smin=smin32=-2047,smax=smax32=2048)")
+__msg("4: (67) r6 <<= 32 ; R6=scalar(smin=0xfffff80100000000,smax=0x80000000000,smin32=0,smax32=umax32=0,var_off=(0x0; 0xffffffff00000000))")
+/* represents shorter of signed / unsigned 64-bit ranges */
+__msg("5: (c7) r6 s>>= 32 ; R6=scalar(smin=smin32=-2047,smax=smax32=2048)")
+__naked void arsh32_imm_sign_extend_check(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r6 = r0; \
+ r6 &= 4095; \
+ r6 -= 2047; \
+ r6 <<= 32; \
+ r6 s>>= 32; \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
__description("end16 (to_le) reg zero extend check")
__success __success_unpriv __retval(0)
__naked void le_reg_zero_extend_check_1(void)
@@ -670,4 +740,254 @@ __naked void ldx_w_zero_extend_check(void)
: __clobber_all);
}
+SEC("socket")
+__success __success_unpriv __retval(0)
+__naked void arsh_31_and(void)
+{
+ /* Below is what LLVM generates in cilium's bpf_wiregard.o */
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w2 = w0; \
+ w2 s>>= 31; \
+ w2 &= -134; /* w2 becomes 0 or -134 */ \
+ if w2 s> -1 goto +2; \
+ /* Branch always taken because w2 = -134 */ \
+ if w2 != -136 goto +1; \
+ w0 /= 0; \
+ w0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__success __success_unpriv __retval(0)
+__naked void arsh_63_and(void)
+{
+ /* Copy of arsh_31 with s/w/r/ */
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r2 = r0; \
+ r2 <<= 32; \
+ r2 s>>= 63; \
+ r2 &= -134; \
+ if r2 s> -1 goto +2; \
+ /* Branch always taken because w2 = -134 */ \
+ if r2 != -136 goto +1; \
+ r0 /= 0; \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__success __success_unpriv __retval(0)
+__naked void arsh_31_or(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w2 = w0; \
+ w2 s>>= 31; \
+ w2 |= 134; /* w2 becomes -1 or 134 */ \
+ if w2 s> -1 goto +2; \
+ /* Branch always taken because w2 = -1 */ \
+ if w2 == -1 goto +1; \
+ w0 /= 0; \
+ w0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__success __success_unpriv __retval(0)
+__naked void arsh_63_or(void)
+{
+ /* Copy of arsh_31 with s/w/r/ */
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r2 = r0; \
+ r2 <<= 32; \
+ r2 s>>= 63; \
+ r2 |= 134; /* r2 becomes -1 or 134 */ \
+ if r2 s> -1 goto +2; \
+ /* Branch always taken because w2 = -1 */ \
+ if r2 == -1 goto +1; \
+ r0 /= 0; \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__success __retval(42)
+__naked void arsh32_imm1_value(void)
+{
+ asm volatile (" \
+ r0 = 42; \
+ r1 = -2147483648; \
+ w1 s>>= 1; /* r1 = 0xC0000000 */ \
+ r2 = 0xC0000000 ll; \
+ if r1 == r2 goto l0_%=; \
+ r0 /= 0; /* unreachable */ \
+l0_%=: exit; \
+" :
+ :
+ : __clobber_all);
+}
+
+SEC("socket")
+__success __retval(1)
+__naked void lsh32_reg0_zero_extend_check(void)
+{
+ asm volatile (" \
+ r6 = 1; \
+ call %[bpf_get_prandom_u32]; \
+ r1 = 0x1000000000 ll; \
+ r0 |= r1; \
+ w1 = 0; \
+ w0 <<= w1; /* reg shift by 0 */ \
+ r0 >>= 32; /* must be 0 */ \
+ if r0 == 0 goto l0_%=; \
+ r6 /= 0; /* unreachable */ \
+l0_%=: r0 = r6; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__success __retval(1)
+__naked void rsh32_reg0_zero_extend_check(void)
+{
+ asm volatile (" \
+ r6 = 1; \
+ call %[bpf_get_prandom_u32]; \
+ r1 = 0x1000000000 ll; \
+ r0 |= r1; \
+ w1 = 0; \
+ w0 >>= w1; /* reg rsh by 0 */ \
+ r0 >>= 32; /* must be 0 */ \
+ if r0 == 0 goto l0_%=; \
+ r6 /= 0; /* unreachable */ \
+l0_%=: r0 = r6; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__success __retval(1)
+__naked void arsh32_reg0_zero_extend_check(void)
+{
+ asm volatile (" \
+ r6 = 1; \
+ call %[bpf_get_prandom_u32]; \
+ r1 = 0x1000000000 ll; \
+ r0 |= r1; \
+ w1 = 0; \
+ w0 s>>= w1; /* reg arsh by 0 */ \
+ r0 >>= 32; /* must be 0 */ \
+ if r0 == 0 goto l0_%=; \
+ r6 /= 0; /* unreachable */ \
+l0_%=: r0 = r6; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__success __retval(42)
+__naked void lsh32_imm31_value(void)
+{
+ asm volatile (" \
+ r0 = 42; \
+ r1 = 1; \
+ w1 <<= 31; /* r1 = 0x80000000 */ \
+ r2 = 0x80000000 ll; \
+ if r1 == r2 goto l0_%=; \
+ r0 /= 0; /* unreachable */ \
+l0_%=: exit; \
+" :
+ :
+ : __clobber_all);
+}
+
+SEC("socket")
+__success __retval(42)
+__naked void rsh32_imm31_value(void)
+{
+ asm volatile (" \
+ r0 = 42; \
+ r1 = -2147483648; /* 0x80000000 */ \
+ w1 >>= 31; /* r1 = 1 */ \
+ if r1 == 1 goto l0_%=; \
+ r0 /= 0; /* unreachable */ \
+l0_%=: exit; \
+" :
+ :
+ : __clobber_all);
+}
+
+SEC("socket")
+__success __retval(42)
+__naked void arsh32_imm31_value(void)
+{
+ asm volatile (" \
+ r0 = 42; \
+ r1 = -2147483648; /* 0x80000000 */ \
+ w1 s>>= 31; /* r1 = 0xFFFFFFFF */ \
+ r2 = 0xFFFFFFFF ll; \
+ if r1 == r2 goto l0_%=; \
+ r0 /= 0; /* unreachable */ \
+l0_%=: exit; \
+" :
+ :
+ : __clobber_all);
+}
+
+SEC("socket")
+__success __retval(1)
+__naked void lsh32_unknown_precise_bounds(void)
+{
+ asm volatile (" \
+ r6 = 1; \
+ call %[bpf_get_prandom_u32]; \
+ w0 &= 3; /* u32: [0, 3] */ \
+ w0 <<= 1; /* u32: [0, 6] */ \
+ if w0 < 7 goto l0_%=; \
+ r6 /= 0; /* unreachable */ \
+l0_%=: r0 = r6; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__success __retval(1)
+__naked void rsh32_unknown_bounds(void)
+{
+ asm volatile (" \
+ r6 = 1; \
+ call %[bpf_get_prandom_u32]; \
+ w0 >>= 28; /* u32: [0, 15] */ \
+ if w0 < 16 goto l0_%=; \
+ r6 /= 0; /* unreachable */ \
+l0_%=: r0 = r6; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c b/tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c
index 8d60c634a114..48fa34d2959f 100644
--- a/tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c
+++ b/tools/testing/selftests/bpf/progs/verifier_tailcall_jit.c
@@ -56,6 +56,7 @@ __jited("L1: pushq %rax") /* rbp[-16] = rax */
* (cause original rax might be clobbered by this point)
*/
__jited(" movq -0x10(%rbp), %rax")
+__jited("...")
__jited(" callq 0x{{.*}}") /* call to sub() */
__jited(" xorl %eax, %eax")
__jited(" leave")
diff --git a/tools/testing/selftests/bpf/progs/verifier_uninit.c b/tools/testing/selftests/bpf/progs/verifier_uninit.c
index 7718cd7d19ce..691018a46049 100644
--- a/tools/testing/selftests/bpf/progs/verifier_uninit.c
+++ b/tools/testing/selftests/bpf/progs/verifier_uninit.c
@@ -9,6 +9,7 @@
SEC("socket")
__description("read uninitialized register")
__failure __msg("R2 !read_ok")
+__msg("R2 has never been initialized on this path")
__failure_unpriv
__naked void read_uninitialized_register(void)
{
diff --git a/tools/testing/selftests/bpf/progs/verifier_unpriv.c b/tools/testing/selftests/bpf/progs/verifier_unpriv.c
index 28b4f7035ceb..3069e70fbcbd 100644
--- a/tools/testing/selftests/bpf/progs/verifier_unpriv.c
+++ b/tools/testing/selftests/bpf/progs/verifier_unpriv.c
@@ -6,6 +6,8 @@
#include "../../../include/linux/filter.h"
#include "bpf_misc.h"
+extern const int bpf_prog_active __ksym;
+
#define BPF_SK_LOOKUP(func) \
/* struct bpf_sock_tuple tuple = {} */ \
"r2 = 0;" \
@@ -78,6 +80,41 @@ __naked void dummy_prog_loop1_socket(void)
}
SEC("socket")
+__description("unpriv: pseudo btf id log masks address")
+__success_unpriv
+__msg_unpriv("0: (18) r1 = 0x0")
+__not_msg_unpriv("0: (18) r1 = 0x{{[1-9a-f][0-9a-f]*}}")
+__retval_unpriv(0)
+__log_level(2)
+__naked void pseudo_btf_id_log_masks_address(void)
+{
+ asm volatile ("r1 = %[bpf_prog_active] ll;"
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_addr(bpf_prog_active)
+ : __clobber_all);
+}
+
+static int pseudo_func_callback(__u32 index, void *ctx)
+{
+ return 0;
+}
+
+SEC("socket")
+__description("unpriv: pseudo function policy diagnostic")
+__success __failure_unpriv
+__msg_unpriv("loading/calling other bpf or kernel functions")
+__not_msg_unpriv("BPF-to-BPF function call")
+__msg_unpriv("policy check failed for BPF function reference")
+__msg_unpriv("avoid BPF function references in unprivileged")
+int unpriv_pseudo_func_policy(void *ctx)
+{
+ bpf_loop(1, pseudo_func_callback, NULL, 0);
+ return 0;
+}
+
+SEC("socket")
__description("unpriv: return pointer")
__success __failure_unpriv __msg_unpriv("R0 leaks addr")
__retval(POINTER_VALUE)
@@ -584,7 +621,7 @@ __naked void alu32_mov_u32_const(void)
{
asm volatile (" \
w7 = 0; \
- w7 &= 1; \
+ w7 ^= w7; \
w0 = w7; \
if r0 == 0 goto l0_%=; \
r0 = *(u64*)(r7 + 0); \
@@ -894,7 +931,9 @@ __naked void unpriv_spectre_v1_and_v4_simple(void)
{
asm volatile (" \
r8 = 0; \
+ r8 ^= r8; \
r9 = 0; \
+ r9 ^= r9; \
r0 = r10; \
r1 = 0; \
r2 = r10; \
@@ -932,7 +971,9 @@ __naked void unpriv_ldimm64_spectre_v1_and_v4_simple(void)
{
asm volatile (" \
r8 = 0; \
+ r8 ^= r8; \
r9 = 0; \
+ r9 ^= r9; \
r0 = r10; \
r1 = 0; \
r2 = r10; \
@@ -950,4 +991,48 @@ l3_%=: r0 = 0; \
" ::: __clobber_all);
}
+SEC("socket")
+__description("unpriv: nospec after dead stack write in helper")
+__success __success_unpriv
+__retval(0)
+/* Dead code sanitizer rewrites the call to `goto -1`. */
+__naked void unpriv_dead_helper_stack_write_nospec_result(void)
+{
+ asm volatile (" \
+ r0 = 0; \
+ if r0 != 1 goto l0_%=; \
+ r2 = 0; \
+ r3 = r10; \
+ r3 += -16; \
+ r4 = 4; \
+ r5 = 0; \
+ call %[bpf_skb_load_bytes_relative]; \
+l0_%=: exit; \
+" :
+ : __imm(bpf_skb_load_bytes_relative)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("unpriv: Spectre v4 stack write slot index")
+__success __success_unpriv
+__retval(0)
+#ifdef SPEC_V4
+__xlated_unpriv("r0 = 0")
+__xlated_unpriv("*(u32 *)(r10 -4) = r0")
+__xlated_unpriv("nospec")
+__xlated_unpriv("*(u32 *)(r10 -8) = r0")
+__xlated_unpriv("nospec")
+__xlated_unpriv("exit")
+#endif
+__naked void stack_write_nospec_slot_index(void)
+{
+ asm volatile (" \
+ r0 = 0; \
+ *(u32 *)(r10 - 4) = r0; \
+ *(u32 *)(r10 - 8) = r0; \
+ exit; \
+" ::: __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c b/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c
index 2129e4353fd9..4d8273c258d5 100644
--- a/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c
+++ b/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c
@@ -173,14 +173,15 @@ __naked void flow_keys_illegal_variable_offset_alu(void)
asm volatile(" \
r6 = r1; \
r7 = *(u64*)(r6 + %[flow_keys_off]); \
- r8 = 8; \
- r8 /= 1; \
+ call %[bpf_get_prandom_u32]; \
+ r8 = r0; \
r8 &= 8; \
r7 += r8; \
r0 = *(u64*)(r7 + 0); \
exit; \
" :
- : __imm_const(flow_keys_off, offsetof(struct __sk_buff, flow_keys))
+ : __imm_const(flow_keys_off, offsetof(struct __sk_buff, flow_keys)),
+ __imm(bpf_get_prandom_u32)
: __clobber_all);
}
diff --git a/tools/testing/selftests/bpf/progs/verifier_value_ptr_arith.c b/tools/testing/selftests/bpf/progs/verifier_value_ptr_arith.c
index af7938ce56cb..b3b701b44550 100644
--- a/tools/testing/selftests/bpf/progs/verifier_value_ptr_arith.c
+++ b/tools/testing/selftests/bpf/progs/verifier_value_ptr_arith.c
@@ -346,7 +346,7 @@ l2_%=: r0 = 1; \
SEC("socket")
__description("map access: value_ptr -= known scalar from different maps")
__success __failure_unpriv
-__msg_unpriv("R0 min value is outside of the allowed memory range")
+__msg_unpriv("R0 min value is negative")
__retval(1)
__naked void known_scalar_from_different_maps(void)
{
@@ -683,9 +683,7 @@ l0_%=: r0 = 1; \
SEC("socket")
__description("map access: value_ptr -= known scalar, lower oob arith, test 1")
-__failure __msg("R0 min value is outside of the allowed memory range")
-__failure_unpriv
-__msg_unpriv("R0 pointer arithmetic of map value goes out of range")
+__failure __msg("R0 min value is negative")
__naked void lower_oob_arith_test_1(void)
{
asm volatile (" \
@@ -840,7 +838,7 @@ l0_%=: r0 = 1; \
SEC("socket")
__description("map access: value_ptr += known scalar, 3")
-__failure __msg("invalid access to map value")
+__failure __msg("R0 min value is negative")
__failure_unpriv
__naked void value_ptr_known_scalar_3(void)
{
@@ -1207,7 +1205,7 @@ l0_%=: r0 = 1; \
SEC("socket")
__description("map access: value_ptr -= known scalar")
-__failure __msg("R0 min value is outside of the allowed memory range")
+__failure __msg("R0 min value is negative")
__failure_unpriv
__naked void access_value_ptr_known_scalar(void)
{
diff --git a/tools/testing/selftests/bpf/progs/verifier_var_off.c b/tools/testing/selftests/bpf/progs/verifier_var_off.c
index f345466bca68..a63e33675091 100644
--- a/tools/testing/selftests/bpf/progs/verifier_var_off.c
+++ b/tools/testing/selftests/bpf/progs/verifier_var_off.c
@@ -60,6 +60,116 @@ __naked void stack_read_priv_vs_unpriv(void)
}
SEC("cgroup/skb")
+__description("variable-offset stack read preserves spilled zero")
+__success
+__log_level(2)
+__msg("mark_precise: frame0: regs= stack=-8")
+__msg("R3=0")
+__retval(0)
+__naked void stack_read_var_off_preserves_spilled_zero(void)
+{
+ asm volatile (" \
+ r0 = 0; \
+ *(u64*)(r10 - 8) = r0; \
+ r2 = *(u32*)(r1 + 0); \
+ r2 &= 7; \
+ r2 -= 8; \
+ r2 += r10; \
+ r3 = *(u8*)(r2 + 0); \
+ r1 = r10; \
+ r1 += -1; \
+ r1 += r3; \
+ *(u8*)(r1 + 0) = r3; \
+ r0 = 0; \
+ exit; \
+" ::: __clobber_all);
+}
+
+SEC("cgroup/skb")
+__description("variable-offset stack read preserves spilled zero across slots")
+__success
+__log_level(2)
+__msg("mark_precise: frame0: regs= stack=-8,-16")
+__msg("R3=0")
+__retval(0)
+__naked void stack_read_var_off_preserves_spilled_zero_across_slots(void)
+{
+ asm volatile (" \
+ r0 = 0; \
+ *(u64*)(r10 - 8) = r0; \
+ *(u64*)(r10 - 16) = r0; \
+ r2 = *(u32*)(r1 + 0); \
+ r2 &= 15; \
+ r2 -= 16; \
+ r2 += r10; \
+ r3 = *(u8*)(r2 + 0); \
+ r1 = r10; \
+ r1 += -1; \
+ r1 += r3; \
+ *(u8*)(r1 + 0) = r3; \
+ r0 = 0; \
+ exit; \
+" ::: __clobber_all);
+}
+
+SEC("cgroup/skb")
+__description("variable-offset stack read preserves partial spilled zero")
+__success
+__log_level(2)
+__msg("mark_precise: frame0: regs= stack=-8")
+__msg("R3=0")
+__retval(0)
+__naked void stack_read_var_off_preserves_partial_spilled_zero(void)
+{
+ asm volatile (" \
+ r0 = 0; \
+ *(u8*)(r10 - 9) = r0; \
+ *(u8*)(r10 - 10) = r0; \
+ *(u8*)(r10 - 11) = r0; \
+ *(u8*)(r10 - 12) = r0; \
+ *(u8*)(r10 - 13) = r0; \
+ *(u8*)(r10 - 14) = r0; \
+ *(u8*)(r10 - 15) = r0; \
+ *(u32*)(r10 - 8) = r0; \
+ r2 = *(u32*)(r1 + 0); \
+ r2 &= 15; \
+ if r2 > 10 goto l0_%=; \
+ r2 -= 15; \
+ r2 += r10; \
+ r3 = *(u8*)(r2 + 0); \
+ r1 = r10; \
+ r1 += -1; \
+ r1 += r3; \
+ *(u8*)(r1 + 0) = r3; \
+l0_%=: r0 = 0; \
+ exit; \
+" ::: __clobber_all);
+}
+
+SEC("cgroup/skb")
+__description("variable-offset stack read partial spill with misc data")
+__failure
+__msg("invalid variable-offset write to stack R1")
+__naked void stack_read_var_off_partial_spill_with_misc_data(void)
+{
+ asm volatile (" \
+ r0 = 0; \
+ *(u32*)(r10 - 8) = r0; \
+ r2 = *(u32*)(r1 + 0); \
+ r2 &= 7; \
+ r2 -= 8; \
+ r2 += r10; \
+ r3 = *(u8*)(r2 + 0); \
+ r1 = r10; \
+ r1 += -1; \
+ r1 += r3; \
+ *(u8*)(r1 + 0) = 0; \
+ r0 = 0; \
+ exit; \
+" ::: __clobber_all);
+}
+
+SEC("cgroup/skb")
__description("variable-offset stack read, uninitialized")
__success
__failure_unpriv __msg_unpriv("R2 variable stack access prohibited for !root")
@@ -88,7 +198,8 @@ __success
/* Check that the maximum stack depth is correctly maintained according to the
* maximum possible variable offset.
*/
-__log_level(4) __msg("stack depth 16")
+__log_level(4)
+__msg("subprog 0 (stack_write_priv_vs_unpriv) main {{.*}} stack 16")
__failure_unpriv
/* Variable stack access is rejected for unprivileged.
*/
@@ -128,7 +239,8 @@ __success
/* Check that the maximum stack depth is correctly maintained according to the
* maximum possible variable offset.
*/
-__log_level(4) __msg("stack depth 16")
+__log_level(4)
+__msg("subprog 0 (stack_write_followed_by_read) main {{.*}} stack 16")
__failure_unpriv
__msg_unpriv("R2 variable stack access prohibited for !root")
__retval(0)
diff --git a/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c b/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
index 4b392c6c8fc4..8f0c45421f89 100644
--- a/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
+++ b/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
@@ -13,7 +13,7 @@
static char buf[PATH_MAX];
SEC("lsm.s/file_open")
-__failure __msg("Possibly NULL pointer passed to trusted arg0")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
int BPF_PROG(get_task_exe_file_kfunc_null)
{
struct file *acquired;
@@ -28,7 +28,7 @@ int BPF_PROG(get_task_exe_file_kfunc_null)
}
SEC("lsm.s/inode_getxattr")
-__failure __msg("arg#0 pointer type STRUCT task_struct must point to scalar, or struct with scalar")
+__failure __msg("R1 is fp expected STRUCT task_struct")
int BPF_PROG(get_task_exe_file_kfunc_fp)
{
u64 x;
@@ -80,7 +80,7 @@ int BPF_PROG(get_task_exe_file_kfunc_unreleased)
}
SEC("lsm.s/file_open")
-__failure __msg("release kernel function bpf_put_file expects")
+__failure __msg("release kfunc bpf_put_file expects referenced PTR_TO_BTF_ID passed to R1")
int BPF_PROG(put_file_kfunc_unacquired, struct file *file)
{
/* Can't release an unacquired pointer. */
@@ -89,7 +89,7 @@ int BPF_PROG(put_file_kfunc_unacquired, struct file *file)
}
SEC("lsm.s/file_open")
-__failure __msg("Possibly NULL pointer passed to trusted arg0")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
int BPF_PROG(path_d_path_kfunc_null)
{
/* Can't pass NULL value to bpf_path_d_path() kfunc. */
@@ -98,7 +98,7 @@ int BPF_PROG(path_d_path_kfunc_null)
}
SEC("lsm.s/task_alloc")
-__failure __msg("R1 must be referenced or trusted")
+__failure __msg("dereference of modified untrusted_ptr_")
int BPF_PROG(path_d_path_kfunc_untrusted_from_argument, struct task_struct *task)
{
struct path *root;
@@ -112,7 +112,7 @@ int BPF_PROG(path_d_path_kfunc_untrusted_from_argument, struct task_struct *task
}
SEC("lsm.s/file_open")
-__failure __msg("R1 must be referenced or trusted")
+__failure __msg("dereference of modified untrusted_ptr_")
int BPF_PROG(path_d_path_kfunc_untrusted_from_current)
{
struct path *pwd;
@@ -128,7 +128,7 @@ int BPF_PROG(path_d_path_kfunc_untrusted_from_current)
}
SEC("lsm.s/file_open")
-__failure __msg("kernel function bpf_path_d_path args#0 expected pointer to STRUCT path but R1 has a pointer to STRUCT file")
+__failure __msg("kernel function bpf_path_d_path R1 expected pointer to STRUCT path but R1 has a pointer to STRUCT file")
int BPF_PROG(path_d_path_kfunc_type_mismatch, struct file *file)
{
bpf_path_d_path((struct path *)&file->f_task_work, buf, sizeof(buf));
diff --git a/tools/testing/selftests/bpf/progs/verifier_xdp.c b/tools/testing/selftests/bpf/progs/verifier_xdp.c
index 50768ed179b3..7dc9226aeb34 100644
--- a/tools/testing/selftests/bpf/progs/verifier_xdp.c
+++ b/tools/testing/selftests/bpf/progs/verifier_xdp.c
@@ -5,6 +5,14 @@
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, int);
+ __type(value, __u64);
+ __uint(map_flags, BPF_F_RDONLY_PROG);
+} map_array_ro SEC(".maps");
+
SEC("xdp")
__description("XDP, using ifindex from netdev")
__success __retval(1)
@@ -21,4 +29,31 @@ l0_%=: exit; \
: __clobber_all);
}
+SEC("xdp")
+__description("XDP, using xdp_store_bytes from RO map")
+__success __retval(0)
+__naked void xdp_store_bytes_from_ro_map(void)
+{
+ asm volatile (" \
+ r6 = r1; \
+ r1 = 0; \
+ *(u64*)(r10 - 8) = r1; \
+ r2 = r10; \
+ r2 += -8; \
+ r1 = %[map_array_ro] ll; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == 0 goto l0_%=; \
+ r1 = r6; \
+ r2 = 0; \
+ r3 = r0; \
+ r4 = 8; \
+ call %[bpf_xdp_store_bytes]; \
+l0_%=: exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm(bpf_xdp_store_bytes),
+ __imm_addr(map_array_ro)
+ : __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c
index df2dfd1b15d1..9866bc154194 100644
--- a/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c
+++ b/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c
@@ -69,7 +69,7 @@ l0_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_data' > pkt_end, bad access 1")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void pkt_end_bad_access_1_1(void)
{
@@ -131,7 +131,7 @@ l0_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_data' > pkt_end, corner case -1, bad access")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void corner_case_1_bad_access_1(void)
{
@@ -173,7 +173,7 @@ l1_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_end > pkt_data', corner case -1, bad access")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void corner_case_1_bad_access_2(void)
{
@@ -279,7 +279,7 @@ l1_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_data' < pkt_end, corner case -1, bad access")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void corner_case_1_bad_access_3(void)
{
@@ -384,7 +384,7 @@ l0_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_end < pkt_data', bad access 1")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void pkt_data_bad_access_1_1(void)
{
@@ -446,7 +446,7 @@ l0_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_end < pkt_data', corner case -1, bad access")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void corner_case_1_bad_access_4(void)
{
@@ -487,7 +487,7 @@ l0_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_data' >= pkt_end, corner case -1, bad access")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void corner_case_1_bad_access_5(void)
{
@@ -590,7 +590,7 @@ l1_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_end >= pkt_data', bad access 1")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void pkt_data_bad_access_1_2(void)
{
@@ -654,7 +654,7 @@ l1_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_end >= pkt_data', corner case -1, bad access")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void corner_case_1_bad_access_6(void)
{
@@ -697,7 +697,7 @@ l1_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_data' <= pkt_end, bad access 1")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void pkt_end_bad_access_1_2(void)
{
@@ -761,7 +761,7 @@ l1_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_data' <= pkt_end, corner case -1, bad access")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void corner_case_1_bad_access_7(void)
{
@@ -803,7 +803,7 @@ l0_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_end <= pkt_data', corner case -1, bad access")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void corner_case_1_bad_access_8(void)
{
@@ -905,7 +905,7 @@ l0_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_meta' > pkt_data, bad access 1")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void pkt_data_bad_access_1_3(void)
{
@@ -926,7 +926,7 @@ l0_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_meta' > pkt_data, bad access 2")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void pkt_data_bad_access_2_5(void)
{
@@ -967,7 +967,7 @@ l0_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_meta' > pkt_data, corner case -1, bad access")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void corner_case_1_bad_access_9(void)
{
@@ -1009,7 +1009,7 @@ l1_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_data > pkt_meta', corner case -1, bad access")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void corner_case_1_bad_access_10(void)
{
@@ -1031,7 +1031,7 @@ l1_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_data > pkt_meta', bad access 2")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void pkt_meta_bad_access_2_1(void)
{
@@ -1115,7 +1115,7 @@ l1_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_meta' < pkt_data, corner case -1, bad access")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void corner_case_1_bad_access_11(void)
{
@@ -1137,7 +1137,7 @@ l1_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_meta' < pkt_data, bad access 2")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void pkt_data_bad_access_2_6(void)
{
@@ -1220,7 +1220,7 @@ l0_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_data < pkt_meta', bad access 1")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void pkt_meta_bad_access_1_1(void)
{
@@ -1241,7 +1241,7 @@ l0_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_data < pkt_meta', bad access 2")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void pkt_meta_bad_access_2_2(void)
{
@@ -1282,7 +1282,7 @@ l0_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_data < pkt_meta', corner case -1, bad access")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void corner_case_1_bad_access_12(void)
{
@@ -1323,7 +1323,7 @@ l0_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_meta' >= pkt_data, corner case -1, bad access")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void corner_case_1_bad_access_13(void)
{
@@ -1344,7 +1344,7 @@ l0_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_meta' >= pkt_data, bad access 2")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void pkt_data_bad_access_2_7(void)
{
@@ -1426,7 +1426,7 @@ l1_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_data >= pkt_meta', bad access 1")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void pkt_meta_bad_access_1_2(void)
{
@@ -1448,7 +1448,7 @@ l1_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_data >= pkt_meta', bad access 2")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void pkt_meta_bad_access_2_3(void)
{
@@ -1490,7 +1490,7 @@ l1_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_data >= pkt_meta', corner case -1, bad access")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void corner_case_1_bad_access_14(void)
{
@@ -1533,7 +1533,7 @@ l1_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_meta' <= pkt_data, bad access 1")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void pkt_data_bad_access_1_4(void)
{
@@ -1555,7 +1555,7 @@ l1_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_meta' <= pkt_data, bad access 2")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void pkt_data_bad_access_2_8(void)
{
@@ -1597,7 +1597,7 @@ l1_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_meta' <= pkt_data, corner case -1, bad access")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void corner_case_1_bad_access_15(void)
{
@@ -1639,7 +1639,7 @@ l0_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_data <= pkt_meta', corner case -1, bad access")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void corner_case_1_bad_access_16(void)
{
@@ -1660,7 +1660,7 @@ l0_%=: r0 = 0; \
SEC("xdp")
__description("XDP pkt read, pkt_data <= pkt_meta', bad access 2")
-__failure __msg("R1 offset is outside of the packet")
+__failure __msg("R1 {{min|max}} value is outside of the allowed memory range")
__flag(BPF_F_ANY_ALIGNMENT)
__naked void pkt_meta_bad_access_2_4(void)
{
@@ -1719,4 +1719,39 @@ l0_%=: r0 = 0; \
: __clobber_all);
}
+SEC("xdp")
+__description("XDP pkt regsafe preserves packet pointer class displacement")
+__failure __msg("R2 min value is outside of the allowed memory range")
+__flag(BPF_F_ANY_ALIGNMENT) __flag(BPF_F_TEST_STATE_FREQ)
+__naked void pkt_regsafe_class_displacement(void)
+{
+ asm volatile (" \
+ r8 = *(u32 *)(r1 + %[xdp_md_data_end]); \
+ r9 = *(u32 *)(r1 + %[xdp_md_data]); \
+ r4 = *(u32 *)(r1 + %[xdp_md_rx_queue_index]); \
+ r4 &= 15; \
+ r0 = *(u32 *)(r1 + %[xdp_md_ingress_ifindex]); \
+ if r0 != 0 goto l0_%=; \
+ r2 = r9; \
+ r2 += r4; \
+ r3 = r2; \
+ r3 += 8; \
+ goto l1_%=; \
+l0_%=: r4 &= 3; \
+ r4 += 8; \
+ r2 = r9; \
+ r2 += r4; \
+ r3 = r2; \
+l1_%=: if r3 > r8 goto l2_%=; \
+ r0 = *(u64 *)(r2 + 0); \
+l2_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm_const(xdp_md_data, offsetof(struct xdp_md, data)),
+ __imm_const(xdp_md_data_end, offsetof(struct xdp_md, data_end)),
+ __imm_const(xdp_md_rx_queue_index, offsetof(struct xdp_md, rx_queue_index)),
+ __imm_const(xdp_md_ingress_ifindex, offsetof(struct xdp_md, ingress_ifindex))
+ : __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
new file mode 100644
index 000000000000..572017fe28fb
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_zext.c
@@ -0,0 +1,418 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "../../../include/linux/filter.h"
+#include <bpf_arena_common.h>
+#include <bpf/bpf_core_read.h>
+#include "bpf_misc.h"
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARENA);
+ __uint(map_flags, BPF_F_MMAPABLE | BPF_F_NO_USER_CONV);
+ __uint(max_entries, 1);
+} arena SEC(".maps");
+
+extern long bpf_kfunc_call_test4(signed char a, short b, int c, long d) __ksym;
+
+/* to retain debug info for BTF generation */
+void __kfunc_btf_root(void)
+{
+ bpf_kfunc_call_test4(0, 0, 0, 0);
+ bpf_arena_alloc_pages(0, 0, 0, 0, 0);
+ bpf_rdonly_cast(0, 0);
+}
+
+SEC("socket")
+__flag(BPF_F_TEST_STATE_FREQ)
+__flag(BPF_F_TEST_RND_HI32)
+__success __retval(0)
+__naked void zext_lost_across_checkpoint(void)
+{
+ asm volatile (" \
+ call %[bpf_ktime_get_ns]; \
+ r8 = r0; \
+ r6 = 0xdeadbeefcafebabe ll; /* inject some value for r6's upper half */ \
+ if r8 != 0 goto 1f; /* fall-through cached first, branch pruned */ \
+ r6 = 32; /* full 64-bit def */ \
+ goto 2f; \
+1: w6 = 32; /* 32-bit def, zext mark lost */ \
+2: r0 = r6; /* buggy verifier believed upper 32 bits are 0 */ \
+ /* and thus did not zero extended w6 = 32. */ \
+ r0 >>= 32; \
+ exit; \
+" :
+ : __imm(bpf_ktime_get_ns)
+ : __clobber_all);
+}
+
+/* 32-bit ALU result read as 64-bit -> zext */
+SEC("socket")
+__success __log_level(2)
+__msg("w1 = w0{{ +}}; zext")
+__naked void zext_alu32_hi_used(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ r0 = r1; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* 32-bit ALU result read only as 32-bit -> no zext */
+SEC("socket")
+__success __log_level(2)
+__not_msg("; zext")
+__naked void no_zext_alu32_hi_unused(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; /* MOV */ \
+ w2 = w1; \
+ w2 += w1; /* ALU32, BPF_X */ \
+ w2 += 1; /* ALU32, BPF_K */ \
+ w2 = w2; /* keep w2 alive for previous instruction */ \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* 64-bit definition is never zero extended */
+SEC("socket")
+__success __log_level(2)
+__not_msg("r1 = r0{{.*}}; zext")
+__naked void no_zext_mov64(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ r0 = r1; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* Narrow load result read as 64-bit -> zext */
+SEC("socket")
+__success __log_level(2)
+__msg("r1 = *(u32 *)(r10 -8){{ +}}; zext")
+__naked void zext_narrow_load_hi_used(void)
+{
+ asm volatile (" \
+ r0 = 0; \
+ *(u64 *)(r10 - 8) = r0; \
+ r1 = *(u32 *)(r10 - 8); \
+ r0 = r1; \
+ exit; \
+" ::: __clobber_all);
+}
+
+/* 32-bit atomic fetch result read as 64-bit -> zext */
+SEC("socket")
+__success __log_level(2)
+__msg("r1 = atomic_fetch_add((u32 *)(r10 -8), r1){{ +}}; zext")
+__naked void zext_atomic_fetch32_hi_used(void)
+{
+ asm volatile (" \
+ r1 = 0; \
+ *(u64 *)(r10 - 8) = r1; \
+ w1 = 1; \
+ .8byte %[fetch_add32]; \
+ r0 = r1; \
+ exit; \
+" :
+ : __imm_insn(fetch_add32,
+ BPF_ATOMIC_OP(BPF_W, BPF_ADD | BPF_FETCH, BPF_REG_10, BPF_REG_1, -8))
+ : __clobber_all);
+}
+
+/* 32-bit atomic cmpxchg result (r0) read as 64-bit -> zext */
+SEC("socket")
+__success __log_level(2)
+__msg("r0 = atomic_cmpxchg((u32 *)(r10 -8), r0, r1){{ +}}; zext")
+__naked void zext_cmpxchg32_hi_used(void)
+{
+ asm volatile (" \
+ r1 = 0; \
+ *(u64 *)(r10 - 8) = r1; \
+ w0 = 0; \
+ w1 = 1; \
+ .8byte %[cmpxchg32]; \
+ r2 = r0; \
+ r0 = r2; \
+ exit; \
+" :
+ : __imm_insn(cmpxchg32,
+ BPF_ATOMIC_OP(BPF_W, BPF_CMPXCHG, BPF_REG_10, BPF_REG_1, -8))
+ : __clobber_all);
+}
+
+/* 32-bit def before a branch, upper half used on one branch -> zext */
+SEC("socket")
+__success __log_level(2)
+__msg("w6 = 32{{ +}}; zext")
+__naked void zext_cfg_hi_used_one_branch(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w6 = 32; \
+ if r0 == 0 goto 1f; \
+ r0 = r6; \
+ exit; \
+1: r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* r1's upper half is dead, so 'w1 = 1' must NOT be marked for zero extension. */
+SEC("socket")
+__success __log_level(2)
+__not_msg("w1 = 1{{.*}}; zext")
+__naked void no_zext_other_reg_hi_used(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r6 = r0; \
+ r6 <<= 32; \
+ w1 = 1; \
+ r0 = r6; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* LD_ABS defines r0; when r0 is read as 64-bit it must be zero extended */
+SEC("socket")
+__success __log_level(2)
+__msg("r0 = *(u8 *)skb[0]{{.*}}; zext")
+__naked void zext_ld_abs_hi_used(void)
+{
+ asm volatile (" \
+ r6 = r1; \
+ r0 = *(u8 *)skb[0]; \
+ r7 = r0; \
+ r0 = r7; \
+ exit; \
+" ::: __clobber_all);
+}
+
+/* Helper parameters are read as 64-bit (call_use_mask() fallback) */
+SEC("socket")
+__success __log_level(2)
+__msg("w2 = 1{{ +}}; zext")
+__naked void helper_param_read_as_64bit(void)
+{
+ asm volatile (" \
+ r1 = r10; \
+ r1 += -8; \
+ w2 = 1; \
+ call %[bpf_trace_printk]; \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_trace_printk)
+ : __clobber_all);
+}
+
+static __used __naked int subprog_reads_arg_as_64bit(void)
+{
+ asm volatile (" \
+ r0 = r1; \
+ exit; \
+" ::: __clobber_all);
+}
+
+/* subprogram parameters are conservatively read as 64-bit */
+SEC("socket")
+__success __log_level(2)
+__msg("w1 = w0{{ +}}; zext")
+__naked void subprog_param_read_as_64bit(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ call subprog_reads_arg_as_64bit; \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/* kfunc parameters are zero extended */
+SEC("tc")
+__success __log_level(2)
+__msg("w1 = 1{{ +}}; zext")
+__msg("w2 = 1{{ +}}; zext")
+__msg("w3 = 1{{ +}}; zext")
+__msg("w4 = 1{{ +}}; zext")
+__naked void kfunc_param_read_per_btf(void)
+{
+ asm volatile (" \
+ w1 = 1; \
+ w2 = 1; \
+ w3 = 1; \
+ w4 = 1; \
+ call bpf_kfunc_call_test4; \
+ r0 = 0; \
+ exit; \
+" ::: __clobber_all);
+}
+
+SEC("socket")
+__success __log_level(2)
+__not_msg("; zext")
+__naked void alu32_and_32bit_conditional(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ if w1 > 42 goto 1f; /* BPF_K */ \
+ w2 = 28; \
+ if w2 > w1 goto 1f; /* BPF_X */ \
+ r0 = 0; \
+1: exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__success __log_level(2)
+__msg("w1 = w0{{ +}}; zext")
+__naked void alu32_and_64bit_conditional(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w1 = w0; \
+ if r1 > 42 goto 1f; /* BPF_K */ \
+ r2 = 28; \
+ if r2 > r1 goto 1f; /* BPF_X */ \
+ r0 = 0; \
+1: exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("socket")
+__success __log_level(2)
+__not_msg("; zext")
+__naked void alu64_and_conditionals(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ if w1 > 42 goto 1f; /* BPF_K */ \
+ if r1 > 42 goto 1f; /* BPF_K */ \
+ r2 = 28; \
+ if w2 > w1 goto 1f; /* BPF_X */ \
+ if r2 > r1 goto 1f; /* BPF_X */ \
+ r0 = 0; \
+1: exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+#ifdef __BPF_FEATURE_ADDR_SPACE_CAST
+
+SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
+__arch_s390x
+__xlated("7: w1 = w0")
+__xlated("8: w1 = w1")
+__xlated("9: w1 += 8")
+__xlated("10: w1 = w1")
+__xlated("11: w2 = w1")
+__xlated("12: w2 = w2")
+__xlated("13: *(u64 *)(r1 +0) = r2")
+__naked void arena_ptr(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = 0; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r1 = addr_space_cast(r0, 0, 1); /* needs zext */ \
+ r1 += 8; /* needs zext */ \
+ r2 = addr_space_cast(r1, 1, 0); /* needs zext because of BPF_F_NO_USER_CONV */ \
+ *(u64 *)(r1 +0) = r2; \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_arena_alloc_pages),
+ __imm_addr(arena)
+ : __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. */
+SEC("socket")
+__success __log_level(2)
+__arch_s390x
+__xlated("3: r1 = *(u64 *)(r0 +0)")
+__xlated("4: r2 = *(u32 *)(r0 +0)")
+__xlated("5: w2 = w2")
+__xlated("6: r3 = *(u16 *)(r0 +0)")
+__xlated("7: w3 = w3")
+__xlated("8: r4 = *(u8 *)(r0 +0)")
+__xlated("9: w4 = w4")
+__naked void probe_mem(void)
+{
+ asm volatile (" \
+ r1 = 0; \
+ r2 = 0; \
+ call %[bpf_rdonly_cast]; \
+ r1 = *(u64 *)(r0 + 0); /* BPF_PROBE_MEM */ \
+ r2 = *(u32 *)(r0 + 0); /* BPF_PROBE_MEM */ \
+ r3 = *(u16 *)(r0 + 0); /* BPF_PROBE_MEM */ \
+ r4 = *(u8 *)(r0 + 0); /* BPF_PROBE_MEM */ \
+ r0 = r1; /* make the registers used */ \
+ r0 += r2; \
+ r0 += r3; \
+ r0 += r4; \
+1: exit; \
+" :
+ : __imm(bpf_rdonly_cast)
+ : __clobber_all);
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/veristat_bar.c b/tools/testing/selftests/bpf/progs/veristat_bar.c
new file mode 100644
index 000000000000..83d2a2a1dfc9
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/veristat_bar.c
@@ -0,0 +1,3 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include "veristat_foo.c"
diff --git a/tools/testing/selftests/bpf/progs/veristat_foo.c b/tools/testing/selftests/bpf/progs/veristat_foo.c
new file mode 100644
index 000000000000..bd24b97664b4
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/veristat_foo.c
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+/*
+ * Programs below exist only to exercise veristat's -f name filters,
+ * their bodies are irrelevant, only the names matter.
+ * This file is also included by veristat_bar.c, so that the same set of
+ * program names is available in two differently named object files.
+ */
+
+SEC("socket")
+int foo(void *ctx)
+{
+ return 0;
+}
+
+SEC("socket")
+int bar(void *ctx)
+{
+ return 0;
+}
+
+SEC("socket")
+int buz(void *ctx)
+{
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/wakeup_source.h b/tools/testing/selftests/bpf/progs/wakeup_source.h
new file mode 100644
index 000000000000..cd74de92c82f
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/wakeup_source.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright 2026 Google LLC */
+
+#ifndef __WAKEUP_SOURCE_H__
+#define __WAKEUP_SOURCE_H__
+
+#define WAKEUP_NAME_LEN 128
+
+struct wakeup_event_t {
+ unsigned long active_count;
+ long long active_time_ns;
+ unsigned long event_count;
+ unsigned long expire_count;
+ long long last_time_ns;
+ long long max_time_ns;
+ long long prevent_sleep_time_ns;
+ long long total_time_ns;
+ unsigned long wakeup_count;
+ char name[WAKEUP_NAME_LEN];
+};
+
+#endif /* __WAKEUP_SOURCE_H__ */
diff --git a/tools/testing/selftests/bpf/progs/wakeup_source_fail.c b/tools/testing/selftests/bpf/progs/wakeup_source_fail.c
new file mode 100644
index 000000000000..d4d0f1610853
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/wakeup_source_fail.c
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright 2026 Google LLC */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+struct bpf_ws_lock;
+
+struct bpf_ws_lock *bpf_wakeup_sources_read_lock(void) __ksym;
+void bpf_wakeup_sources_read_unlock(struct bpf_ws_lock *lock) __ksym;
+void *bpf_wakeup_sources_get_head(void) __ksym;
+
+SEC("syscall")
+__failure __msg("BPF_EXIT instruction in main prog would lead to reference leak")
+int wakeup_source_lock_no_unlock(void *ctx)
+{
+ struct bpf_ws_lock *lock;
+
+ lock = bpf_wakeup_sources_read_lock();
+ if (!lock)
+ return 0;
+
+ return 0;
+}
+
+SEC("syscall")
+__failure __msg("access beyond struct")
+int wakeup_source_access_lock_fields(void *ctx)
+{
+ struct bpf_ws_lock *lock;
+ int val;
+
+ lock = bpf_wakeup_sources_read_lock();
+ if (!lock)
+ return 0;
+
+ val = *(int *)lock;
+
+ bpf_wakeup_sources_read_unlock(lock);
+ return val;
+}
+
+SEC("syscall")
+__failure __msg("release kfunc bpf_wakeup_sources_read_unlock expects referenced PTR_TO_BTF_ID passed to R1")
+int wakeup_source_unlock_no_lock(void *ctx)
+{
+ struct bpf_ws_lock *lock = (void *)0x1;
+
+ bpf_wakeup_sources_read_unlock(lock);
+
+ return 0;
+}
+
+SEC("syscall")
+__failure __msg("Possibly NULL pointer passed to trusted")
+int wakeup_source_unlock_null(void *ctx)
+{
+ bpf_wakeup_sources_read_unlock(NULL);
+
+ return 0;
+}
+
+SEC("syscall")
+__failure __msg("R0 invalid mem access 'scalar'")
+int wakeup_source_unsafe_dereference(void *ctx)
+{
+ struct list_head *head = bpf_wakeup_sources_get_head();
+
+ if (head->next)
+ return 1;
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/wq_failures.c b/tools/testing/selftests/bpf/progs/wq_failures.c
index d06f6d40594a..32dc8827e128 100644
--- a/tools/testing/selftests/bpf/progs/wq_failures.c
+++ b/tools/testing/selftests/bpf/progs/wq_failures.c
@@ -97,8 +97,8 @@ __failure
/* check that the first argument of bpf_wq_set_callback()
* is a correct bpf_wq pointer.
*/
-__msg(": (85) call bpf_wq_set_callback_impl#") /* anchor message */
-__msg("arg#0 doesn't point to a map value")
+__msg(": (85) call bpf_wq_set_callback#") /* anchor message */
+__msg("R1 doesn't point to a map value")
long test_wrong_wq_pointer(void *ctx)
{
int key = 0;
@@ -123,7 +123,7 @@ __failure
/* check that the first argument of bpf_wq_set_callback()
* is a correct bpf_wq pointer.
*/
-__msg(": (85) call bpf_wq_set_callback_impl#") /* anchor message */
+__msg(": (85) call bpf_wq_set_callback#") /* anchor message */
__msg("off 1 doesn't point to 'struct bpf_wq' that is at 0")
long test_wrong_wq_pointer_offset(void *ctx)
{
diff --git a/tools/testing/selftests/bpf/progs/xdp_dummy.c b/tools/testing/selftests/bpf/progs/xdp_dummy.c
index d988b2e0cee8..5f1e0771021d 100644
--- a/tools/testing/selftests/bpf/progs/xdp_dummy.c
+++ b/tools/testing/selftests/bpf/progs/xdp_dummy.c
@@ -10,4 +10,10 @@ int xdp_dummy_prog(struct xdp_md *ctx)
return XDP_PASS;
}
+SEC("xdp")
+int __x64_sys_nop(struct xdp_md *ctx)
+{
+ return XDP_PASS;
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/xdp_flowtable.c b/tools/testing/selftests/bpf/progs/xdp_flowtable.c
index 7fdc7b23ee74..e67daa02749d 100644
--- a/tools/testing/selftests/bpf/progs/xdp_flowtable.c
+++ b/tools/testing/selftests/bpf/progs/xdp_flowtable.c
@@ -15,7 +15,10 @@ struct bpf_flowtable_opts___local {
s32 error;
};
-struct flow_offload_tuple_rhash *
+struct flow_offload_tuple_rhash___local {
+};
+
+struct flow_offload_tuple_rhash___local *
bpf_xdp_flow_lookup(struct xdp_md *, struct bpf_fib_lookup *,
struct bpf_flowtable_opts___local *, u32) __ksym;
@@ -67,7 +70,7 @@ int xdp_flowtable_do_lookup(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
struct bpf_flowtable_opts___local opts = {};
- struct flow_offload_tuple_rhash *tuplehash;
+ struct flow_offload_tuple_rhash___local *tuplehash;
struct bpf_fib_lookup tuple = {
.ifindex = ctx->ingress_ifindex,
};
diff --git a/tools/testing/selftests/bpf/progs/xdp_lb_bench.c b/tools/testing/selftests/bpf/progs/xdp_lb_bench.c
new file mode 100644
index 000000000000..13777b3dcac8
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/xdp_lb_bench.c
@@ -0,0 +1,647 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <linux/bpf.h>
+#include <linux/if_ether.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/in.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+#include "bpf_compiler.h"
+#include "xdp_lb_bench_common.h"
+#include "bench_bpf_timing.bpf.h"
+
+#ifndef IPPROTO_FRAGMENT
+#define IPPROTO_FRAGMENT 44
+#endif
+
+/* jhash helpers */
+
+static inline __u32 rol32(__u32 word, unsigned int shift)
+{
+ return (word << shift) | (word >> ((-shift) & 31));
+}
+
+#define __jhash_mix(a, b, c) \
+{ \
+ a -= c; a ^= rol32(c, 4); c += b; \
+ b -= a; b ^= rol32(a, 6); a += c; \
+ c -= b; c ^= rol32(b, 8); b += a; \
+ a -= c; a ^= rol32(c, 16); c += b; \
+ b -= a; b ^= rol32(a, 19); a += c; \
+ c -= b; c ^= rol32(b, 4); b += a; \
+}
+
+#define __jhash_final(a, b, c) \
+{ \
+ c ^= b; c -= rol32(b, 14); \
+ a ^= c; a -= rol32(c, 11); \
+ b ^= a; b -= rol32(a, 25); \
+ c ^= b; c -= rol32(b, 16); \
+ a ^= c; a -= rol32(c, 4); \
+ b ^= a; b -= rol32(a, 14); \
+ c ^= b; c -= rol32(b, 24); \
+}
+
+#define JHASH_INITVAL 0xdeadbeef
+
+static inline __u32 __jhash_nwords(__u32 a, __u32 b, __u32 c, __u32 initval)
+{
+ a += initval;
+ b += initval;
+ c += initval;
+ __jhash_final(a, b, c);
+ return c;
+}
+
+static inline __u32 jhash_2words(__u32 a, __u32 b, __u32 initval)
+{
+ return __jhash_nwords(a, b, 0, initval + JHASH_INITVAL + (2 << 2));
+}
+
+static inline __u32 jhash2_4words(const __u32 *k, __u32 initval)
+{
+ __u32 a, b, c;
+
+ a = b = c = JHASH_INITVAL + (4 << 2) + initval;
+
+ a += k[0]; b += k[1]; c += k[2];
+ __jhash_mix(a, b, c);
+
+ a += k[3];
+ __jhash_final(a, b, c);
+
+ return c;
+}
+
+static __always_inline void ipv4_csum(struct iphdr *iph)
+{
+ __u16 *next_iph = (__u16 *)iph;
+ __u32 csum = 0;
+ int i;
+
+ __pragma_loop_unroll_full
+ for (i = 0; i < (int)(sizeof(*iph) >> 1); i++)
+ csum += *next_iph++;
+
+ csum = (csum & 0xffff) + (csum >> 16);
+ csum = (csum & 0xffff) + (csum >> 16);
+ iph->check = ~csum;
+}
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(max_entries, 64);
+ __type(key, struct vip_definition);
+ __type(value, struct vip_meta);
+} vip_map SEC(".maps");
+
+struct lru_inner_map {
+ __uint(type, BPF_MAP_TYPE_LRU_HASH);
+ __type(key, struct flow_key);
+ __type(value, struct real_pos_lru);
+ __uint(max_entries, DEFAULT_LRU_SIZE);
+} lru_inner SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
+ __type(key, __u32);
+ __type(value, __u32);
+ __uint(max_entries, BENCH_NR_CPUS);
+ __array(values, struct lru_inner_map);
+} lru_mapping SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, CH_RINGS_SIZE);
+ __type(key, __u32);
+ __type(value, __u32);
+} ch_rings SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, MAX_REALS);
+ __type(key, __u32);
+ __type(value, struct real_definition);
+} reals SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
+ __uint(max_entries, STATS_SIZE);
+ __type(key, __u32);
+ __type(value, struct lb_stats);
+} stats SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
+ __uint(max_entries, MAX_REALS);
+ __type(key, __u32);
+ __type(value, struct lb_stats);
+} reals_stats SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, __u32);
+ __type(value, struct ctl_value);
+} ctl_array SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, __u32);
+ __type(value, struct vip_definition);
+} vip_miss_stats SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
+ __uint(max_entries, MAX_REALS);
+ __type(key, __u32);
+ __type(value, __u32);
+} lru_miss_stats SEC(".maps");
+
+volatile __u32 flow_mask;
+volatile __u32 cold_lru;
+__u32 batch_gen;
+
+/*
+ * old_eth MUST be read BEFORE writing the outer header because
+ * bpf_xdp_adjust_head makes them overlap.
+ */
+static __always_inline int encap_v4(struct xdp_md *xdp, __be32 saddr, __be32 daddr,
+ __u16 payload_len, const __u8 *dst_mac)
+{
+ struct ethhdr *new_eth, *old_eth;
+ void *data, *data_end;
+ struct iphdr *iph;
+
+ if (bpf_xdp_adjust_head(xdp, -(int)sizeof(struct iphdr)))
+ return -1;
+
+ data = (void *)(long)xdp->data;
+ data_end = (void *)(long)xdp->data_end;
+
+ new_eth = data;
+ iph = data + sizeof(struct ethhdr);
+ old_eth = data + sizeof(struct iphdr);
+
+ if (new_eth + 1 > data_end || old_eth + 1 > data_end || iph + 1 > data_end)
+ return -1;
+
+ __builtin_memcpy(new_eth->h_source, old_eth->h_dest, sizeof(new_eth->h_source));
+ __builtin_memcpy(new_eth->h_dest, dst_mac, sizeof(new_eth->h_dest));
+ new_eth->h_proto = bpf_htons(ETH_P_IP);
+
+ __builtin_memset(iph, 0, sizeof(*iph));
+ iph->version = 4;
+ iph->ihl = sizeof(*iph) >> 2;
+ iph->protocol = IPPROTO_IPIP;
+ iph->tot_len = bpf_htons(payload_len + sizeof(*iph));
+ iph->ttl = 64;
+ iph->saddr = saddr;
+ iph->daddr = daddr;
+ ipv4_csum(iph);
+
+ return 0;
+}
+
+static __always_inline int encap_v6(struct xdp_md *xdp, const __be32 saddr[4],
+ const __be32 daddr[4], __u8 nexthdr, __u16 payload_len,
+ const __u8 *dst_mac)
+{
+ struct ethhdr *new_eth, *old_eth;
+ void *data, *data_end;
+ struct ipv6hdr *ip6h;
+
+ if (bpf_xdp_adjust_head(xdp, -(int)sizeof(struct ipv6hdr)))
+ return -1;
+
+ data = (void *)(long)xdp->data;
+ data_end = (void *)(long)xdp->data_end;
+
+ new_eth = data;
+ ip6h = data + sizeof(struct ethhdr);
+ old_eth = data + sizeof(struct ipv6hdr);
+
+ if (new_eth + 1 > data_end || old_eth + 1 > data_end || ip6h + 1 > data_end)
+ return -1;
+
+ __builtin_memcpy(new_eth->h_source, old_eth->h_dest, sizeof(new_eth->h_source));
+ __builtin_memcpy(new_eth->h_dest, dst_mac, sizeof(new_eth->h_dest));
+ new_eth->h_proto = bpf_htons(ETH_P_IPV6);
+
+ __builtin_memset(ip6h, 0, sizeof(*ip6h));
+ ip6h->version = 6;
+ ip6h->nexthdr = nexthdr;
+ ip6h->payload_len = bpf_htons(payload_len);
+ ip6h->hop_limit = 64;
+ __builtin_memcpy(&ip6h->saddr, saddr, sizeof(ip6h->saddr));
+ __builtin_memcpy(&ip6h->daddr, daddr, sizeof(ip6h->daddr));
+
+ return 0;
+}
+
+static __always_inline void update_stats(void *map, __u32 key, __u16 bytes)
+{
+ struct lb_stats *st = bpf_map_lookup_elem(map, &key);
+
+ if (st) {
+ st->v1 += 1;
+ st->v2 += bytes;
+ }
+}
+
+static __always_inline void count_action(int action)
+{
+ struct lb_stats *st;
+ __u32 key;
+
+ if (action == XDP_TX)
+ key = STATS_XDP_TX;
+ else if (action == XDP_PASS)
+ key = STATS_XDP_PASS;
+ else
+ key = STATS_XDP_DROP;
+
+ st = bpf_map_lookup_elem(&stats, &key);
+ if (st)
+ st->v1 += 1;
+}
+
+static __always_inline bool is_under_flood(void)
+{
+ __u32 key = STATS_NEW_CONN;
+ struct lb_stats *conn_st = bpf_map_lookup_elem(&stats, &key);
+ __u64 cur_time;
+
+ if (!conn_st)
+ return true;
+
+ cur_time = bpf_ktime_get_ns();
+ if ((cur_time - conn_st->v2) > ONE_SEC) {
+ conn_st->v1 = 1;
+ conn_st->v2 = cur_time;
+ } else {
+ conn_st->v1 += 1;
+ if (conn_st->v1 > MAX_CONN_RATE)
+ return true;
+ }
+ return false;
+}
+
+static __always_inline struct real_definition *connection_table_lookup(void *lru_map,
+ struct flow_key *flow,
+ __u32 *out_pos)
+{
+ struct real_pos_lru *dst_lru;
+ struct real_definition *real;
+ __u32 key;
+
+ dst_lru = bpf_map_lookup_elem(lru_map, flow);
+ if (!dst_lru)
+ return NULL;
+
+ /* UDP connections use atime-based timeout instead of FIN/RST */
+ if (flow->proto == IPPROTO_UDP) {
+ __u64 cur_time = bpf_ktime_get_ns();
+
+ if (cur_time - dst_lru->atime > LRU_UDP_TIMEOUT)
+ return NULL;
+ dst_lru->atime = cur_time;
+ }
+
+ key = dst_lru->pos;
+ *out_pos = key;
+ real = bpf_map_lookup_elem(&reals, &key);
+ return real;
+}
+
+static __always_inline bool get_packet_dst(struct real_definition **real, struct flow_key *flow,
+ struct vip_meta *vip_info, bool is_v6, void *lru_map,
+ bool is_rst, __u32 *out_pos)
+{
+ bool under_flood;
+ __u32 hash, ch_key;
+ __u32 *ch_val;
+ __u32 real_pos;
+
+ under_flood = is_under_flood();
+
+ if (is_v6) {
+ __u32 src_hash = jhash2_4words((__u32 *)flow->srcv6, MAX_VIPS);
+
+ hash = jhash_2words(src_hash, flow->ports, CH_RING_SIZE);
+ } else {
+ hash = jhash_2words(flow->src, flow->ports, CH_RING_SIZE);
+ }
+
+ ch_key = CH_RING_SIZE * vip_info->vip_num + hash % CH_RING_SIZE;
+ ch_val = bpf_map_lookup_elem(&ch_rings, &ch_key);
+ if (!ch_val)
+ return false;
+ real_pos = *ch_val;
+
+ *real = bpf_map_lookup_elem(&reals, &real_pos);
+ if (!(*real))
+ return false;
+
+ if (!(vip_info->flags & F_LRU_BYPASS) && !under_flood && !is_rst) {
+ struct real_pos_lru new_lru = { .pos = real_pos };
+
+ if (flow->proto == IPPROTO_UDP)
+ new_lru.atime = bpf_ktime_get_ns();
+ bpf_map_update_elem(lru_map, flow, &new_lru, BPF_ANY);
+ }
+
+ *out_pos = real_pos;
+ return true;
+}
+
+static __always_inline void update_vip_lru_miss_stats(struct vip_definition *vip, bool is_v6,
+ __u32 real_idx)
+{
+ struct vip_definition *miss_vip;
+ __u32 key = 0;
+ __u32 *cnt;
+
+ miss_vip = bpf_map_lookup_elem(&vip_miss_stats, &key);
+ if (!miss_vip)
+ return;
+
+ if (is_v6) {
+ if (miss_vip->vipv6[0] != vip->vipv6[0] || miss_vip->vipv6[1] != vip->vipv6[1] ||
+ miss_vip->vipv6[2] != vip->vipv6[2] || miss_vip->vipv6[3] != vip->vipv6[3])
+ return;
+ } else {
+ if (miss_vip->vip != vip->vip)
+ return;
+ }
+
+ if (miss_vip->port != vip->port || miss_vip->proto != vip->proto)
+ return;
+
+ cnt = bpf_map_lookup_elem(&lru_miss_stats, &real_idx);
+ if (cnt)
+ *cnt += 1;
+}
+
+static __noinline int process_packet(struct xdp_md *xdp)
+{
+ void *data = (void *)(long)xdp->data;
+ void *data_end = (void *)(long)xdp->data_end;
+ struct ethhdr *eth = data;
+ struct real_definition *dst = NULL;
+ struct vip_definition vip_def = {};
+ struct ctl_value *cval;
+ struct flow_key flow = {};
+ struct vip_meta *vip_info;
+ struct lb_stats *data_stats;
+ struct udphdr *uh;
+ __be32 tnl_src[4];
+ void *lru_map;
+ void *l4;
+ __u16 payload_len;
+ __u32 real_pos = 0, cpu_num, key;
+ __u8 proto;
+ int action = XDP_DROP;
+ bool is_v6, is_syn = false, is_rst = false;
+
+ if (eth + 1 > data_end)
+ goto out;
+
+ if (eth->h_proto == bpf_htons(ETH_P_IPV6)) {
+ is_v6 = true;
+ } else if (eth->h_proto == bpf_htons(ETH_P_IP)) {
+ is_v6 = false;
+ } else {
+ action = XDP_PASS;
+ goto out;
+ }
+
+ if (is_v6) {
+ struct ipv6hdr *ip6h = (void *)(eth + 1);
+
+ if (ip6h + 1 > data_end)
+ goto out;
+ if (ip6h->nexthdr == IPPROTO_FRAGMENT)
+ goto out;
+
+ payload_len = sizeof(struct ipv6hdr) + bpf_ntohs(ip6h->payload_len);
+ proto = ip6h->nexthdr;
+
+ __builtin_memcpy(flow.srcv6, &ip6h->saddr, sizeof(flow.srcv6));
+ __builtin_memcpy(flow.dstv6, &ip6h->daddr, sizeof(flow.dstv6));
+ __builtin_memcpy(vip_def.vipv6, &ip6h->daddr, sizeof(vip_def.vipv6));
+ l4 = (void *)(ip6h + 1);
+ } else {
+ struct iphdr *iph = (void *)(eth + 1);
+
+ if (iph + 1 > data_end)
+ goto out;
+ if (iph->ihl != 5)
+ goto out;
+ if (iph->frag_off & bpf_htons(PCKT_FRAGMENTED))
+ goto out;
+
+ payload_len = bpf_ntohs(iph->tot_len);
+ proto = iph->protocol;
+
+ flow.src = iph->saddr;
+ flow.dst = iph->daddr;
+ vip_def.vip = iph->daddr;
+ l4 = (void *)(iph + 1);
+ }
+
+ /* TCP and UDP share the same port layout at offset 0 */
+ if (proto != IPPROTO_TCP && proto != IPPROTO_UDP) {
+ action = XDP_PASS;
+ goto out;
+ }
+
+ uh = l4;
+ if ((void *)(uh + 1) > data_end)
+ goto out;
+ flow.port16[0] = uh->source;
+ flow.port16[1] = uh->dest;
+
+ if (proto == IPPROTO_TCP) {
+ struct tcphdr *th = l4;
+
+ if ((void *)(th + 1) > data_end)
+ goto out;
+ is_syn = th->syn;
+ is_rst = th->rst;
+ }
+
+ flow.proto = proto;
+ vip_def.port = flow.port16[1];
+ vip_def.proto = proto;
+
+ vip_info = bpf_map_lookup_elem(&vip_map, &vip_def);
+ if (!vip_info) {
+ action = XDP_PASS;
+ goto out;
+ }
+
+ key = STATS_LRU;
+ data_stats = bpf_map_lookup_elem(&stats, &key);
+ if (!data_stats)
+ goto out;
+ data_stats->v1 += 1;
+
+ cpu_num = bpf_get_smp_processor_id();
+ lru_map = bpf_map_lookup_elem(&lru_mapping, &cpu_num);
+ if (!lru_map)
+ goto out;
+
+ if (!(vip_info->flags & F_LRU_BYPASS) && !is_syn)
+ dst = connection_table_lookup(lru_map, &flow, &real_pos);
+
+ if (!dst) {
+ if (flow.proto == IPPROTO_TCP) {
+ struct lb_stats *miss_st;
+
+ key = STATS_LRU_MISS;
+ miss_st = bpf_map_lookup_elem(&stats, &key);
+ if (miss_st)
+ miss_st->v1 += 1;
+ }
+
+ if (!get_packet_dst(&dst, &flow, vip_info, is_v6, lru_map, is_rst, &real_pos))
+ goto out;
+
+ update_vip_lru_miss_stats(&vip_def, is_v6, real_pos);
+ data_stats->v2 += 1;
+ }
+
+ key = 0;
+ cval = bpf_map_lookup_elem(&ctl_array, &key);
+ if (!cval)
+ goto out;
+
+ update_stats(&stats, vip_info->vip_num, payload_len);
+ update_stats(&reals_stats, real_pos, payload_len);
+
+ if (is_v6) {
+ create_encap_ipv6_src(flow.port16[0], flow.srcv6[0], tnl_src);
+ if (encap_v6(xdp, tnl_src, dst->dstv6, IPPROTO_IPV6, payload_len, cval->mac))
+ goto out;
+ } else if (dst->flags & F_IPV6) {
+ create_encap_ipv6_src(flow.port16[0], flow.src, tnl_src);
+ if (encap_v6(xdp, tnl_src, dst->dstv6, IPPROTO_IPIP, payload_len, cval->mac))
+ goto out;
+ } else {
+ if (encap_v4(xdp, create_encap_ipv4_src(flow.port16[0], flow.src), dst->dst,
+ payload_len, cval->mac))
+ goto out;
+ }
+
+ action = XDP_TX;
+
+out:
+ count_action(action);
+ return action;
+}
+
+static __always_inline int strip_encap(struct xdp_md *xdp, const struct ethhdr *saved_eth)
+{
+ void *data = (void *)(long)xdp->data;
+ void *data_end = (void *)(long)xdp->data_end;
+ struct ethhdr *eth = data;
+ int hdr_sz;
+
+ if (eth + 1 > data_end)
+ return -1;
+
+ hdr_sz = (eth->h_proto == bpf_htons(ETH_P_IPV6)) ? (int)sizeof(struct ipv6hdr)
+ : (int)sizeof(struct iphdr);
+
+ if (bpf_xdp_adjust_head(xdp, hdr_sz))
+ return -1;
+
+ data = (void *)(long)xdp->data;
+ data_end = (void *)(long)xdp->data_end;
+ eth = data;
+
+ if (eth + 1 > data_end)
+ return -1;
+
+ __builtin_memcpy(eth, saved_eth, sizeof(*saved_eth));
+ return 0;
+}
+
+static __always_inline void randomize_src(struct xdp_md *xdp, int saddr_off, __u32 *rand_state)
+{
+ void *data = (void *)(long)xdp->data;
+ void *data_end = (void *)(long)xdp->data_end;
+ __u32 *saddr = data + saddr_off;
+
+ *rand_state ^= *rand_state << 13;
+ *rand_state ^= *rand_state >> 17;
+ *rand_state ^= *rand_state << 5;
+
+ if ((void *)(saddr + 1) <= data_end)
+ *saddr = *rand_state & flow_mask;
+}
+
+SEC("xdp")
+int xdp_lb_bench(struct xdp_md *xdp)
+{
+ void *data = (void *)(long)xdp->data;
+ void *data_end = (void *)(long)xdp->data_end;
+ struct ethhdr *eth = data;
+ struct ethhdr saved_eth;
+ __u32 rand_state = 0;
+ __u32 batch_hash = 0;
+ int saddr_off = 0;
+ bool is_v6;
+
+ if (eth + 1 > data_end)
+ return XDP_DROP;
+
+ __builtin_memcpy(&saved_eth, eth, sizeof(saved_eth));
+
+ is_v6 = (saved_eth.h_proto == bpf_htons(ETH_P_IPV6));
+
+ saddr_off = sizeof(struct ethhdr) + (is_v6 ? offsetof(struct ipv6hdr, saddr) :
+ offsetof(struct iphdr, saddr));
+
+ if (flow_mask)
+ rand_state = bpf_get_prandom_u32() | 1;
+
+ if (cold_lru) {
+ __u32 *saddr = data + saddr_off;
+
+ batch_gen++;
+ batch_hash = (batch_gen + bpf_get_smp_processor_id()) * KNUTH_HASH_MULT;
+ if ((void *)(saddr + 1) <= data_end)
+ *saddr ^= batch_hash;
+ }
+
+ return BENCH_BPF_LOOP(
+ process_packet(xdp),
+ ({
+ if (__bench_result == XDP_TX) {
+ if (strip_encap(xdp, &saved_eth))
+ return XDP_DROP;
+ if (rand_state)
+ randomize_src(xdp, saddr_off, &rand_state);
+ }
+ if (cold_lru) {
+ void *d = (void *)(long)xdp->data;
+ void *de = (void *)(long)xdp->data_end;
+ __u32 *__sa = d + saddr_off;
+
+ if ((void *)(__sa + 1) <= de)
+ *__sa ^= batch_hash;
+ }
+ })
+ );
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/xdping_kern.c b/tools/testing/selftests/bpf/progs/xdping_kern.c
deleted file mode 100644
index 44e2b0ef23ae..000000000000
--- a/tools/testing/selftests/bpf/progs/xdping_kern.c
+++ /dev/null
@@ -1,183 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. */
-
-#define KBUILD_MODNAME "foo"
-#include <stddef.h>
-#include <string.h>
-#include <linux/bpf.h>
-#include <linux/icmp.h>
-#include <linux/in.h>
-#include <linux/if_ether.h>
-#include <linux/if_packet.h>
-#include <linux/if_vlan.h>
-#include <linux/ip.h>
-
-#include <bpf/bpf_helpers.h>
-#include <bpf/bpf_endian.h>
-
-#include "bpf_compiler.h"
-#include "xdping.h"
-
-struct {
- __uint(type, BPF_MAP_TYPE_HASH);
- __uint(max_entries, 256);
- __type(key, __u32);
- __type(value, struct pinginfo);
-} ping_map SEC(".maps");
-
-static __always_inline void swap_src_dst_mac(void *data)
-{
- unsigned short *p = data;
- unsigned short dst[3];
-
- dst[0] = p[0];
- dst[1] = p[1];
- dst[2] = p[2];
- p[0] = p[3];
- p[1] = p[4];
- p[2] = p[5];
- p[3] = dst[0];
- p[4] = dst[1];
- p[5] = dst[2];
-}
-
-static __always_inline __u16 csum_fold_helper(__wsum sum)
-{
- sum = (sum & 0xffff) + (sum >> 16);
- return ~((sum & 0xffff) + (sum >> 16));
-}
-
-static __always_inline __u16 ipv4_csum(void *data_start, int data_size)
-{
- __wsum sum;
-
- sum = bpf_csum_diff(0, 0, data_start, data_size, 0);
- return csum_fold_helper(sum);
-}
-
-#define ICMP_ECHO_LEN 64
-
-static __always_inline int icmp_check(struct xdp_md *ctx, int type)
-{
- void *data_end = (void *)(long)ctx->data_end;
- void *data = (void *)(long)ctx->data;
- struct ethhdr *eth = data;
- struct icmphdr *icmph;
- struct iphdr *iph;
-
- if (data + sizeof(*eth) + sizeof(*iph) + ICMP_ECHO_LEN > data_end)
- return XDP_PASS;
-
- if (eth->h_proto != bpf_htons(ETH_P_IP))
- return XDP_PASS;
-
- iph = data + sizeof(*eth);
-
- if (iph->protocol != IPPROTO_ICMP)
- return XDP_PASS;
-
- if (bpf_ntohs(iph->tot_len) - sizeof(*iph) != ICMP_ECHO_LEN)
- return XDP_PASS;
-
- icmph = data + sizeof(*eth) + sizeof(*iph);
-
- if (icmph->type != type)
- return XDP_PASS;
-
- return XDP_TX;
-}
-
-SEC("xdp")
-int xdping_client(struct xdp_md *ctx)
-{
- void *data = (void *)(long)ctx->data;
- struct pinginfo *pinginfo = NULL;
- struct ethhdr *eth = data;
- struct icmphdr *icmph;
- struct iphdr *iph;
- __u64 recvtime;
- __be32 raddr;
- __be16 seq;
- int ret;
- __u8 i;
-
- ret = icmp_check(ctx, ICMP_ECHOREPLY);
-
- if (ret != XDP_TX)
- return ret;
-
- iph = data + sizeof(*eth);
- icmph = data + sizeof(*eth) + sizeof(*iph);
- raddr = iph->saddr;
-
- /* Record time reply received. */
- recvtime = bpf_ktime_get_ns();
- pinginfo = bpf_map_lookup_elem(&ping_map, &raddr);
- if (!pinginfo || pinginfo->seq != icmph->un.echo.sequence)
- return XDP_PASS;
-
- if (pinginfo->start) {
- __pragma_loop_unroll_full
- for (i = 0; i < XDPING_MAX_COUNT; i++) {
- if (pinginfo->times[i] == 0)
- break;
- }
- /* verifier is fussy here... */
- if (i < XDPING_MAX_COUNT) {
- pinginfo->times[i] = recvtime -
- pinginfo->start;
- pinginfo->start = 0;
- i++;
- }
- /* No more space for values? */
- if (i == pinginfo->count || i == XDPING_MAX_COUNT)
- return XDP_PASS;
- }
-
- /* Now convert reply back into echo request. */
- swap_src_dst_mac(data);
- iph->saddr = iph->daddr;
- iph->daddr = raddr;
- icmph->type = ICMP_ECHO;
- seq = bpf_htons(bpf_ntohs(icmph->un.echo.sequence) + 1);
- icmph->un.echo.sequence = seq;
- icmph->checksum = 0;
- icmph->checksum = ipv4_csum(icmph, ICMP_ECHO_LEN);
-
- pinginfo->seq = seq;
- pinginfo->start = bpf_ktime_get_ns();
-
- return XDP_TX;
-}
-
-SEC("xdp")
-int xdping_server(struct xdp_md *ctx)
-{
- void *data = (void *)(long)ctx->data;
- struct ethhdr *eth = data;
- struct icmphdr *icmph;
- struct iphdr *iph;
- __be32 raddr;
- int ret;
-
- ret = icmp_check(ctx, ICMP_ECHO);
-
- if (ret != XDP_TX)
- return ret;
-
- iph = data + sizeof(*eth);
- icmph = data + sizeof(*eth) + sizeof(*iph);
- raddr = iph->saddr;
-
- /* Now convert request into echo reply. */
- swap_src_dst_mac(data);
- iph->saddr = iph->daddr;
- iph->daddr = raddr;
- icmph->type = ICMP_ECHOREPLY;
- icmph->checksum = 0;
- icmph->checksum = ipv4_csum(icmph, ICMP_ECHO_LEN);
-
- return XDP_TX;
-}
-
-char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/xsk_xdp_progs.c b/tools/testing/selftests/bpf/progs/xsk_xdp_progs.c
index 683306db8594..023d8befd4ca 100644
--- a/tools/testing/selftests/bpf/progs/xsk_xdp_progs.c
+++ b/tools/testing/selftests/bpf/progs/xsk_xdp_progs.c
@@ -26,8 +26,10 @@ SEC("xdp.frags") int xsk_def_prog(struct xdp_md *xdp)
SEC("xdp.frags") int xsk_xdp_drop(struct xdp_md *xdp)
{
+ static unsigned int drop_idx;
+
/* Drop every other packet */
- if (idx++ % 2)
+ if (drop_idx++ % 2)
return XDP_DROP;
return bpf_redirect_map(&xsk, 0, XDP_DROP);