WARNING in bpf_mark_chain_precision - backtracking misuse on bpf_loop callback with uninitialized r1

From: Hiker Cl

Date: Fri Aug 28 2026 - 05:26:47 EST


Hi BPF maintainers,

I'm reporting a kernel WARNING (verifier internal-invariant violation) I
encountered on the BPF subsystem, reproducible on Linux 7.2.0-rc6
(bpf-next f79066c78) and 7.2.0-rc7 (mainline 15ef2f78c).

### Summary
A 21-instruction SOCKET_FILTER program that calls bpf_loop() from a subprog
with the nr_loops register (r1 at the call site) left uninitialized
triggers:

verifier bug: backtracking misuse
WARNING: kernel/bpf/backtrack.c:819 at
bpf_mark_chain_precision+0x2f61/0x50d0

The program has an uninitialized register (r1 at the bpf_loop call site)
and should be rejected with the normal "R1 !read_ok" error. Instead, on
the callback pre-verification path, precision backtracking walks across
frames, hits a non-SCALAR register (NOT_INIT) in the caller frame, and
fails the defensive sanity check in bpf_mark_chain_precision() -- a
verifier_bug() that must never be reachable from user input.

### Steps to Reproduce (raw instruction sequence)
A complete standalone reproducer (pure syscall, no libbpf; embeds the
minimal BTF blob and func_info needed for the BPF_PSEUDO_FUNC callback)
is attached (poc_backtrack1.c). Build with `gcc -O2 -o poc_backtrack1
poc_backtrack1.c`, run as root (CAP_BPF); the trigger is printed from the
verifier log on every run.

main (0-3):
0 r6 = 4
1 call subprog /* pseudo call -> insn 4 */
2 r0 = 0
3 exit

subprog (4-13):
4 r7 = 2
5-6 r2 = callback /* LD_IMM64, BPF_PSEUDO_FUNC -> insn 14 */
7 r3 = r10
8 r3 += -8
9 *(u64 *)(r10 - 8) = 0
10 r4 = 1
11 r0 = bpf_loop(181) /* (*) r1 (nr_loops) never set in this frame */
12 r0 = 1
13 exit

callback (14-20):
14 r2 = r1 /* index */
15 if r2 == 1 goto +3
16 r2 += 1
17 r2 &= 7
18 *(u64 *)(r10 - 16) = r2
19 r0 = 1
20 exit

Full bug report (with kernel log) is filed on Bugzilla:

https://bugzilla.kernel.org/show_bug.cgi?id=221899

### Notes
This message ("backtracking misuse", backtrack.c:819) is distinct from
the syzbot reports "WARNING in __mark_chain_precision": those fire inside
backtrack_insn() with message "verifier backtracking bug", while ours is
the entry-point guard in bpf_mark_chain_precision()
(reg->type != SCALAR_VALUE) reached across frames via the bpf_loop
callback pre-verification path.

Please let me know if you need more information or if I can help test a
patch. The poc is in the attachment.
// Poc for: WARNING in kernel/bpf/backtrack.c:819 (verifier_bug
// "backtracking misuse") -- bpf_loop callback pre-verification hits an
// uninitialized register (NOT_INIT) across frames during precision
// backtracking.
//
// Build: gcc -O2 -o poc_backtrack1 poc_backtrack1.c
// Run (as privileged user, CAP_BPF required):
// ./poc_backtrack1
//
// Deterministic trigger (verified 4/4): main -> subprog -> bpf_loop
// callback, with the bpf_loop call site's r1 (nr_loops) left
// uninitialized in the subprog frame. The program should be cleanly
// rejected with "R1 !read_ok"; instead, part of the verification path
// fires verifier_bug("backtracking misuse") at backtrack.c:819.
//
// The kernel prints (dmesg, first occurrence per boot):
// verifier bug: backtracking misuse
// WARNING: CPU: ... at kernel/bpf/backtrack.c:819
// bpf_mark_chain_precision+0x.../...
// and the verifier log (log_level=2) contains the same "backtracking
// misuse" line on every occurrence. This poc prints the verifier log
// tail so the trigger is observable even after WARN_ONCE is consumed.
#include <errno.h>
#include <linux/bpf.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/syscall.h>
#include <unistd.h>

#ifndef __NR_bpf
#define __NR_bpf 321
#endif

