| // 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"; |