[PATCH bpf-next v5 06/16] bpf: Limit the scope of BPF_REG_PARAMS usage

Yonghong Song
2026-04-17T03:47:37Z

BPF_REG_PARAMS (r11) is used for stack argument accesses. The LLVM
compiler [1] only emits BPF_REG_PARAMS in the following instruction
forms:
- BPF_LDX | BPF_MEM (load incoming stack arg)
- BPF_ST | BPF_MEM (store immediate to outgoing stack arg)
- BPF_STX | BPF_MEM (store register to outgoing stack arg)

Reject any other use of BPF_REG_PARAMS in check_and_resolve_insns()
to prevent misuse.

Since BPF_REG_PARAMS is beyond MAX_BPF_REG, array-based register
tracking indexed by register number would cause out-of-bounds
accesses. So do early return if needed.

[1] https://github.com/llvm/llvm-project/pull/189060

Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
kernel/bpf/const_fold.c | 9 +++++++--
kernel/bpf/liveness.c | 9 +++++++--
kernel/bpf/verifier.c | 17 +++++++++++++----
3 files changed, 27 insertions(+), 8 deletions(-)

diff --git a/kernel/bpf/const_fold.c b/kernel/bpf/const_fold.c
index db73c4740b1e..09db7fdb370f 100644
--- a/kernel/bpf/const_fold.c
+++ b/kernel/bpf/const_fold.c
@@ -51,13 +51,18 @@ static void const_reg_xfer(struct bpf_verifier_env *env, struct const_arg_info *
struct bpf_insn *insn, struct bpf_insn *insns, int idx)
{
struct const_arg_info unknown = { .state = CONST_ARG_UNKNOWN, .val = 0 };
- struct const_arg_info *dst = &ci_out[insn->dst_reg];
- struct const_arg_info *src = &ci_out[insn->src_reg];
+ struct const_arg_info *dst, *src;
u8 class = BPF_CLASS(insn->code);
u8 mode = BPF_MODE(insn->code);
u8 opcode = BPF_OP(insn->code) | BPF_SRC(insn->code);
int r;

+ /* Stack arguments using BPF_REG_PARAMS are outside the tracked register set. */
+ if (insn->dst_reg >= MAX_BPF_REG || insn->src_reg >= MAX_BPF_REG)
+ return;
+
+ dst = &ci_out[insn->dst_reg];
+ src = &ci_out[insn->src_reg];
switch (class) {
case BPF_ALU:
case BPF_ALU64:
diff --git a/kernel/bpf/liveness.c b/kernel/bpf/liveness.c
index 1fb4c511db5a..993d7e543e9f 100644
--- a/kernel/bpf/liveness.c
+++ b/kernel/bpf/liveness.c
@@ -1056,11 +1056,16 @@ static void arg_track_xfer(struct bpf_verifier_env *env, struct bpf_insn *insn,
int depth = instance->depth;
u8 class = BPF_CLASS(insn->code);
u8 code = BPF_OP(insn->code);
- struct arg_track *dst = &at_out[insn->dst_reg];
- struct arg_track *src = &at_out[insn->src_reg];
+ struct arg_track *dst, *src;
struct arg_track none = { .frame = ARG_NONE };
int r;

+ /* Stack arguments using BPF_REG_PARAMS are outside the tracked register set. */
+ if (insn->dst_reg >= MAX_BPF_REG || insn->src_reg >= MAX_BPF_REG)
+ return;
+
+ dst = &at_out[insn->dst_reg];
+ src = &at_out[insn->src_reg];
if (class == BPF_ALU64 && BPF_SRC(insn->code) == BPF_K) {
if (code == BPF_MOV) {
*dst = none;
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index ff0c55d80311..f25a56cfabac 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -18487,13 +18487,22 @@ static int check_and_resolve_insns(struct bpf_verifier_env *env)
return err;

for (i = 0; i < insn_cnt; i++, insn++) {
+ u8 class = BPF_CLASS(insn->code);
+ u8 mode = BPF_MODE(insn->code);
+
if (insn->dst_reg >= MAX_BPF_REG) {
- verbose(env, "R%d is invalid\n", insn->dst_reg);
- return -EINVAL;
+ if (insn->dst_reg != BPF_REG_PARAMS ||
+ !((class == BPF_ST || class == BPF_STX) && mode == BPF_MEM)) {
+ verbose(env, "R%d is invalid\n", insn->dst_reg);
+ return -EINVAL;
+ }
}
if (insn->src_reg >= MAX_BPF_REG) {
- verbose(env, "R%d is invalid\n", insn->src_reg);
- return -EINVAL;
+ if (insn->src_reg != BPF_REG_PARAMS || class != BPF_LDX ||
+ mode != BPF_MEM) {
+ verbose(env, "R%d is invalid\n", insn->src_reg);
+ return -EINVAL;
+ }
}
if (insn[0].code == (BPF_LD | BPF_IMM | BPF_DW)) {
struct bpf_insn_aux_data *aux;
--
2.52.0