/* uapi constants that may be missing from older libc headers */
#ifndef BPF_PSEUDO_CALL
#define BPF_PSEUDO_CALL 1
#endif
#ifndef BPF_PSEUDO_FUNC
#define BPF_PSEUDO_FUNC 4
#endif
#define BPF_FUNC_loop 181

static int sys_bpf(enum bpf_cmd cmd, union bpf_attr *attr)
{
return syscall(__NR_bpf, cmd, attr, sizeof(*attr));
}

/* ---- instruction builders (pure syscall, no libbpf) ---- */
#define BPF_RAW_INSN(CODE, DST, SRC, OFF, IMM) \
((struct bpf_insn){ .code = CODE, .dst_reg = DST, .src_reg = SRC, \
.off = OFF, .imm = IMM })
#define BPF_MOV64_IMM(DST, IMM) BPF_RAW_INSN(BPF_ALU64 | BPF_MOV | BPF_K, DST, 0, 0, IMM)
#define BPF_MOV64_REG(DST, SRC) BPF_RAW_INSN(BPF_ALU64 | BPF_MOV | BPF_X, DST, SRC, 0, 0)
#define BPF_ALU64_IMM(OP, DST, IMM) BPF_RAW_INSN(BPF_ALU64 | BPF_OP(OP) | BPF_K, DST, 0, 0, IMM)
#define BPF_ST_MEM(SIZE, DST, OFF, IMM) BPF_RAW_INSN(BPF_ST | BPF_SIZE(SIZE) | BPF_MEM, DST, 0, OFF, IMM)
#define BPF_STX_MEM(SIZE, DST, SRC, OFF) BPF_RAW_INSN(BPF_STX | BPF_SIZE(SIZE) | BPF_MEM, DST, SRC, OFF, 0)
#define BPF_JMP_IMM(OP, DST, IMM, OFF) BPF_RAW_INSN(BPF_JMP | BPF_OP(OP) | BPF_K, DST, 0, OFF, IMM)
#define BPF_CALL_REL(OFF) BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_CALL, 0, OFF)
#define BPF_CALL_FUNC(ID) BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, ID)
#define BPF_EXIT_INSN() BPF_RAW_INSN(BPF_JMP | BPF_EXIT, 0, 0, 0, 0)
#define BPF_LD_IMM64_RAW(DST, SRC, IMM) \
BPF_RAW_INSN(BPF_LD | BPF_DW | BPF_IMM, DST, SRC, 0, IMM), \
BPF_RAW_INSN(0, 0, 0, 0, 0)
#define BPF_LD_PSEUDO_FUNC(DST, REL) BPF_LD_IMM64_RAW(DST, BPF_PSEUDO_FUNC, REL)

/* ---- minimal BTF blob (v2, 119 bytes): INT + PTR + FUNC_PROTO(ret=int,
* vlen=2, args PTR) + FUNC 'callback_fn' static. Same layout as used by
* the framework executor; pinned in kernel EBTF expectations:
* type 1 = INT (size 4, bits 32)
* type 2 = PTR
* type 3 = FUNC_PROTO (ret int, 2 PTR params)
* type 4 = FUNC 'callback_fn' (linkage STATIC, vlen 0)
* FUNC type id used for func_info records is 4. ---- */
static const unsigned char btf_blob_min[] = {
0x9f, 0xeb, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x44, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x0d,
0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x16, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x00, 0x00, 0x69, 0x6e, 0x74,
0x00, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x66, 0x6e,
0x00, 0x61, 0x72, 0x67, 0x30, 0x00, 0x61, 0x72, 0x67, 0x31, 0x00,
};
#define BTF_TYPE_FUNC_CALLBACK 4

/* func_info records: entry (insn 0) + subprog (insn 4) + callback (insn 14).
* insn_off is in INSTRUCTION INDEX units (matches subprog_info[i].start
* in the kernel's check_btf_func on this kernel). */
static struct bpf_func_info func_info[] = {
{ .insn_off = 0, .type_id = BTF_TYPE_FUNC_CALLBACK },
{ .insn_off = 4, .type_id = BTF_TYPE_FUNC_CALLBACK },
{ .insn_off = 14, .type_id = BTF_TYPE_FUNC_CALLBACK },
};

#define LOG_SIZE (64 * 1024)
static char vlog[LOG_SIZE];

