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)
stream_exhaust(void * ctx)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)*}}")
stream_cond_break(void * ctx)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)*}}")
stream_deadlock(void * ctx)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)
stream_syscall(void * ctx)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)*}}")
stream_arena_write_fault(void * ctx)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)*}}")
stream_arena_read_fault(void * ctx)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)*}}")
stream_arena_load_acquire_fault(void * ctx)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)*}}")
stream_arena_xchg_fault(void * ctx)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)*}}")
stream_arena_cmpxchg_fault(void * ctx)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
subprog(void)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)*}}")
stream_arena_subprog_fault(void * ctx)351 int stream_arena_subprog_fault(void *ctx)
352 {
353 subprog();
354 return 0;
355 }
356
timer_cb(void * map,int * key,struct bpf_timer * timer)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)*}}")
stream_arena_callback_fault(void * ctx)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)*}}")
stream_print_stack_kfunc(void * ctx)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)
stream_print_stack_invalid_id(void * ctx)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)
__stdout(_STR)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