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