int main(void)
{
/* 1. load minimal BTF */
union bpf_attr b = {};
b.btf = (uint64_t)(uintptr_t)btf_blob_min;
b.btf_size = sizeof(btf_blob_min);
int btf_fd = sys_bpf(BPF_BTF_LOAD, &b);
if (btf_fd < 0) {
printf("BPF_BTF_LOAD failed: errno=%d (%s)\n", errno, strerror(errno));
return 1;
}

/* 2. program: main -> subprog -> bpf_loop callback.
* The subprog frame calls bpf_loop with r1 (nr_loops) never set. */
struct bpf_insn insns[] = {
/* main (0-3): call subprog (pseudo call, rel +2 -> insn 4) */
BPF_MOV64_IMM(BPF_REG_6, 4), /* 0: r6 = 4 */
BPF_CALL_REL(2), /* 1: call subprog (insn 4) */
BPF_MOV64_IMM(BPF_REG_0, 0), /* 2: r0 = 0 */
BPF_EXIT_INSN(), /* 3: exit */

/* subprog (4-13): bpf_loop with uninitialized r1 */
BPF_MOV64_IMM(BPF_REG_7, 2), /* 4: r7 = 2 */
BPF_LD_PSEUDO_FUNC(BPF_REG_2, 8), /* 5-6: r2 = callback (insn 14) */
BPF_MOV64_REG(BPF_REG_3, BPF_REG_10), /* 7: r3 = r10 */
BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, -8), /* 8: r3 += -8 */
BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0), /* 9: *(u64*)(r10-8) = 0 */
BPF_MOV64_IMM(BPF_REG_4, 1), /* 10: r4 = 1 (flags) */
BPF_CALL_FUNC(BPF_FUNC_loop), /* 11: r0 = bpf_loop(...) */
BPF_MOV64_IMM(BPF_REG_0, 1), /* 12: r0 = 1 */
BPF_EXIT_INSN(), /* 13: exit */

/* callback (14-20): precision chain */
BPF_MOV64_REG(BPF_REG_2, BPF_REG_1), /* 14: r2 = r1 (index) */
BPF_JMP_IMM(BPF_JEQ, BPF_REG_2, 1, 3), /* 15: if r2 == 1 goto +3 */
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, 1), /* 16: r2 += 1 */
BPF_ALU64_IMM(BPF_AND, BPF_REG_2, 7), /* 17: r2 &= 7 */
BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_2, -16), /* 18: *(u64*)(r10-16) = r2 */
BPF_MOV64_IMM(BPF_REG_0, 1), /* 19: r0 = 1 */
BPF_EXIT_INSN(), /* 20: exit */
};

/* 3. load with func_info + verifier log (level 2) */
union bpf_attr a = {};
a.prog_type = BPF_PROG_TYPE_SOCKET_FILTER;
a.insn_cnt = sizeof(insns) / sizeof(insns[0]);
a.insns = (uint64_t)(uintptr_t)insns;
a.license = (uint64_t)(uintptr_t)"GPL";
a.prog_btf_fd = btf_fd;
a.func_info_rec_size = sizeof(struct bpf_func_info);
a.func_info_cnt = sizeof(func_info) / sizeof(func_info[0]);
a.func_info = (uint64_t)(uintptr_t)func_info;
a.log_level = 2;
a.log_size = LOG_SIZE;
a.log_buf = (uint64_t)(uintptr_t)vlog;
vlog[0] = 0;

int fd = sys_bpf(BPF_PROG_LOAD, &a);
printf("BPF_PROG_LOAD rc=%d errno=%d (%s)\n", fd, fd < 0 ? errno : 0,
fd < 0 ? strerror(errno) : "");

/* 4. report detection: "backtracking misuse" in verifier log */
vlog[LOG_SIZE - 1] = 0;
int hit = strstr(vlog, "backtracking misuse") != NULL;
printf("verifier log has 'backtracking misuse': %s\n",
hit ? "YES (triggered)" : "no");
if (hit) {
/* print the log region around the marker */
const char *p = strstr(vlog, "backtracking misuse");
long start = p - vlog;
long from = start > 400 ? start - 400 : 0;
printf("--- verifier log tail around trigger ---\n");
printf("%s\n", vlog + from);
} else {
/* print last 1.5KB so failures are diagnosable */
long len = strlen(vlog);
long from = len > 1536 ? len - 1536 : 0;
printf("--- verifier log tail (last 1536 bytes) ---\n");
printf("%s\n", vlog + from);
}
printf("check dmesg for: WARNING: ... kernel/bpf/backtrack.c:819\n");
close(btf_fd);
if (fd >= 0)
close(fd);
return hit ? 0 : 2;
}