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