From: Kumar Kartikeya Dwivedi <memxor@gmail.com>
To: bpf@vger.kernel.org
Cc: Eduard Zingerman <eddyz87@gmail.com>,
Alexei Starovoitov <ast@kernel.org>,
Andrii Nakryiko <andrii@kernel.org>,
Daniel Borkmann <daniel@iogearbox.net>,
Emil Tsalapatis <emil@etsalapatis.com>,
kkd@meta.com, kernel-team@meta.com
Subject: [PATCH bpf-next v3 08/17] bpf: Report Register Type Safety errors
Date: Mon, 13 Jul 2026 17:38:57 +0200 [thread overview]
Message-ID: <20260713153910.2556007-9-memxor@gmail.com> (raw)
In-Reply-To: <20260713153910.2556007-1-memxor@gmail.com>
Augment selected register-state verifier failures with Register Type Safety
reports. The existing verbose verifier messages remain in place; the new
reports add reason, source context, causal path, and suggestions.
Cover invalid pointer dereferences, unreadable registers, missing outgoing
stack arguments for bpf2bpf and kfunc calls, and rejected pointer arithmetic.
Use scoped diagnostic history so reports start from the latest relevant value
change and then show later branch outcomes.
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
kernel/bpf/diagnostics.c | 249 +++++++++++++++++++++++++++++++++++++++
kernel/bpf/diagnostics.h | 19 +++
kernel/bpf/verifier.c | 160 ++++++++++++++++++++++---
3 files changed, 412 insertions(+), 16 deletions(-)
diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index bd06974efd7b..0fa61882ee69 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -853,6 +853,255 @@ void bpf_diag_report_source(struct bpf_verifier_env *env, u32 insn_idx, const ch
kfree(msg);
}
+static u32 diag_current_frameno(const struct bpf_verifier_env *env)
+{
+ return env->cur_state->frame[env->cur_state->curframe]->frameno;
+}
+
+void bpf_diag_report_register_type(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *problem, const char *reason, const char *suggestion)
+{
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_REG,
+ .frameno = diag_current_frameno(env),
+ .regno = regno,
+ };
+
+ bpf_diag_report_header(env, CATEGORY_REGISTER_TYPE_SAFETY, problem);
+ diag_report_reason(env, "%s", reason);
+
+ diag_report_section(env, "At");
+ bpf_diag_report_source(env, insn_idx, "error", "%s", problem);
+
+ if (regno >= 0)
+ diag_print_history(env, &opts);
+
+ diag_report_suggestion(env, "%s", suggestion);
+}
+
+const char *bpf_diag_reg_type_plain(struct bpf_verifier_env *env, enum bpf_reg_type type)
+{
+ switch (base_type(type)) {
+ case NOT_INIT:
+ return "an uninitialized value";
+ case SCALAR_VALUE:
+ return "an integer scalar";
+ case PTR_TO_CTX:
+ return "a context pointer";
+ case PTR_TO_STACK:
+ return "a stack pointer";
+ case PTR_TO_MAP_VALUE:
+ if (type_may_be_null(type))
+ return "a nullable map value pointer";
+ return "a map value pointer";
+ case PTR_TO_MEM:
+ if (type_may_be_null(type))
+ return "a nullable memory pointer";
+ return "a memory pointer";
+ case PTR_TO_BTF_ID:
+ if (type_may_be_null(type))
+ return "a nullable kernel object pointer";
+ if (type_is_non_owning_ref(type))
+ return "a borrowed allocated object pointer";
+ if (type_is_ptr_alloc_obj(type))
+ return "an owned allocated object pointer";
+ if (type_flag(type) & PTR_UNTRUSTED)
+ return "an untrusted kernel object pointer";
+ return "a kernel object pointer";
+ default:
+ return reg_type_str(env, type);
+ }
+}
+
+static const char *diag_arg_ordinal(int argno)
+{
+ switch (argno) {
+ case 1:
+ return "first";
+ case 2:
+ return "second";
+ case 3:
+ return "third";
+ case 4:
+ return "fourth";
+ case 5:
+ return "fifth";
+ case 6:
+ return "sixth";
+ case 7:
+ return "seventh";
+ case 8:
+ return "eighth";
+ case 9:
+ return "ninth";
+ case 10:
+ return "tenth";
+ case 11:
+ return "eleventh";
+ case 12:
+ return "twelfth";
+ default:
+ return NULL;
+ }
+}
+
+void bpf_diag_report_invalid_deref(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *reg_name, const struct bpf_reg_state *reg,
+ enum bpf_diag_invalid_deref_kind kind, s64 offset)
+{
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_REG,
+ .frameno = diag_current_frameno(env),
+ .regno = regno,
+ };
+ const char *type_name = bpf_diag_reg_type_plain(env, reg->type);
+
+ bpf_diag_report_header(env, CATEGORY_REGISTER_TYPE_SAFETY, "invalid dereference");
+
+ switch (kind) {
+ case BPF_DIAG_DEREF_SCALAR:
+ diag_report_reason(env, "%s is an integer scalar here, not a pointer to memory.",
+ reg_name);
+ break;
+ case BPF_DIAG_DEREF_NULLABLE_PTR:
+ diag_report_reason(env,
+ "%s may be NULL here (%s). The program could dereference NULL "
+ "on this path, so the verifier cannot prove this access is "
+ "safe.",
+ reg_name, type_name);
+ break;
+ case BPF_DIAG_DEREF_MODIFIED_PTR:
+ diag_report_reason(env,
+ "%s has offset %lld here, but this pointer type must be "
+ "dereferenced in its original form.",
+ reg_name, offset);
+ break;
+ case BPF_DIAG_DEREF_INVALID_PTR:
+ default:
+ diag_report_reason(env,
+ "%s has type %s here, which is not valid for this memory "
+ "access.",
+ reg_name, type_name);
+ break;
+ }
+
+ diag_report_section(env, "At");
+ if (kind == BPF_DIAG_DEREF_MODIFIED_PTR)
+ bpf_diag_report_source(env, insn_idx, "error",
+ "dereference requires the original %s pointer", type_name);
+ else
+ bpf_diag_report_source(env, insn_idx, "error", "invalid dereference of %s (%s)",
+ reg_name, type_name);
+
+ if (regno >= 0)
+ diag_print_history(env, &opts);
+
+ switch (kind) {
+ case BPF_DIAG_DEREF_NULLABLE_PTR:
+ diag_report_suggestion(env, "Add a NULL check before the access and dereference "
+ "the pointer only on the non-NULL path.");
+ break;
+ case BPF_DIAG_DEREF_MODIFIED_PTR:
+ diag_report_suggestion(env, "Preserve the original pointer in another register, or "
+ "use only offsets this pointer type permits before "
+ "dereferencing it.");
+ break;
+ case BPF_DIAG_DEREF_SCALAR:
+ case BPF_DIAG_DEREF_INVALID_PTR:
+ default:
+ diag_report_suggestion(env, "Preserve a pointer-valued register where needed, or "
+ "reload and revalidate the pointer after scalar "
+ "arithmetic, helper calls, or other operations that "
+ "can invalidate it.");
+ break;
+ }
+}
+
+void bpf_diag_report_unreadable_reg(struct bpf_verifier_env *env, u32 insn_idx, int regno)
+{
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_REG,
+ .frameno = diag_current_frameno(env),
+ .regno = regno,
+ };
+
+ bpf_diag_report_header(env, CATEGORY_REGISTER_TYPE_SAFETY, "unreadable register");
+ diag_report_reason(env,
+ "R%d is not readable here. A previous operation may have invalidated "
+ "this register, so the verifier cannot use it as an input.",
+ regno);
+
+ diag_report_section(env, "At");
+ bpf_diag_report_source(env, insn_idx, "error", "R%d is not readable", regno);
+
+ if (regno >= 0)
+ diag_print_history(env, &opts);
+
+ diag_report_suggestion(env, "Avoid using the register after it is invalidated, or reload "
+ "and revalidate a fresh pointer before this instruction.");
+}
+
+static int diag_stack_argno(u8 slot)
+{
+ return MAX_BPF_FUNC_REG_ARGS + slot + 1;
+}
+
+static void diag_format_stack_arg(char *buf, size_t size, u8 slot, const char *arg_name)
+{
+ int argno = diag_stack_argno(slot);
+ const char *ordinal = diag_arg_ordinal(argno);
+
+ if (ordinal && arg_name)
+ scnprintf(buf, size, "outgoing stack argument %u (%s argument, %s)", slot + 1,
+ ordinal, arg_name);
+ else if (ordinal)
+ scnprintf(buf, size, "outgoing stack argument %u (%s argument)", slot + 1, ordinal);
+ else if (arg_name)
+ scnprintf(buf, size, "outgoing stack argument %u (%s)", slot + 1, arg_name);
+ else
+ scnprintf(buf, size, "outgoing stack argument %u", slot + 1);
+}
+
+void bpf_diag_report_stack_arg_uninit(struct bpf_verifier_env *env, u32 insn_idx, int nargs,
+ int stack_arg_slot, const char *callee_name,
+ const char *arg_name)
+{
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_STACK_ARG,
+ .frameno = diag_current_frameno(env),
+ .stack_arg_slot = stack_arg_slot,
+ };
+ const char *arg_buf;
+
+ arg_buf = bpf_diag_scratch_buf(env, 1, NULL);
+ if (arg_buf)
+ diag_format_stack_arg((char *)arg_buf, BPF_DIAG_SCRATCH_STR_LEN, stack_arg_slot,
+ arg_name);
+ else
+ arg_buf = "";
+ bpf_diag_report_header(env, CATEGORY_REGISTER_TYPE_SAFETY, "missing stack argument");
+ if (callee_name && *callee_name)
+ diag_report_reason(env,
+ "Function %s expects %d arguments, but %s is not initialized at "
+ "this call.",
+ callee_name, nargs, arg_buf);
+ else
+ diag_report_reason(env,
+ "The callee expects %d arguments, but %s is not initialized at "
+ "this call.",
+ nargs, arg_buf);
+
+ diag_report_section(env, "At");
+ bpf_diag_report_source(env, insn_idx, "error", "%s is not initialized", arg_buf);
+
+ if (stack_arg_slot >= 0)
+ diag_print_history(env, &opts);
+
+ diag_report_suggestion(env, "Write the outgoing stack argument after any operation that "
+ "may invalidate stored pointer values, and before making this "
+ "call.");
+}
+
int bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx, bool cond_true)
{
struct bpf_diag_history_event event = {
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
index 931d880b7eda..28bc234cfe56 100644
--- a/kernel/bpf/diagnostics.h
+++ b/kernel/bpf/diagnostics.h
@@ -4,6 +4,7 @@
#ifndef __BPF_DIAGNOSTICS_H
#define __BPF_DIAGNOSTICS_H
+#include <linux/bpf.h>
#include <linux/compiler_attributes.h>
#include <linux/types.h>
@@ -98,6 +99,14 @@ enum bpf_diag_context_event_kind {
BPF_DIAG_CONTEXT_EVENT_IRQ = BPF_DIAG_CONTEXT_IRQ,
BPF_DIAG_CONTEXT_EVENT_LOCK = BPF_DIAG_CONTEXT_LOCK,
};
+
+enum bpf_diag_invalid_deref_kind {
+ BPF_DIAG_DEREF_SCALAR,
+ BPF_DIAG_DEREF_NULLABLE_PTR,
+ BPF_DIAG_DEREF_MODIFIED_PTR,
+ BPF_DIAG_DEREF_INVALID_PTR,
+};
+
bool bpf_diag_enabled(const struct bpf_verifier_env *env);
int bpf_diag_init(struct bpf_verifier_env *env);
char *bpf_diag_scratch_buf(struct bpf_verifier_env *env, unsigned int slot, size_t *size);
@@ -108,6 +117,7 @@ const char *bpf_diag_scratch_printf(struct bpf_verifier_env *env, unsigned int s
const char *fmt, ...) __printf(3, 4);
const char *bpf_diag_format_btf_type_scratch(struct bpf_verifier_env *env, unsigned int slot,
const struct btf *btf, u32 type_id);
+const char *bpf_diag_reg_type_plain(struct bpf_verifier_env *env, enum bpf_reg_type type);
u32 bpf_diag_event_log_pos(struct bpf_verifier_env *env);
void bpf_diag_event_log_reset(struct bpf_verifier_env *env, u32 pos);
int bpf_diag_error(const struct bpf_verifier_env *env);
@@ -117,6 +127,15 @@ void bpf_diag_report_header(struct bpf_verifier_env *env, const char *category,
const char *problem);
void bpf_diag_report_source(struct bpf_verifier_env *env, u32 insn_idx, const char *label,
const char *fmt, ...) __printf(4, 5);
+void bpf_diag_report_register_type(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *problem, const char *reason, const char *suggestion);
+void bpf_diag_report_invalid_deref(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *reg_name, const struct bpf_reg_state *reg,
+ enum bpf_diag_invalid_deref_kind kind, s64 offset);
+void bpf_diag_report_unreadable_reg(struct bpf_verifier_env *env, u32 insn_idx, int regno);
+void bpf_diag_report_stack_arg_uninit(struct bpf_verifier_env *env, u32 insn_idx, int nargs,
+ int stack_arg_slot, const char *callee_name,
+ const char *arg_name);
int bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx, bool cond_true);
void bpf_diag_record_mod(struct bpf_verifier_env *env, u32 insn_idx,
struct bpf_diag_mod_target target, enum bpf_diag_mod_reason reason,
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 4cc4f1cc01c0..ca903e4e53ea 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -3253,6 +3253,7 @@ static int __check_reg_arg(struct bpf_verifier_env *env, struct bpf_reg_state *r
/* check whether register used as source operand can be read */
if (reg->type == NOT_INIT) {
verbose(env, "R%d !read_ok\n", regno);
+ bpf_diag_report_unreadable_reg(env, env->insn_idx, regno);
return -EACCES;
}
/* We don't need to worry about FP liveness because it's read-only */
@@ -4311,7 +4312,8 @@ static int mark_stack_arg_precision(struct bpf_verifier_env *env, int arg_idx)
}
static int check_outgoing_stack_args(struct bpf_verifier_env *env, struct bpf_func_state *caller,
- int nargs)
+ int nargs, const char *callee_name, const struct btf *btf,
+ const struct btf_param *args)
{
int i, spi;
@@ -4319,8 +4321,14 @@ static int check_outgoing_stack_args(struct bpf_verifier_env *env, struct bpf_fu
spi = i - MAX_BPF_FUNC_REG_ARGS;
if (spi >= caller->out_stack_arg_cnt ||
caller->stack_arg_regs[spi].type == NOT_INIT) {
+ const char *arg_name = NULL;
+
+ if (args && args[i].name_off)
+ arg_name = btf_name_by_offset(btf, args[i].name_off);
verbose(env, "callee expects %d args, stack arg%d is not initialized\n",
nargs, spi + 1);
+ bpf_diag_report_stack_arg_uninit(env, env->insn_idx, nargs, spi,
+ callee_name, arg_name);
return -EFAULT;
}
}
@@ -4475,6 +4483,9 @@ static int __check_ptr_off_reg(struct bpf_verifier_env *env,
if (!fixed_off_ok && reg->var_off.value != 0) {
verbose(env, "dereference of modified %s ptr %s off=%lld disallowed\n",
reg_type_str(env, reg->type), reg_arg_name(env, argno), reg->var_off.value);
+ bpf_diag_report_invalid_deref(env, env->insn_idx, reg_from_argno(argno),
+ reg_arg_name(env, argno), reg,
+ BPF_DIAG_DEREF_MODIFIED_PTR, reg->var_off.value);
return -EACCES;
}
@@ -6363,6 +6374,9 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
if (type_may_be_null(reg->type)) {
verbose(env, "%s invalid mem access '%s'\n", reg_arg_name(env, argno),
reg_type_str(env, reg->type));
+ bpf_diag_report_invalid_deref(env, insn_idx, reg_from_argno(argno),
+ reg_arg_name(env, argno), reg,
+ BPF_DIAG_DEREF_NULLABLE_PTR, 0);
return -EACCES;
}
@@ -6519,8 +6533,16 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
if (t == BPF_READ && value_regno >= 0)
mark_reg_unknown(env, regs, value_regno);
} else {
+ enum bpf_diag_invalid_deref_kind kind = BPF_DIAG_DEREF_INVALID_PTR;
+
verbose(env, "%s invalid mem access '%s'\n", reg_arg_name(env, argno),
reg_type_str(env, reg->type));
+ if (reg->type == SCALAR_VALUE)
+ kind = BPF_DIAG_DEREF_SCALAR;
+ else if (type_may_be_null(reg->type))
+ kind = BPF_DIAG_DEREF_NULLABLE_PTR;
+ bpf_diag_report_invalid_deref(env, insn_idx, reg_from_argno(argno),
+ reg_arg_name(env, argno), reg, kind, 0);
return -EACCES;
}
@@ -9550,20 +9572,28 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
struct bpf_func_state *caller = cur_func(env);
struct bpf_verifier_log *log = &env->log;
struct ref_obj_desc ref_obj = {};
+ const struct btf_param *args;
+ const struct btf_type *func, *func_proto;
u32 i;
int ret, err;
ret = btf_prepare_func_args(env, subprog);
if (ret) {
if (bpf_in_stack_arg_cnt(sub) > 0) {
- err = check_outgoing_stack_args(env, caller, sub->arg_cnt);
+ err = check_outgoing_stack_args(env, caller, sub->arg_cnt,
+ bpf_verifier_subprog_name(env, subprog),
+ NULL, NULL);
if (err)
return err;
}
return ret;
}
- ret = check_outgoing_stack_args(env, caller, sub->arg_cnt);
+ func = btf_type_by_id(btf, env->prog->aux->func_info[subprog].type_id);
+ func_proto = btf_type_by_id(btf, func->type);
+ args = btf_params(func_proto);
+ ret = check_outgoing_stack_args(env, caller, sub->arg_cnt,
+ bpf_verifier_subprog_name(env, subprog), btf, args);
if (ret)
return ret;
@@ -12372,7 +12402,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
return -ENOTSUPP;
}
- ret = check_outgoing_stack_args(env, caller, nargs);
+ ret = check_outgoing_stack_args(env, caller, nargs, func_name, btf, args);
if (ret)
return ret;
@@ -14103,9 +14133,8 @@ static int sanitize_check_bounds(struct bpf_verifier_env *env,
* If we return -EACCES, caller may want to try again treating pointer as a
* scalar. So we only emit a diagnostic if !env->allow_ptr_leaks.
*/
-static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
- struct bpf_insn *insn,
- const struct bpf_reg_state *ptr_reg,
+static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, struct bpf_insn *insn,
+ u32 ptr_regno, const struct bpf_reg_state *ptr_reg,
const struct bpf_reg_state *off_reg)
{
struct bpf_verifier_state *vstate = env->cur_state;
@@ -14117,6 +14146,7 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
struct bpf_sanitize_info info = {};
u8 opcode = BPF_OP(insn->code);
u32 dst = insn->dst_reg;
+ const char *reason;
int ret, bounds_ret;
dst_reg = ®s[dst];
@@ -14140,12 +14170,31 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
verbose(env,
"R%d 32-bit pointer arithmetic prohibited\n",
dst);
+ reason = bpf_diag_scratch_printf(env, 0,
+ "R%d holds %s. 32-bit ALU operations on pointers "
+ "discard pointer tracking, so the verifier cannot "
+ "keep the result as a safe pointer.",
+ ptr_regno,
+ bpf_diag_reg_type_plain(env, ptr_reg->type));
+ bpf_diag_report_register_type(env, env->insn_idx, ptr_regno,
+ "32-bit pointer arithmetic", reason,
+ "Use a 64-bit ALU instruction with an allowed, "
+ "bounded scalar offset.");
return -EACCES;
}
if (ptr_reg->type & PTR_MAYBE_NULL) {
verbose(env, "R%d pointer arithmetic on %s prohibited, null-check it first\n",
dst, reg_type_str(env, ptr_reg->type));
+ reason = bpf_diag_scratch_printf(env, 0,
+ "R%d may be NULL (%s). Pointer arithmetic is "
+ "allowed only after the program proves the "
+ "pointer is non-NULL on this path.",
+ ptr_regno, reg_type_str(env, ptr_reg->type));
+ bpf_diag_report_register_type(env, env->insn_idx, ptr_regno,
+ "pointer arithmetic before NULL check", reason,
+ "Make sure that a NULL check precedes any arithmetic "
+ "performed on the pointer.");
return -EACCES;
}
@@ -14182,6 +14231,13 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
default:
verbose(env, "R%d pointer arithmetic on %s prohibited\n",
dst, reg_type_str(env, ptr_reg->type));
+ reason = bpf_diag_scratch_printf(
+ env, 0, "R%d holds %s. This pointer kind does not allow offset arithmetic.",
+ ptr_regno, bpf_diag_reg_type_plain(env, ptr_reg->type));
+ bpf_diag_report_register_type(env, env->insn_idx, ptr_regno,
+ "pointer arithmetic is not allowed", reason,
+ "Do not change this pointer's offset; use it only in "
+ "operations accepted for its kind.");
return -EACCES;
}
@@ -14191,9 +14247,30 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
dst_reg->type = ptr_reg->type;
dst_reg->id = ptr_reg->id;
- if (!check_reg_sane_offset_scalar(env, off_reg, ptr_reg->type) ||
- !check_reg_sane_offset_ptr(env, ptr_reg, ptr_reg->type))
+ if (!check_reg_sane_offset_scalar(env, off_reg, ptr_reg->type)) {
+ reason = bpf_diag_scratch_printf(env, 0,
+ "The scalar offset used with R%d is unbounded or "
+ "outside the verifier's safe pointer-offset range "
+ "[-%u, %u].",
+ ptr_regno, BPF_MAX_VAR_OFF, BPF_MAX_VAR_OFF);
+ bpf_diag_report_register_type(env, env->insn_idx, ptr_regno,
+ "pointer offset is not safe", reason,
+ "Clamp or bounds-check the scalar offset before "
+ "applying it to the pointer.");
return -EINVAL;
+ }
+ if (!check_reg_sane_offset_ptr(env, ptr_reg, ptr_reg->type)) {
+ reason = bpf_diag_scratch_printf(env, 0,
+ "R%d already has an offset outside the verifier's "
+ "safe range [-%u, %u] for %s.",
+ ptr_regno, BPF_MAX_VAR_OFF, BPF_MAX_VAR_OFF,
+ bpf_diag_reg_type_plain(env, ptr_reg->type));
+ bpf_diag_report_register_type(env, env->insn_idx, ptr_regno,
+ "pointer offset is not safe", reason,
+ "Keep the base pointer within the verifier's allowed "
+ "offset range before applying more arithmetic.");
+ return -EINVAL;
+ }
/* pointer types do not carry 32-bit bounds at the moment. */
__mark_reg32_unbounded(dst_reg);
@@ -14236,6 +14313,17 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
/* scalar -= pointer. Creates an unknown scalar */
verbose(env, "R%d tried to subtract pointer from scalar\n",
dst);
+ reason = bpf_diag_scratch_printf(env, 0,
+ "This operation subtracts pointer "
+ "register R%d from scalar register R%d. "
+ "The verifier only tracks "
+ "pointer-minus-scalar arithmetic for "
+ "allowed pointer types.",
+ ptr_regno, dst);
+ bpf_diag_report_register_type(env, env->insn_idx, ptr_regno,
+ "pointer subtracted from scalar", reason,
+ "Keep the pointer as the base; only add or "
+ "subtract bounded scalars when permitted.");
return -EACCES;
}
/* We don't allow subtraction from FP, because (according to
@@ -14245,6 +14333,15 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
if (ptr_reg->type == PTR_TO_STACK) {
verbose(env, "R%d subtraction from stack pointer prohibited\n",
dst);
+ reason = bpf_diag_scratch_printf(env, 0,
+ "R%d is a stack pointer. The verifier "
+ "does not allow BPF_SUB to move stack "
+ "pointers.",
+ ptr_regno);
+ bpf_diag_report_register_type(env, env->insn_idx, ptr_regno,
+ "subtraction from stack pointer", reason,
+ "Use addition from R10 to form stack "
+ "addresses within the tracked stack frame.");
return -EACCES;
}
dst_reg->r64 = cnum64_add(ptr_reg->r64, cnum64_negate(off_reg->r64));
@@ -14270,16 +14367,49 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
/* bitwise ops on pointers are troublesome, prohibit. */
verbose(env, "R%d bitwise operator %s on pointer prohibited\n",
dst, bpf_alu_string[opcode >> 4]);
+ reason = bpf_diag_scratch_printf(env, 0,
+ "R%d holds %s. Bitwise operator %s would destroy "
+ "the pointer value the verifier is tracking.",
+ ptr_regno,
+ bpf_diag_reg_type_plain(env, ptr_reg->type),
+ bpf_alu_string[opcode >> 4]);
+ bpf_diag_report_register_type(env, env->insn_idx, ptr_regno,
+ "bitwise operation on pointer", reason,
+ "Do bitwise operations on scalar values, not on "
+ "pointer-valued registers.");
return -EACCES;
default:
/* other operators (e.g. MUL,LSH) produce non-pointer results */
verbose(env, "R%d pointer arithmetic with %s operator prohibited\n",
dst, bpf_alu_string[opcode >> 4]);
+ reason = bpf_diag_scratch_printf(env, 0,
+ "R%d holds %s. Operator %s is not one of the "
+ "limited pointer arithmetic operations the "
+ "verifier can track.",
+ ptr_regno,
+ bpf_diag_reg_type_plain(env, ptr_reg->type),
+ bpf_alu_string[opcode >> 4]);
+ bpf_diag_report_register_type(env, env->insn_idx, ptr_regno,
+ "invalid pointer arithmetic operator", reason,
+ "Use only verifier-supported addition or subtraction "
+ "with a bounded scalar offset, or perform this "
+ "operation on a scalar value.");
return -EACCES;
}
- if (!check_reg_sane_offset_ptr(env, dst_reg, ptr_reg->type))
+ if (!check_reg_sane_offset_ptr(env, dst_reg, ptr_reg->type)) {
+ reason = bpf_diag_scratch_printf(env, 0,
+ "After this arithmetic, R%d would be outside the "
+ "verifier's safe offset range [-%u, %u] for %s.",
+ dst, BPF_MAX_VAR_OFF, BPF_MAX_VAR_OFF,
+ bpf_diag_reg_type_plain(env, ptr_reg->type));
+ bpf_diag_report_register_type(env, env->insn_idx, ptr_regno,
+ "pointer offset is not safe", reason,
+ "Tighten the scalar bounds before the arithmetic so "
+ "the resulting pointer remains within the allowed "
+ "range.");
return -EINVAL;
+ }
reg_bounds_sync(dst_reg);
bounds_ret = sanitize_check_bounds(env, insn, dst_reg);
if (bounds_ret == -EACCES)
@@ -15250,16 +15380,15 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
err = mark_chain_precision(env, insn->dst_reg);
if (err)
return err;
- return adjust_ptr_min_max_vals(env, insn,
- src_reg, dst_reg);
+ return adjust_ptr_min_max_vals(env, insn, insn->src_reg, src_reg,
+ dst_reg);
}
} else if (ptr_reg) {
/* pointer += scalar */
err = mark_chain_precision(env, insn->src_reg);
if (err)
return err;
- return adjust_ptr_min_max_vals(env, insn,
- dst_reg, src_reg);
+ return adjust_ptr_min_max_vals(env, insn, insn->dst_reg, dst_reg, src_reg);
} else if (dst_reg->precise) {
/* if dst_reg is precise, src_reg should be precise as well */
err = mark_chain_precision(env, insn->src_reg);
@@ -15274,8 +15403,7 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
__mark_reg_known(&off_reg, insn->imm);
src_reg = &off_reg;
if (ptr_reg) /* pointer += K */
- return adjust_ptr_min_max_vals(env, insn,
- ptr_reg, src_reg);
+ return adjust_ptr_min_max_vals(env, insn, insn->dst_reg, ptr_reg, src_reg);
}
/* Got here implies adding two SCALAR_VALUEs */
--
2.53.0
next prev parent reply other threads:[~2026-07-13 15:39 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-13 15:38 [PATCH bpf-next v3 00/17] Redesign Verification Errors Kumar Kartikeya Dwivedi
2026-07-13 15:38 ` [PATCH bpf-next v3 01/17] bpf: Add verifier diagnostics report helpers Kumar Kartikeya Dwivedi
2026-07-13 15:50 ` sashiko-bot
2026-07-13 15:38 ` [PATCH bpf-next v3 02/17] bpf: Add source and instruction diagnostic context Kumar Kartikeya Dwivedi
2026-07-13 15:38 ` [PATCH bpf-next v3 03/17] bpf: Add verifier diagnostic event log Kumar Kartikeya Dwivedi
2026-07-13 15:54 ` sashiko-bot
2026-07-13 15:38 ` [PATCH bpf-next v3 04/17] bpf: Prune verifier diagnostics when switching paths Kumar Kartikeya Dwivedi
2026-07-13 16:02 ` sashiko-bot
2026-07-13 15:38 ` [PATCH bpf-next v3 05/17] bpf: Track verifier register diagnostic events Kumar Kartikeya Dwivedi
2026-07-13 16:06 ` sashiko-bot
2026-07-13 15:38 ` [PATCH bpf-next v3 06/17] bpf: Track verifier reference " Kumar Kartikeya Dwivedi
2026-07-13 15:38 ` [PATCH bpf-next v3 07/17] bpf: Track verifier context " Kumar Kartikeya Dwivedi
2026-07-13 15:38 ` Kumar Kartikeya Dwivedi [this message]
2026-07-13 15:38 ` [PATCH bpf-next v3 09/17] bpf: Report Memory Safety bounds errors Kumar Kartikeya Dwivedi
2026-07-13 15:38 ` [PATCH bpf-next v3 10/17] bpf: Report Resource Lifetime reference leaks Kumar Kartikeya Dwivedi
2026-07-13 16:04 ` sashiko-bot
2026-07-13 15:39 ` [PATCH bpf-next v3 11/17] bpf: Report Call Type Safety argument errors Kumar Kartikeya Dwivedi
2026-07-13 15:51 ` sashiko-bot
2026-07-13 15:39 ` [PATCH bpf-next v3 12/17] bpf: Report Execution Context Safety errors Kumar Kartikeya Dwivedi
2026-07-13 15:53 ` sashiko-bot
2026-07-13 15:39 ` [PATCH bpf-next v3 13/17] bpf: Report Program Structure CFG errors Kumar Kartikeya Dwivedi
2026-07-13 15:39 ` [PATCH bpf-next v3 14/17] bpf: Report Policy helper and kfunc errors Kumar Kartikeya Dwivedi
2026-07-13 16:09 ` sashiko-bot
2026-07-13 15:39 ` [PATCH bpf-next v3 15/17] bpf: Report Verifier Limit errors Kumar Kartikeya Dwivedi
2026-07-13 15:39 ` [PATCH bpf-next v3 16/17] bpf: Report Verifier Internal errors Kumar Kartikeya Dwivedi
2026-07-13 15:39 ` [PATCH bpf-next v3 17/17] bpf: Gate verifier diagnostics on log level Kumar Kartikeya Dwivedi
2026-07-14 3:36 ` kernel test robot
2026-07-14 6:10 ` [syzbot ci] Re: Redesign Verification Errors syzbot ci
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260713153910.2556007-9-memxor@gmail.com \
--to=memxor@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=emil@etsalapatis.com \
--cc=kernel-team@meta.com \
--cc=kkd@meta.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox