xref: /linux/tools/testing/selftests/bpf/progs/verifier_liveness_exp.c (revision 0fc8f6200d2313278fbf4539bbab74677c685531)
1*27417e5eSAlexei Starovoitov // SPDX-License-Identifier: GPL-2.0
2*27417e5eSAlexei Starovoitov /* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
3*27417e5eSAlexei Starovoitov 
4*27417e5eSAlexei Starovoitov #include <linux/bpf.h>
5*27417e5eSAlexei Starovoitov #include <bpf/bpf_helpers.h>
6*27417e5eSAlexei Starovoitov #include "bpf_misc.h"
7*27417e5eSAlexei Starovoitov 
8*27417e5eSAlexei Starovoitov /*
9*27417e5eSAlexei Starovoitov  * Exponential complexity in analyze_subprog() liveness analysis.
10*27417e5eSAlexei Starovoitov  *
11*27417e5eSAlexei Starovoitov  * analyze_subprog() recurses into each call site that passes FP-derived
12*27417e5eSAlexei Starovoitov  * arguments, creating a unique func_instance per (callsite, depth).
13*27417e5eSAlexei Starovoitov  * There is no memoization for callees reached with equivalent entry args.
14*27417e5eSAlexei Starovoitov  * Even if memoization were added, it can be defeated by passing a distinct
15*27417e5eSAlexei Starovoitov  * FP offset at each call site.  arg_track keys on (frame, off[]), so
16*27417e5eSAlexei Starovoitov  * r1=fp-8, r1=fp-16, ... r1=fp-400 produce 50 unique cache keys per level.
17*27417e5eSAlexei Starovoitov  *
18*27417e5eSAlexei Starovoitov  * This test chains 8 subprograms (the MAX_CALL_FRAMES limit).  Each
19*27417e5eSAlexei Starovoitov  * intermediate function calls the next one 50 times, each time with a
20*27417e5eSAlexei Starovoitov  * different FP-relative offset in r1.
21*27417e5eSAlexei Starovoitov  *
22*27417e5eSAlexei Starovoitov  * Without complexity limits in analyze_subprog() the resulting 50^7 ~ 7.8 * 10^11
23*27417e5eSAlexei Starovoitov  * recursive analyze_subprog() calls will cause a CPU soft lockup or OOM.
24*27417e5eSAlexei Starovoitov  *
25*27417e5eSAlexei Starovoitov  * The BPF program itself is ~1200 instructions and perfectly valid.
26*27417e5eSAlexei Starovoitov  */
27*27417e5eSAlexei Starovoitov 
28*27417e5eSAlexei Starovoitov char _license[] SEC("license") = "GPL";
29*27417e5eSAlexei Starovoitov 
30*27417e5eSAlexei Starovoitov /* Call fn with r1 = r10 + off (a unique FP-derived arg per call site) */
31*27417e5eSAlexei Starovoitov #define C(fn, off)	"r1 = r10;"		\
32*27417e5eSAlexei Starovoitov 			"r1 += -" #off ";"	\
33*27417e5eSAlexei Starovoitov 			"call " #fn ";"
34*27417e5eSAlexei Starovoitov 
35*27417e5eSAlexei Starovoitov /* 50 calls, each with a distinct FP offset: -8, -16, ... -400 */
36*27417e5eSAlexei Starovoitov #define CALLS_50(fn)							\
37*27417e5eSAlexei Starovoitov 	C(fn,   8) C(fn,  16) C(fn,  24) C(fn,  32) C(fn,  40)		\
38*27417e5eSAlexei Starovoitov 	C(fn,  48) C(fn,  56) C(fn,  64) C(fn,  72) C(fn,  80)		\
39*27417e5eSAlexei Starovoitov 	C(fn,  88) C(fn,  96) C(fn, 104) C(fn, 112) C(fn, 120)		\
40*27417e5eSAlexei Starovoitov 	C(fn, 128) C(fn, 136) C(fn, 144) C(fn, 152) C(fn, 160)		\
41*27417e5eSAlexei Starovoitov 	C(fn, 168) C(fn, 176) C(fn, 184) C(fn, 192) C(fn, 200)		\
42*27417e5eSAlexei Starovoitov 	C(fn, 208) C(fn, 216) C(fn, 224) C(fn, 232) C(fn, 240)		\
43*27417e5eSAlexei Starovoitov 	C(fn, 248) C(fn, 256) C(fn, 264) C(fn, 272) C(fn, 280)		\
44*27417e5eSAlexei Starovoitov 	C(fn, 288) C(fn, 296) C(fn, 304) C(fn, 312) C(fn, 320)		\
45*27417e5eSAlexei Starovoitov 	C(fn, 328) C(fn, 336) C(fn, 344) C(fn, 352) C(fn, 360)		\
46*27417e5eSAlexei Starovoitov 	C(fn, 368) C(fn, 376) C(fn, 384) C(fn, 392) C(fn, 400)
47*27417e5eSAlexei Starovoitov 
48*27417e5eSAlexei Starovoitov /* Leaf: depth 7, no further calls */
49*27417e5eSAlexei Starovoitov __naked __noinline __used
50*27417e5eSAlexei Starovoitov static unsigned long exp_sub7(void)
51*27417e5eSAlexei Starovoitov {
52*27417e5eSAlexei Starovoitov 	asm volatile (
53*27417e5eSAlexei Starovoitov 		"r0 = 0;"
54*27417e5eSAlexei Starovoitov 		"exit;"
55*27417e5eSAlexei Starovoitov 		::: __clobber_all);
56*27417e5eSAlexei Starovoitov }
57*27417e5eSAlexei Starovoitov 
58*27417e5eSAlexei Starovoitov /* depth 6 -> calls exp_sub7 x50 with distinct offsets */
59*27417e5eSAlexei Starovoitov __naked __noinline __used
60*27417e5eSAlexei Starovoitov static unsigned long exp_sub6(void)
61*27417e5eSAlexei Starovoitov {
62*27417e5eSAlexei Starovoitov 	asm volatile (
63*27417e5eSAlexei Starovoitov 		CALLS_50(exp_sub7)
64*27417e5eSAlexei Starovoitov 		"r0 = 0;"
65*27417e5eSAlexei Starovoitov 		"exit;"
66*27417e5eSAlexei Starovoitov 		::: __clobber_all);
67*27417e5eSAlexei Starovoitov }
68*27417e5eSAlexei Starovoitov 
69*27417e5eSAlexei Starovoitov /* depth 5 -> calls exp_sub6 x50 */
70*27417e5eSAlexei Starovoitov __naked __noinline __used
71*27417e5eSAlexei Starovoitov static unsigned long exp_sub5(void)
72*27417e5eSAlexei Starovoitov {
73*27417e5eSAlexei Starovoitov 	asm volatile (
74*27417e5eSAlexei Starovoitov 		CALLS_50(exp_sub6)
75*27417e5eSAlexei Starovoitov 		"r0 = 0;"
76*27417e5eSAlexei Starovoitov 		"exit;"
77*27417e5eSAlexei Starovoitov 		::: __clobber_all);
78*27417e5eSAlexei Starovoitov }
79*27417e5eSAlexei Starovoitov 
80*27417e5eSAlexei Starovoitov /* depth 4 -> calls exp_sub5 x50 */
81*27417e5eSAlexei Starovoitov __naked __noinline __used
82*27417e5eSAlexei Starovoitov static unsigned long exp_sub4(void)
83*27417e5eSAlexei Starovoitov {
84*27417e5eSAlexei Starovoitov 	asm volatile (
85*27417e5eSAlexei Starovoitov 		CALLS_50(exp_sub5)
86*27417e5eSAlexei Starovoitov 		"r0 = 0;"
87*27417e5eSAlexei Starovoitov 		"exit;"
88*27417e5eSAlexei Starovoitov 		::: __clobber_all);
89*27417e5eSAlexei Starovoitov }
90*27417e5eSAlexei Starovoitov 
91*27417e5eSAlexei Starovoitov /* depth 3 -> calls exp_sub4 x50 */
92*27417e5eSAlexei Starovoitov __naked __noinline __used
93*27417e5eSAlexei Starovoitov static unsigned long exp_sub3(void)
94*27417e5eSAlexei Starovoitov {
95*27417e5eSAlexei Starovoitov 	asm volatile (
96*27417e5eSAlexei Starovoitov 		CALLS_50(exp_sub4)
97*27417e5eSAlexei Starovoitov 		"r0 = 0;"
98*27417e5eSAlexei Starovoitov 		"exit;"
99*27417e5eSAlexei Starovoitov 		::: __clobber_all);
100*27417e5eSAlexei Starovoitov }
101*27417e5eSAlexei Starovoitov 
102*27417e5eSAlexei Starovoitov /* depth 2 -> calls exp_sub3 x50 */
103*27417e5eSAlexei Starovoitov __naked __noinline __used
104*27417e5eSAlexei Starovoitov static unsigned long exp_sub2(void)
105*27417e5eSAlexei Starovoitov {
106*27417e5eSAlexei Starovoitov 	asm volatile (
107*27417e5eSAlexei Starovoitov 		CALLS_50(exp_sub3)
108*27417e5eSAlexei Starovoitov 		"r0 = 0;"
109*27417e5eSAlexei Starovoitov 		"exit;"
110*27417e5eSAlexei Starovoitov 		::: __clobber_all);
111*27417e5eSAlexei Starovoitov }
112*27417e5eSAlexei Starovoitov 
113*27417e5eSAlexei Starovoitov /* depth 1 -> calls exp_sub2 x50 */
114*27417e5eSAlexei Starovoitov __naked __noinline __used
115*27417e5eSAlexei Starovoitov static unsigned long exp_sub1(void)
116*27417e5eSAlexei Starovoitov {
117*27417e5eSAlexei Starovoitov 	asm volatile (
118*27417e5eSAlexei Starovoitov 		CALLS_50(exp_sub2)
119*27417e5eSAlexei Starovoitov 		"r0 = 0;"
120*27417e5eSAlexei Starovoitov 		"exit;"
121*27417e5eSAlexei Starovoitov 		::: __clobber_all);
122*27417e5eSAlexei Starovoitov }
123*27417e5eSAlexei Starovoitov 
124*27417e5eSAlexei Starovoitov /*
125*27417e5eSAlexei Starovoitov  * Entry: depth 0.  Calls exp_sub1 50 times, each with a distinct
126*27417e5eSAlexei Starovoitov  * FP offset in r1.  Every call site produces a unique arg_track,
127*27417e5eSAlexei Starovoitov  * defeating any memoization keyed on entry args.
128*27417e5eSAlexei Starovoitov  */
129*27417e5eSAlexei Starovoitov SEC("?raw_tp")
130*27417e5eSAlexei Starovoitov __failure __log_level(2)
131*27417e5eSAlexei Starovoitov __msg("liveness analysis exceeded complexity limit")
132*27417e5eSAlexei Starovoitov __naked int liveness_exponential_complexity(void)
133*27417e5eSAlexei Starovoitov {
134*27417e5eSAlexei Starovoitov 	asm volatile (
135*27417e5eSAlexei Starovoitov 		CALLS_50(exp_sub1)
136*27417e5eSAlexei Starovoitov 		"r0 = 0;"
137*27417e5eSAlexei Starovoitov 		"exit;"
138*27417e5eSAlexei Starovoitov 		::: __clobber_all);
139*27417e5eSAlexei Starovoitov }
140