[PATCH bpf-next v2 1/2] bpf: Support multi-level pointer params via PTR_TO_MEM for trampolines
From: Slava Imameev
Date: Tue Feb 17 2026 - 17:15:49 EST
Add BPF verifier support for multi-level pointer parameters and return
values in BPF trampolines. The implementation treats these parameters as
PTR_TO_MEM with read-only semantics, applying either untrusted or trusted
access patterns while honoring __nullable annotations. Runtime safety is
ensured through existing exception handling mechanisms for untrusted
memory reads, with the verifier enforcing bounds checking and null
validation.
Signed-off-by: Slava Imameev <slava.imameev@xxxxxxxxxxxxxxx>
---
include/linux/bpf.h | 3 ++-
kernel/bpf/btf.c | 54 ++++++++++++++++++++++++++++++++++++-------
kernel/bpf/verifier.c | 4 +++-
3 files changed, 51 insertions(+), 10 deletions(-)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index cd9b96434904..6dd6a85cf13a 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1052,7 +1052,8 @@ struct bpf_insn_access_aux {
struct btf *btf;
u32 btf_id;
u32 ref_obj_id;
- };
+ }; /* base type PTR_TO_BTF_ID */
+ u32 mem_size; /* base type PTR_TO_MEM */
};
struct bpf_verifier_log *log; /* for verbose logs */
bool is_retval; /* is accessing function return value ? */
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 7708958e3fb8..7b7cb30cdc98 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -760,6 +760,21 @@ const struct btf_type *btf_type_resolve_func_ptr(const struct btf *btf,
return NULL;
}
+static bool is_multilevel_ptr(const struct btf *btf, const struct btf_type *t)
+{
+ u32 depth = 0;
+
+ if (!btf_type_is_ptr(t))
+ return false;
+
+ do {
+ depth += 1;
+ t = btf_type_skip_modifiers(btf, t->type, NULL);
+ } while (btf_type_is_ptr(t) && depth < 2);
+
+ return depth > 1;
+}
+
/* Types that act only as a source, not sink or intermediate
* type when resolving.
*/
@@ -6790,6 +6805,7 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
const char *tag_value;
u32 nr_args, arg;
int i, ret;
+ bool trusted, nullable;
if (off % 8) {
bpf_log(log, "func '%s' offset %d is not multiple of 8\n",
@@ -6927,12 +6943,8 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
}
}
- info->reg_type = PTR_TO_BTF_ID;
- if (prog_args_trusted(prog))
- info->reg_type |= PTR_TRUSTED;
-
- if (btf_param_match_suffix(btf, &args[arg], "__nullable"))
- info->reg_type |= PTR_MAYBE_NULL;
+ trusted = prog_args_trusted(prog);
+ nullable = btf_param_match_suffix(btf, &args[arg], "__nullable");
if (prog->expected_attach_type == BPF_TRACE_RAW_TP) {
struct btf *btf = prog->aux->attach_btf;
@@ -6953,7 +6965,7 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
if (strcmp(tname, raw_tp_null_args[i].func))
continue;
if (raw_tp_null_args[i].mask & (0x1ULL << (arg * 4)))
- info->reg_type |= PTR_MAYBE_NULL;
+ nullable = true;
/* Is the current arg IS_ERR? */
if (raw_tp_null_args[i].mask & (0x2ULL << (arg * 4)))
ptr_err_raw_tp = true;
@@ -6964,9 +6976,35 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
* argument as PTR_MAYBE_NULL.
*/
if (i == ARRAY_SIZE(raw_tp_null_args) && btf_is_module(btf))
- info->reg_type |= PTR_MAYBE_NULL;
+ nullable = true;
}
+ if (is_multilevel_ptr(btf, t)) {
+ /* If it can be IS_ERR at runtime, mark as scalar. */
+ if (ptr_err_raw_tp) {
+ bpf_log(log, "marking func '%s' pointer arg%d as scalar as it may encode error",
+ tname, arg);
+ info->reg_type = SCALAR_VALUE;
+ } else {
+ info->reg_type = PTR_TO_MEM | MEM_RDONLY;
+ if (!trusted)
+ info->reg_type |= PTR_UNTRUSTED;
+ /* for return value be conservative and mark it nullable */
+ if (nullable || arg == nr_args)
+ info->reg_type |= PTR_MAYBE_NULL;
+ /* this is a pointer to another pointer */
+ info->mem_size = sizeof(void *);
+ }
+ return true;
+ }
+
+ info->reg_type = PTR_TO_BTF_ID;
+ if (trusted)
+ info->reg_type |= PTR_TRUSTED;
+
+ if (nullable)
+ info->reg_type |= PTR_MAYBE_NULL;
+
if (tgt_prog) {
enum bpf_prog_type tgt_type;
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 0162f946032f..5de56336e169 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -6311,7 +6311,7 @@ static int check_ctx_access(struct bpf_verifier_env *env, int insn_idx, int off,
off);
return -EACCES;
}
- } else {
+ } else if (base_type(info->reg_type) != PTR_TO_MEM) {
env->insn_aux_data[insn_idx].ctx_field_size = info->ctx_field_size;
}
/* remember the offset of last byte accessed in ctx */
@@ -7771,6 +7771,8 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
regs[value_regno].btf = info.btf;
regs[value_regno].btf_id = info.btf_id;
regs[value_regno].ref_obj_id = info.ref_obj_id;
+ } else if (base_type(info.reg_type) == PTR_TO_MEM) {
+ regs[value_regno].mem_size = info.mem_size;
}
}
regs[value_regno].type = info.reg_type;
--
2.50.1 (Apple Git-155)