1 // SPDX-License-Identifier: GPL-2.0 2 /* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */ 3 #include <vmlinux.h> 4 #include <bpf/bpf_tracing.h> 5 #include <bpf/bpf_helpers.h> 6 #include "bpf_misc.h" 7 #include "bpf_experimental.h" 8 #include <bpf_arena_common.h> 9 10 struct arr_elem { 11 struct bpf_res_spin_lock lock; 12 }; 13 14 struct { 15 __uint(type, BPF_MAP_TYPE_ARRAY); 16 __uint(max_entries, 1); 17 __type(key, int); 18 __type(value, struct arr_elem); 19 } arrmap SEC(".maps"); 20 21 struct { 22 __uint(type, BPF_MAP_TYPE_ARENA); 23 __uint(map_flags, BPF_F_MMAPABLE); 24 __uint(max_entries, 1); /* number of pages */ 25 } arena SEC(".maps"); 26 27 struct elem { 28 struct bpf_timer timer; 29 }; 30 31 struct { 32 __uint(type, BPF_MAP_TYPE_ARRAY); 33 __uint(max_entries, 1); 34 __type(key, int); 35 __type(value, struct elem); 36 } array SEC(".maps"); 37 38 #define ENOSPC 28 39 #define _STR "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 40 41 int size; 42 u64 fault_addr; 43 void *arena_ptr; 44 45 #define private(name) SEC(".bss." #name) __hidden __attribute__((aligned(8))) 46 47 private(STREAM) struct bpf_spin_lock block; 48 49 SEC("syscall") 50 __success __retval(0) 51 int stream_exhaust(void *ctx) 52 { 53 /* Use global variable for loop convergence. */ 54 size = 0; 55 bpf_repeat(BPF_MAX_LOOPS) { 56 if (bpf_stream_printk(BPF_STDOUT, _STR) == -ENOSPC && size == 99954) 57 return 0; 58 size += sizeof(_STR) - 1; 59 } 60 return 1; 61 } 62 63 SEC("syscall") 64 __arch_x86_64 65 __arch_arm64 66 __arch_s390x 67 __arch_riscv64 68 __arch_loongarch 69 __success __retval(0) 70 __stderr("ERROR: Timeout detected for may_goto instruction") 71 __stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}") 72 __stderr("Call trace:\n" 73 "{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n" 74 "|[ \t]+[^\n]+\n)*}}") 75 int stream_cond_break(void *ctx) 76 { 77 while (can_loop) 78 ; 79 return 0; 80 } 81 82 SEC("syscall") 83 __success __retval(0) 84 __stderr("ERROR: AA or ABBA deadlock detected for bpf_res_spin_lock") 85 __stderr("{{Attempted lock = (0x[0-9a-fA-F]+)\n" 86 "Total held locks = 1\n" 87 "Held lock\\[ 0\\] = \\1}}") 88 __stderr("...") 89 __stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}") 90 __stderr("Call trace:\n" 91 "{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n" 92 "|[ \t]+[^\n]+\n)*}}") 93 int stream_deadlock(void *ctx) 94 { 95 struct bpf_res_spin_lock *lock, *nlock; 96 97 lock = bpf_map_lookup_elem(&arrmap, &(int){0}); 98 if (!lock) 99 return 1; 100 nlock = bpf_map_lookup_elem(&arrmap, &(int){0}); 101 if (!nlock) 102 return 1; 103 if (bpf_res_spin_lock(lock)) 104 return 1; 105 if (bpf_res_spin_lock(nlock)) { 106 bpf_res_spin_unlock(lock); 107 return 0; 108 } 109 bpf_res_spin_unlock(nlock); 110 bpf_res_spin_unlock(lock); 111 return 1; 112 } 113 114 SEC("syscall") 115 __success __retval(0) 116 int stream_syscall(void *ctx) 117 { 118 bpf_stream_printk(BPF_STDOUT, "foo"); 119 return 0; 120 } 121 122 SEC("syscall") 123 __arch_x86_64 124 __arch_arm64 125 __success __retval(0) 126 __stderr("ERROR: Arena WRITE access at unmapped address 0x{{.*}}") 127 __stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}") 128 __stderr("Call trace:\n" 129 "{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n" 130 "|[ \t]+[^\n]+\n)*}}") 131 int stream_arena_write_fault(void *ctx) 132 { 133 struct bpf_arena *ptr = (void *)&arena; 134 u64 user_vm_start; 135 136 /* Prevent GCC bounds warning: casting &arena to struct bpf_arena * 137 * triggers bounds checking since the map definition is smaller than struct 138 * bpf_arena. barrier_var() makes the pointer opaque to GCC, preventing the 139 * bounds analysis 140 */ 141 barrier_var(ptr); 142 user_vm_start = ptr->user_vm_start; 143 fault_addr = user_vm_start + 0x7fff; 144 bpf_addr_space_cast(user_vm_start, 0, 1); 145 asm volatile ( 146 "r1 = %0;" 147 "r2 = 1;" 148 "*(u32 *)(r1 + 0x7fff) = r2;" 149 : 150 : "r" (user_vm_start) 151 : "r1", "r2" 152 ); 153 return 0; 154 } 155 156 SEC("syscall") 157 __arch_x86_64 158 __arch_arm64 159 __success __retval(0) 160 __stderr("ERROR: Arena READ access at unmapped address 0x{{.*}}") 161 __stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}") 162 __stderr("Call trace:\n" 163 "{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n" 164 "|[ \t]+[^\n]+\n)*}}") 165 int stream_arena_read_fault(void *ctx) 166 { 167 struct bpf_arena *ptr = (void *)&arena; 168 u64 user_vm_start; 169 170 /* Prevent GCC bounds warning: casting &arena to struct bpf_arena * 171 * triggers bounds checking since the map definition is smaller than struct 172 * bpf_arena. barrier_var() makes the pointer opaque to GCC, preventing the 173 * bounds analysis 174 */ 175 barrier_var(ptr); 176 user_vm_start = ptr->user_vm_start; 177 fault_addr = user_vm_start + 0x7fff; 178 bpf_addr_space_cast(user_vm_start, 0, 1); 179 asm volatile ( 180 "r1 = %0;" 181 "r1 = *(u32 *)(r1 + 0x7fff);" 182 : 183 : "r" (user_vm_start) 184 : "r1" 185 ); 186 return 0; 187 } 188 189 SEC("syscall") 190 __arch_x86_64 191 __arch_arm64 192 __success __retval(0) 193 __stderr("ERROR: Arena READ access at unmapped address 0x{{.*}}") 194 __stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}") 195 __stderr("Call trace:\n" 196 "{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n" 197 "|[ \t]+[^\n]+\n)*}}") 198 int stream_arena_load_acquire_fault(void *ctx) 199 { 200 static const struct bpf_insn load_acquire_insn = { 201 .code = 0xc3, /* BPF_STX | BPF_ATOMIC | BPF_W */ 202 .dst_reg = 0, /* BPF_REG_0 */ 203 .src_reg = 1, /* BPF_REG_1 */ 204 .off = 0x7fff, 205 .imm = 0x100, /* BPF_LOAD_ACQ */ 206 }; 207 struct bpf_arena *ptr = (void *)&arena; 208 u64 user_vm_start, val; 209 210 /* 211 * Prevent GCC bounds warning: casting &arena to struct bpf_arena * 212 * triggers bounds checking since the map definition is smaller than 213 * struct bpf_arena. barrier_var() makes the pointer opaque to GCC, 214 * preventing the bounds analysis. 215 */ 216 barrier_var(ptr); 217 user_vm_start = ptr->user_vm_start; 218 fault_addr = user_vm_start + 0x7fff; 219 bpf_addr_space_cast(user_vm_start, 0, 1); 220 asm volatile ( 221 "r1 = %[user_vm_start];" 222 "r0 = 1;" 223 ".8byte %[load_acquire_insn];" /* r0 = load_acquire((u32 *)(r1 + 0x7fff)) */ 224 "%[val] = r0;" 225 : [val] "=r" (val) 226 : [user_vm_start] "r" (user_vm_start), 227 __imm_insn(load_acquire_insn, load_acquire_insn) 228 : "r0", "r1" 229 ); 230 return val; 231 } 232 233 SEC("syscall") 234 __arch_x86_64 235 __arch_arm64 236 __success __retval(0) 237 __stderr("ERROR: Arena WRITE access at unmapped address 0x{{.*}}") 238 __stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}") 239 __stderr("Call trace:\n" 240 "{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n" 241 "|[ \t]+[^\n]+\n)*}}") 242 int stream_arena_xchg_fault(void *ctx) 243 { 244 static const struct bpf_insn xchg_insn = { 245 .code = 0xc3, /* BPF_STX | BPF_ATOMIC | BPF_W */ 246 .dst_reg = 1, /* BPF_REG_1 */ 247 .src_reg = 2, /* BPF_REG_2 */ 248 .off = 0x7fff, 249 .imm = 0xe1, /* BPF_XCHG */ 250 }; 251 struct bpf_arena *ptr = (void *)&arena; 252 u64 user_vm_start, val; 253 254 /* 255 * Prevent GCC bounds warning: casting &arena to struct bpf_arena * 256 * triggers bounds checking since the map definition is smaller than 257 * struct bpf_arena. barrier_var() makes the pointer opaque to GCC, 258 * preventing the bounds analysis. 259 */ 260 barrier_var(ptr); 261 user_vm_start = ptr->user_vm_start; 262 fault_addr = user_vm_start + 0x7fff; 263 bpf_addr_space_cast(user_vm_start, 0, 1); 264 /* 265 * A read-modify-write carrying BPF_FETCH writes to memory, so the fault 266 * has to be reported as a WRITE from the dst_reg address, but it also 267 * reads the old value into src_reg, so the exception handler has to 268 * clear src_reg. Poison it up front, the returned value must be 0. 269 */ 270 asm volatile ( 271 "r1 = %[user_vm_start];" 272 "r2 = 1;" 273 ".8byte %[xchg_insn];" /* r2 = xchg((u32 *)(r1 + 0x7fff), r2) */ 274 "%[val] = r2;" 275 : [val] "=r" (val) 276 : [user_vm_start] "r" (user_vm_start), 277 __imm_insn(xchg_insn, xchg_insn) 278 : "r1", "r2" 279 ); 280 return val; 281 } 282 283 SEC("syscall") 284 __arch_x86_64 285 __arch_arm64 286 __success __retval(0) 287 __stderr("ERROR: Arena WRITE access at unmapped address 0x{{.*}}") 288 __stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}") 289 __stderr("Call trace:\n" 290 "{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n" 291 "|[ \t]+[^\n]+\n)*}}") 292 int stream_arena_cmpxchg_fault(void *ctx) 293 { 294 static const struct bpf_insn cmpxchg_insn = { 295 .code = 0xc3, /* BPF_STX | BPF_ATOMIC | BPF_W */ 296 .dst_reg = 1, /* BPF_REG_1 */ 297 .src_reg = 2, /* BPF_REG_2 */ 298 .off = 0x7fff, 299 .imm = 0xf1, /* BPF_CMPXCHG */ 300 }; 301 struct bpf_arena *ptr = (void *)&arena; 302 u64 user_vm_start, val; 303 304 /* 305 * Prevent GCC bounds warning: casting &arena to struct bpf_arena * 306 * triggers bounds checking since the map definition is smaller than 307 * struct bpf_arena. barrier_var() makes the pointer opaque to GCC, 308 * preventing the bounds analysis. 309 */ 310 barrier_var(ptr); 311 user_vm_start = ptr->user_vm_start; 312 fault_addr = user_vm_start + 0x7fff; 313 bpf_addr_space_cast(user_vm_start, 0, 1); 314 /* 315 * Same as the exchange above, except that a BPF_CMPXCHG reads the old 316 * value into r0 rather than into src_reg, so r0 is the register the 317 * exception handler has to clear. It doubles as the compare value, but 318 * the comparison never happens since the access faults first. 319 */ 320 asm volatile ( 321 "r1 = %[user_vm_start];" 322 "r0 = 1;" 323 "r2 = 2;" 324 ".8byte %[cmpxchg_insn];" /* r0 = cmpxchg((u32 *)(r1 + 0x7fff), r0, r2) */ 325 "%[val] = r0;" 326 : [val] "=r" (val) 327 : [user_vm_start] "r" (user_vm_start), 328 __imm_insn(cmpxchg_insn, cmpxchg_insn) 329 : "r0", "r1", "r2" 330 ); 331 return val; 332 } 333 334 static __noinline void subprog(void) 335 { 336 int __arena *addr = (int __arena *)0xdeadbeef; 337 338 arena_ptr = &arena; 339 *addr = 1; 340 } 341 342 SEC("syscall") 343 __arch_x86_64 344 __arch_arm64 345 __success __retval(0) 346 __stderr("ERROR: Arena WRITE access at unmapped address 0x{{.*}}") 347 __stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}") 348 __stderr("Call trace:\n" 349 "{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n" 350 "|[ \t]+[^\n]+\n)*}}") 351 int stream_arena_subprog_fault(void *ctx) 352 { 353 subprog(); 354 return 0; 355 } 356 357 static __noinline int timer_cb(void *map, int *key, struct bpf_timer *timer) 358 { 359 int __arena *addr = (int __arena *)0xdeadbeef; 360 361 arena_ptr = &arena; 362 *addr = 1; 363 return 0; 364 } 365 366 SEC("syscall") 367 __arch_x86_64 368 __arch_arm64 369 __success __retval(0) 370 __stderr("ERROR: Arena WRITE access at unmapped address 0x{{.*}}") 371 __stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}") 372 __stderr("Call trace:\n" 373 "{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n" 374 "|[ \t]+[^\n]+\n)*}}") 375 int stream_arena_callback_fault(void *ctx) 376 { 377 struct bpf_timer *arr_timer; 378 379 arr_timer = bpf_map_lookup_elem(&array, &(int){0}); 380 if (!arr_timer) 381 return 0; 382 bpf_timer_init(arr_timer, &array, 1); 383 bpf_timer_set_callback(arr_timer, timer_cb); 384 bpf_timer_start(arr_timer, 0, 0); 385 return 0; 386 } 387 388 SEC("syscall") 389 __arch_x86_64 390 __arch_arm64 391 __success __retval(0) 392 __stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}") 393 __stderr("Call trace:\n" 394 "{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n" 395 "|[ \t]+[^\n]+\n)*}}") 396 int stream_print_stack_kfunc(void *ctx) 397 { 398 return bpf_stream_print_stack(BPF_STDERR); 399 } 400 401 SEC("syscall") 402 __success __retval(-2) 403 int stream_print_stack_invalid_id(void *ctx) 404 { 405 /* Try to pass an invalid stream ID. */ 406 return bpf_stream_print_stack((enum bpf_stream_id)0xbadcafe); 407 } 408 409 SEC("syscall") 410 __arch_x86_64 411 __arch_arm64 412 __success __retval(0) 413 __stdout(_STR) 414 __stderr("CPU: {{[0-9]+}} UID: 0 PID: {{[0-9]+}} Comm: {{.*}}") 415 __stderr("Call trace:\n" 416 "{{([a-zA-Z_][a-zA-Z0-9_]*\\+0x[0-9a-fA-F]+/0x[0-9a-fA-F]+\n" 417 "|[ \t]+[^\n]+\n)*}}") 418 int stream_print_kfuncs_locked(void *ctx) 419 { 420 int ret; 421 422 bpf_spin_lock(&block); 423 424 ret = bpf_stream_printk(BPF_STDOUT, _STR); 425 if (ret) 426 goto out; 427 428 ret = bpf_stream_print_stack(BPF_STDERR); 429 430 out: 431 bpf_spin_unlock(&block); 432 433 return ret; 434 } 435 436 437 char _license[] SEC("license") = "GPL"; 438