Commit 27417e5e authored by Alexei Starovoitov's avatar Alexei Starovoitov
Browse files

selftests/bpf: add new tests for static stack liveness analysis



Add a bunch of new tests to verify the static stack
liveness analysis.

Signed-off-by: default avatarEduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20260410-patch-set-v4-13-5d4eecb343db@gmail.com


Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parent 957c30c0
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@
#include "verifier_leak_ptr.skel.h"
#include "verifier_linked_scalars.skel.h"
#include "verifier_live_stack.skel.h"
#include "verifier_liveness_exp.skel.h"
#include "verifier_load_acquire.skel.h"
#include "verifier_loops1.skel.h"
#include "verifier_lwt.skel.h"
@@ -202,6 +203,7 @@ void test_verifier_ldsx(void) { RUN(verifier_ldsx); }
void test_verifier_leak_ptr(void)             { RUN(verifier_leak_ptr); }
void test_verifier_linked_scalars(void)       { RUN(verifier_linked_scalars); }
void test_verifier_live_stack(void)           { RUN(verifier_live_stack); }
void test_verifier_liveness_exp(void)         { RUN(verifier_liveness_exp); }
void test_verifier_loops1(void)               { RUN(verifier_loops1); }
void test_verifier_lwt(void)                  { RUN(verifier_lwt); }
void test_verifier_map_in_map(void)           { RUN(verifier_map_in_map); }
+2323 −0

File changed.

Preview size limit exceeded, changes collapsed.

+139 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"

/*
 * Exponential complexity in analyze_subprog() liveness analysis.
 *
 * analyze_subprog() recurses into each call site that passes FP-derived
 * arguments, creating a unique func_instance per (callsite, depth).
 * There is no memoization for callees reached with equivalent entry args.
 * Even if memoization were added, it can be defeated by passing a distinct
 * FP offset at each call site.  arg_track keys on (frame, off[]), so
 * r1=fp-8, r1=fp-16, ... r1=fp-400 produce 50 unique cache keys per level.
 *
 * This test chains 8 subprograms (the MAX_CALL_FRAMES limit).  Each
 * intermediate function calls the next one 50 times, each time with a
 * different FP-relative offset in r1.
 *
 * Without complexity limits in analyze_subprog() the resulting 50^7 ~ 7.8 * 10^11
 * recursive analyze_subprog() calls will cause a CPU soft lockup or OOM.
 *
 * The BPF program itself is ~1200 instructions and perfectly valid.
 */

char _license[] SEC("license") = "GPL";

/* Call fn with r1 = r10 + off (a unique FP-derived arg per call site) */
#define C(fn, off)	"r1 = r10;"		\
			"r1 += -" #off ";"	\
			"call " #fn ";"

/* 50 calls, each with a distinct FP offset: -8, -16, ... -400 */
#define CALLS_50(fn)							\
	C(fn,   8) C(fn,  16) C(fn,  24) C(fn,  32) C(fn,  40)		\
	C(fn,  48) C(fn,  56) C(fn,  64) C(fn,  72) C(fn,  80)		\
	C(fn,  88) C(fn,  96) C(fn, 104) C(fn, 112) C(fn, 120)		\
	C(fn, 128) C(fn, 136) C(fn, 144) C(fn, 152) C(fn, 160)		\
	C(fn, 168) C(fn, 176) C(fn, 184) C(fn, 192) C(fn, 200)		\
	C(fn, 208) C(fn, 216) C(fn, 224) C(fn, 232) C(fn, 240)		\
	C(fn, 248) C(fn, 256) C(fn, 264) C(fn, 272) C(fn, 280)		\
	C(fn, 288) C(fn, 296) C(fn, 304) C(fn, 312) C(fn, 320)		\
	C(fn, 328) C(fn, 336) C(fn, 344) C(fn, 352) C(fn, 360)		\
	C(fn, 368) C(fn, 376) C(fn, 384) C(fn, 392) C(fn, 400)

/* Leaf: depth 7, no further calls */
__naked __noinline __used
static unsigned long exp_sub7(void)
{
	asm volatile (
		"r0 = 0;"
		"exit;"
		::: __clobber_all);
}

/* depth 6 -> calls exp_sub7 x50 with distinct offsets */
__naked __noinline __used
static unsigned long exp_sub6(void)
{
	asm volatile (
		CALLS_50(exp_sub7)
		"r0 = 0;"
		"exit;"
		::: __clobber_all);
}

/* depth 5 -> calls exp_sub6 x50 */
__naked __noinline __used
static unsigned long exp_sub5(void)
{
	asm volatile (
		CALLS_50(exp_sub6)
		"r0 = 0;"
		"exit;"
		::: __clobber_all);
}

/* depth 4 -> calls exp_sub5 x50 */
__naked __noinline __used
static unsigned long exp_sub4(void)
{
	asm volatile (
		CALLS_50(exp_sub5)
		"r0 = 0;"
		"exit;"
		::: __clobber_all);
}

/* depth 3 -> calls exp_sub4 x50 */
__naked __noinline __used
static unsigned long exp_sub3(void)
{
	asm volatile (
		CALLS_50(exp_sub4)
		"r0 = 0;"
		"exit;"
		::: __clobber_all);
}

/* depth 2 -> calls exp_sub3 x50 */
__naked __noinline __used
static unsigned long exp_sub2(void)
{
	asm volatile (
		CALLS_50(exp_sub3)
		"r0 = 0;"
		"exit;"
		::: __clobber_all);
}

/* depth 1 -> calls exp_sub2 x50 */
__naked __noinline __used
static unsigned long exp_sub1(void)
{
	asm volatile (
		CALLS_50(exp_sub2)
		"r0 = 0;"
		"exit;"
		::: __clobber_all);
}

/*
 * Entry: depth 0.  Calls exp_sub1 50 times, each with a distinct
 * FP offset in r1.  Every call site produces a unique arg_track,
 * defeating any memoization keyed on entry args.
 */
SEC("?raw_tp")
__failure __log_level(2)
__msg("liveness analysis exceeded complexity limit")
__naked int liveness_exponential_complexity(void)
{
	asm volatile (
		CALLS_50(exp_sub1)
		"r0 = 0;"
		"exit;"
		::: __clobber_all);
}