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