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