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