1 // SPDX-License-Identifier: GPL-2.0 2 /* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ 3 4 #include <linux/bpf.h> 5 #include <bpf/bpf_helpers.h> 6 #include "bpf_misc.h" 7 8 struct { 9 __uint(type, BPF_MAP_TYPE_HASH); 10 __uint(max_entries, 1); 11 __type(key, long long); 12 __type(value, long long); 13 } map_hash_8b SEC(".maps"); 14 15 #if (defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64) || \ 16 (defined(__TARGET_ARCH_riscv) && __riscv_xlen == 64)) && \ 17 defined(__BPF_FEATURE_STACK_ARGUMENT) 18 19 __noinline __used 20 static int subprog_6args(int a, int b, int c, int d, int e, int f) 21 { 22 return a + b + c + d + e + f; 23 } 24 25 __noinline __used 26 static int subprog_7args(int a, int b, int c, int d, int e, int f, int g) 27 { 28 return a + b + c + d + e + f + g; 29 } 30 31 __noinline __used 32 static long subprog_deref_arg6(long a, long b, long c, long d, long e, long *f) 33 { 34 return *f; 35 } 36 37 SEC("tc") 38 __description("stack_arg: subprog with 6 args") 39 __success __retval(21) 40 __naked void stack_arg_6args(void) 41 { 42 asm volatile ( 43 "r1 = 1;" 44 "r2 = 2;" 45 "r3 = 3;" 46 "r4 = 4;" 47 "r5 = 5;" 48 "*(u64 *)(r11 - 8) = 6;" 49 "call subprog_6args;" 50 "exit;" 51 ::: __clobber_all 52 ); 53 } 54 55 SEC("tc") 56 __description("stack_arg: two subprogs with >5 args") 57 __success __retval(90) 58 __naked void stack_arg_two_subprogs(void) 59 { 60 asm volatile ( 61 "r1 = 1;" 62 "r2 = 2;" 63 "r3 = 3;" 64 "r4 = 4;" 65 "r5 = 5;" 66 "*(u64 *)(r11 - 8) = 10;" 67 "call subprog_6args;" 68 "r6 = r0;" 69 "r1 = 1;" 70 "r2 = 2;" 71 "r3 = 3;" 72 "r4 = 4;" 73 "r5 = 5;" 74 "*(u64 *)(r11 - 16) = 30;" 75 "*(u64 *)(r11 - 8) = 20;" 76 "call subprog_7args;" 77 "r0 += r6;" 78 "exit;" 79 ::: __clobber_all 80 ); 81 } 82 83 SEC("tc") 84 __description("stack_arg: read from uninitialized stack arg slot") 85 __failure 86 __msg("invalid read from stack arg off 8 depth 0") 87 __naked void stack_arg_read_uninitialized(void) 88 { 89 asm volatile ( 90 "r0 = *(u64 *)(r11 + 8);" 91 "r0 = 0;" 92 "exit;" 93 ::: __clobber_all 94 ); 95 } 96 97 SEC("tc") 98 __description("stack_arg: gap at offset -8, only wrote -16") 99 __failure 100 __msg("callee expects 7 args, stack arg1 is not initialized") 101 __naked void stack_arg_gap_at_minus8(void) 102 { 103 asm volatile ( 104 "r1 = 1;" 105 "r2 = 2;" 106 "r3 = 3;" 107 "r4 = 4;" 108 "r5 = 5;" 109 "*(u64 *)(r11 - 16) = 30;" 110 "call subprog_7args;" 111 "exit;" 112 ::: __clobber_all 113 ); 114 } 115 116 SEC("tc") 117 __description("stack_arg: pruning with different stack arg types") 118 __failure __log_level(2) 119 __flag(BPF_F_TEST_STATE_FREQ) 120 __msg("arg JOIN insn 8 -> 10 r1: _ + fp0-8 => fp0-8|fp0+0") 121 __msg("arg JOIN insn 8 -> 10 sa0: _ + fp0-8 => fp0-8|fp0+0") 122 __msg("R{{[0-9]}} invalid mem access 'scalar'") 123 __naked void stack_arg_pruning_type_mismatch(void) 124 { 125 asm volatile ( 126 "call %[bpf_get_prandom_u32];" 127 "r6 = r0;" 128 /* local = 0 on program stack */ 129 "r7 = 0;" 130 "*(u64 *)(r10 - 8) = r7;" 131 /* Branch based on random value */ 132 "if r6 s> 3 goto l0_%=;" 133 /* Path 1: store stack pointer to outgoing arg6 */ 134 "r1 = r10;" 135 "r1 += -8;" 136 "*(u64 *)(r11 - 8) = r1;" 137 "goto l1_%=;" 138 "l0_%=:" 139 /* Path 2: store scalar to outgoing arg6 */ 140 "*(u64 *)(r11 - 8) = 42;" 141 "l1_%=:" 142 /* Call subprog that dereferences arg6 */ 143 "r1 = r6;" 144 "r2 = 0;" 145 "r3 = 0;" 146 "r4 = 0;" 147 "r5 = 0;" 148 "call subprog_deref_arg6;" 149 "exit;" 150 :: __imm(bpf_get_prandom_u32) 151 : __clobber_all 152 ); 153 } 154 155 SEC("tc") 156 __description("stack_arg: release_reference invalidates stack arg slot") 157 __failure 158 __msg("callee expects 6 args, stack arg1 is not initialized") 159 __naked void stack_arg_release_ref(void) 160 { 161 asm volatile ( 162 "r6 = r1;" 163 /* struct bpf_sock_tuple tuple = {} */ 164 "r2 = 0;" 165 "*(u32 *)(r10 - 8) = r2;" 166 "*(u64 *)(r10 - 16) = r2;" 167 "*(u64 *)(r10 - 24) = r2;" 168 "*(u64 *)(r10 - 32) = r2;" 169 "*(u64 *)(r10 - 40) = r2;" 170 "*(u64 *)(r10 - 48) = r2;" 171 /* sk = bpf_sk_lookup_tcp(ctx, &tuple, sizeof(tuple), 0, 0) */ 172 "r1 = r6;" 173 "r2 = r10;" 174 "r2 += -48;" 175 "r3 = %[sizeof_bpf_sock_tuple];" 176 "r4 = 0;" 177 "r5 = 0;" 178 "call %[bpf_sk_lookup_tcp];" 179 /* r0 = sk (PTR_TO_SOCK_OR_NULL) */ 180 "if r0 == 0 goto l0_%=;" 181 /* Store sock ref to outgoing arg6 slot */ 182 "*(u64 *)(r11 - 8) = r0;" 183 /* Release the reference — invalidates the stack arg slot */ 184 "r1 = r0;" 185 "call %[bpf_sk_release];" 186 /* Call subprog that dereferences arg6 — should fail */ 187 "r1 = 1;" 188 "r2 = 2;" 189 "r3 = 3;" 190 "r4 = 4;" 191 "r5 = 5;" 192 "call subprog_deref_arg6;" 193 "l0_%=:" 194 "r0 = 0;" 195 "exit;" 196 : 197 : __imm(bpf_sk_lookup_tcp), 198 __imm(bpf_sk_release), 199 __imm_const(sizeof_bpf_sock_tuple, sizeof(struct bpf_sock_tuple)) 200 : __clobber_all 201 ); 202 } 203 204 SEC("tc") 205 __description("stack_arg: pkt pointer in stack arg slot invalidated after pull_data") 206 __failure 207 __msg("callee expects 6 args, stack arg1 is not initialized") 208 __naked void stack_arg_stale_pkt_ptr(void) 209 { 210 asm volatile ( 211 "r6 = r1;" 212 "r7 = *(u32 *)(r6 + %[__sk_buff_data]);" 213 "r8 = *(u32 *)(r6 + %[__sk_buff_data_end]);" 214 /* check pkt has at least 1 byte */ 215 "r0 = r7;" 216 "r0 += 8;" 217 "if r0 > r8 goto l0_%=;" 218 /* Store valid pkt pointer to outgoing arg6 slot */ 219 "*(u64 *)(r11 - 8) = r7;" 220 /* bpf_skb_pull_data invalidates all pkt pointers */ 221 "r1 = r6;" 222 "r2 = 0;" 223 "call %[bpf_skb_pull_data];" 224 /* Call subprog that dereferences arg6 — should fail */ 225 "r1 = 1;" 226 "r2 = 2;" 227 "r3 = 3;" 228 "r4 = 4;" 229 "r5 = 5;" 230 "call subprog_deref_arg6;" 231 "l0_%=:" 232 "r0 = 0;" 233 "exit;" 234 : 235 : __imm(bpf_skb_pull_data), 236 __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), 237 __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)) 238 : __clobber_all 239 ); 240 } 241 242 SEC("tc") 243 __description("stack_arg: null propagation rejects deref on null branch") 244 __failure 245 __msg("R{{[0-9]}} invalid mem access 'scalar'") 246 __naked void stack_arg_null_propagation_fail(void) 247 { 248 asm volatile ( 249 "r1 = 0;" 250 "*(u64 *)(r10 - 8) = r1;" 251 /* r0 = bpf_map_lookup_elem(&map_hash_8b, &key) */ 252 "r2 = r10;" 253 "r2 += -8;" 254 "r1 = %[map_hash_8b] ll;" 255 "call %[bpf_map_lookup_elem];" 256 /* Store PTR_TO_MAP_VALUE_OR_NULL to outgoing arg6 slot */ 257 "*(u64 *)(r11 - 8) = r0;" 258 /* null check on r0 */ 259 "if r0 != 0 goto l0_%=;" 260 /* 261 * On null branch, outgoing slot is SCALAR(0). 262 * Call subprog that dereferences arg6 — should fail. 263 */ 264 "r1 = 0;" 265 "r2 = 0;" 266 "r3 = 0;" 267 "r4 = 0;" 268 "r5 = 0;" 269 "call subprog_deref_arg6;" 270 "l0_%=:" 271 "r0 = 0;" 272 "exit;" 273 : 274 : __imm(bpf_map_lookup_elem), 275 __imm_addr(map_hash_8b) 276 : __clobber_all 277 ); 278 } 279 280 SEC("tc") 281 __description("stack_arg: missing store on one branch") 282 __failure 283 __msg("callee expects 7 args, stack arg1 is not initialized") 284 __naked void stack_arg_missing_store_one_branch(void) 285 { 286 asm volatile ( 287 "call %[bpf_get_prandom_u32];" 288 "r1 = 1;" 289 "r2 = 2;" 290 "r3 = 3;" 291 "r4 = 4;" 292 "r5 = 5;" 293 /* Write arg7 (r11-16) before branch */ 294 "*(u64 *)(r11 - 16) = 20;" 295 "if r0 > 0 goto l0_%=;" 296 /* Path 1: write arg6 and call */ 297 "*(u64 *)(r11 - 8) = 10;" 298 "r1 = 1;" 299 "r2 = 2;" 300 "r3 = 3;" 301 "r4 = 4;" 302 "r5 = 5;" 303 "call subprog_7args;" 304 "goto l1_%=;" 305 "l0_%=:" 306 /* Path 2: missing arg6 store, call should fail */ 307 "r1 = 1;" 308 "r2 = 2;" 309 "r3 = 3;" 310 "r4 = 4;" 311 "r5 = 5;" 312 "call subprog_7args;" 313 "l1_%=:" 314 "r0 = 0;" 315 "exit;" 316 :: __imm(bpf_get_prandom_u32) 317 : __clobber_all 318 ); 319 } 320 321 SEC("tc") 322 __description("stack_arg: share a store for both branches") 323 __success __retval(0) 324 __naked void stack_arg_shared_store(void) 325 { 326 asm volatile ( 327 "call %[bpf_get_prandom_u32];" 328 "r1 = 1;" 329 "r2 = 2;" 330 "r3 = 3;" 331 "r4 = 4;" 332 "r5 = 5;" 333 /* Write arg7 (r11-16) before branch */ 334 "*(u64 *)(r11 - 16) = 20;" 335 "if r0 > 0 goto l0_%=;" 336 /* Path 1: write arg6 and call */ 337 "*(u64 *)(r11 - 8) = 10;" 338 "r1 = 1;" 339 "r2 = 2;" 340 "r3 = 3;" 341 "r4 = 4;" 342 "r5 = 5;" 343 "call subprog_7args;" 344 "goto l1_%=;" 345 "l0_%=:" 346 /* Path 2: also write arg6 and call */ 347 "*(u64 *)(r11 - 8) = 30;" 348 "r1 = 1;" 349 "r2 = 2;" 350 "r3 = 3;" 351 "r4 = 4;" 352 "r5 = 5;" 353 "call subprog_7args;" 354 "l1_%=:" 355 "r0 = 0;" 356 "exit;" 357 :: __imm(bpf_get_prandom_u32) 358 : __clobber_all 359 ); 360 } 361 362 SEC("tc") 363 __description("stack_arg: write beyond max outgoing depth") 364 __failure 365 __msg("stack arg write offset -80 exceeds max 7 stack args") 366 __naked void stack_arg_write_beyond_max(void) 367 { 368 asm volatile ( 369 "r1 = 1;" 370 "r2 = 2;" 371 "r3 = 3;" 372 "r4 = 4;" 373 "r5 = 5;" 374 /* Write to offset -80, way beyond any callee's needs */ 375 "*(u64 *)(r11 - 80) = 99;" 376 "*(u64 *)(r11 - 16) = 20;" 377 "*(u64 *)(r11 - 8) = 10;" 378 "call subprog_7args;" 379 "r0 = 0;" 380 "exit;" 381 ::: __clobber_all 382 ); 383 } 384 385 SEC("tc") 386 __description("stack_arg: write unused stack arg slot") 387 __failure 388 __msg("func#0 writes 5 stack arg slots, but calls only require 2") 389 __naked void stack_arg_write_unused_slot(void) 390 { 391 asm volatile ( 392 "r1 = 1;" 393 "r2 = 2;" 394 "r3 = 3;" 395 "r4 = 4;" 396 "r5 = 5;" 397 /* Write to offset -40, unused for the callee */ 398 "*(u64 *)(r11 - 40) = 99;" 399 "*(u64 *)(r11 - 16) = 20;" 400 "*(u64 *)(r11 - 8) = 10;" 401 "call subprog_7args;" 402 "r0 = 0;" 403 "exit;" 404 ::: __clobber_all 405 ); 406 } 407 408 SEC("tc") 409 __description("stack_arg: sequential calls reuse slots") 410 __failure 411 __msg("callee expects 7 args, stack arg1 is not initialized") 412 __naked void stack_arg_sequential_calls(void) 413 { 414 asm volatile ( 415 "r1 = 1;" 416 "r2 = 2;" 417 "r3 = 3;" 418 "r4 = 4;" 419 "r5 = 5;" 420 "*(u64 *)(r11 - 8) = 6;" 421 "*(u64 *)(r11 - 16) = 7;" 422 "call subprog_7args;" 423 "r6 = r0;" 424 "r1 = 1;" 425 "r2 = 2;" 426 "r3 = 3;" 427 "r4 = 4;" 428 "r5 = 5;" 429 "call subprog_7args;" 430 "r0 += r6;" 431 "exit;" 432 ::: __clobber_all 433 ); 434 } 435 436 #else 437 438 SEC("socket") 439 __description("stack_arg is not supported by compiler or jit, use a dummy test") 440 __skip("stack_arg is not supported by compiler or jit") 441 __success 442 int dummy_test(void) 443 { 444 return 0; 445 } 446 447 #endif 448 449 char _license[] SEC("license") = "GPL"; 450