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