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