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