* [PATCH bpf-next v1 0/4] Allow variable offsets for syscall PTR_TO_CTX
@ 2026-03-17 11:18 Kumar Kartikeya Dwivedi
2026-03-17 11:18 ` [PATCH bpf-next v1 1/4] bpf: Support " Kumar Kartikeya Dwivedi
` (3 more replies)
0 siblings, 4 replies; 11+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-03-17 11:18 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Martin KaFai Lau, Eduard Zingerman, Tejun Heo, Dan Schatzberg,
kkd, kernel-team
Enable pointer modification with variable offsets accumulated in the
register for PTR_TO_CTX for syscall programs where it won't be
rewritten, and the context is user-supplied and checked against the max
offset. See patches for details. Fixed offset support landed in [0].
By combining this set with [0], examples like the one below should
succeed verification.
SEC("syscall")
int prog(void *ctx) {
int *arr = ctx;
int i;
bpf_for(i, 0, 100)
arr[i] *= i;
return 0;
}
[0]: https://lore.kernel.org/bpf/20260227005725.1247305-1-memxor@gmail.com
Kumar Kartikeya Dwivedi (4):
bpf: Support variable offsets for syscall PTR_TO_CTX
selftests/bpf: Adjust syscall ctx variable offset tests
bpf: Reject modified syscall PTR_TO_CTX for global subprogs
selftests/bpf: Test modified syscall ctx for global subprog
kernel/bpf/verifier.c | 62 ++-
.../selftests/bpf/prog_tests/verifier.c | 34 +-
.../selftests/bpf/progs/verifier_ctx.c | 356 +++++++++++++++---
.../bpf/progs/verifier_global_subprogs.c | 43 ++-
.../selftests/bpf/test_kmods/bpf_testmod.c | 2 +-
5 files changed, 409 insertions(+), 88 deletions(-)
base-commit: 2364959abecb052d7b798ae51d0626edec8b9288
--
2.52.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH bpf-next v1 1/4] bpf: Support variable offsets for syscall PTR_TO_CTX
2026-03-17 11:18 [PATCH bpf-next v1 0/4] Allow variable offsets for syscall PTR_TO_CTX Kumar Kartikeya Dwivedi
@ 2026-03-17 11:18 ` Kumar Kartikeya Dwivedi
2026-03-17 12:09 ` bot+bpf-ci
2026-03-17 16:45 ` Emil Tsalapatis
2026-03-17 11:18 ` [PATCH bpf-next v1 2/4] selftests/bpf: Adjust syscall ctx variable offset tests Kumar Kartikeya Dwivedi
` (2 subsequent siblings)
3 siblings, 2 replies; 11+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-03-17 11:18 UTC (permalink / raw)
To: bpf
Cc: Tejun Heo, Dan Schatzberg, Alexei Starovoitov, Andrii Nakryiko,
Daniel Borkmann, Martin KaFai Lau, Eduard Zingerman, kkd,
kernel-team
Allow accessing PTR_TO_CTX with variable offsets in syscall programs.
Fixed offsets are already enabled for all program types that do not
convert their ctx accesses, since the changes we made in the commit
de6c7d99f898 ("bpf: Relax fixed offset check for PTR_TO_CTX"). Note
that we also lift the restriction on passing syscall context into
helpers, which was not permitted before, and passing modified syscall
context into kfuncs.
The structure of check_mem_access can be mostly shared and preserved,
but we must use check_mem_region_access to correctly verify access with
variable offsets.
The check made in check_helper_mem_access is hardened to only allow
PTR_TO_CTX for syscall programs to be passed in as helper memory. This
was the original intention of the existing code anyway, and it makes
little sense for other program types' context to be utilized as a memory
buffer. In case a convincing example presents itself in the future, this
check can be relaxed further.
We also no longer use the last-byte access to simulate helper memory
access, but instead go through check_mem_region_access. Since this no
longer updates our max_ctx_offset, we must do so manually, to keep track
of the maximum offset at which the program ctx may be accessed.
Cc: Tejun Heo <tj@kernel.org>
Cc: Dan Schatzberg <dschatzberg@meta.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
kernel/bpf/verifier.c | 51 +++++++++++--------
.../bpf/progs/verifier_global_subprogs.c | 1 -
2 files changed, 31 insertions(+), 21 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 01c18f4268de..50639bb69d91 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -7843,6 +7843,7 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
* Program types that don't rewrite ctx accesses can safely
* dereference ctx pointers with fixed offsets.
*/
+ bool var_off_ok = resolve_prog_type(env->prog) == BPF_PROG_TYPE_SYSCALL;
bool fixed_off_ok = !env->ops->convert_ctx_access;
struct bpf_retval_range range;
struct bpf_insn_access_aux info = {
@@ -7857,15 +7858,25 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
return -EACCES;
}
- err = __check_ptr_off_reg(env, reg, regno, fixed_off_ok);
- if (err < 0)
- return err;
+ if (var_off_ok) {
+ err = check_mem_region_access(env, regno, off, size, U16_MAX, false);
+ if (err)
+ return err;
+ } else {
+ err = __check_ptr_off_reg(env, reg, regno, fixed_off_ok);
+ if (err < 0)
+ return err;
+ }
/*
* Fold the register's constant offset into the insn offset so
- * that is_valid_access() sees the true effective offset.
+ * that is_valid_access() sees the true effective offset. If the
+ * register's offset is not constant, then the maximum possible
+ * offset is simulated.
*/
- if (fixed_off_ok)
+ if (var_off_ok)
+ off += reg->umax_value;
+ else if (fixed_off_ok)
off += reg->var_off.value;
err = check_ctx_access(env, insn_idx, off, size, t, &info);
if (err)
@@ -8442,22 +8453,16 @@ static int check_helper_mem_access(struct bpf_verifier_env *env, int regno,
return check_ptr_to_btf_access(env, regs, regno, 0,
access_size, BPF_READ, -1);
case PTR_TO_CTX:
- /* in case the function doesn't know how to access the context,
- * (because we are in a program of type SYSCALL for example), we
- * can not statically check its size.
- * Dynamically check it now.
- */
- if (!env->ops->convert_ctx_access) {
- int offset = access_size - 1;
-
- /* Allow zero-byte read from PTR_TO_CTX */
- if (access_size == 0)
- return zero_size_allowed ? 0 : -EACCES;
-
- return check_mem_access(env, env->insn_idx, regno, offset, BPF_B,
- access_type, -1, false, false);
+ /* Only permit reading or writing syscall context using helper calls. */
+ if (resolve_prog_type(env->prog) == BPF_PROG_TYPE_SYSCALL) {
+ int err = check_mem_region_access(env, regno, 0, access_size, U16_MAX,
+ zero_size_allowed);
+ if (err)
+ return err;
+ if (env->prog->aux->max_ctx_offset < reg->umax_value + access_size)
+ env->prog->aux->max_ctx_offset = reg->umax_value + access_size;
+ return 0;
}
-
fallthrough;
default: /* scalar_value or invalid ptr */
/* Allow zero-byte read from NULL, regardless of pointer type */
@@ -9401,6 +9406,7 @@ static const struct bpf_reg_types mem_types = {
PTR_TO_MEM | MEM_RINGBUF,
PTR_TO_BUF,
PTR_TO_BTF_ID | PTR_TRUSTED,
+ PTR_TO_CTX,
},
};
@@ -9710,6 +9716,11 @@ static int check_func_arg_reg_off(struct bpf_verifier_env *env,
* still need to do checks instead of returning.
*/
return __check_ptr_off_reg(env, reg, regno, true);
+ case PTR_TO_CTX:
+ /* Allow fixed and variable offsets for syscall context. */
+ if (resolve_prog_type(env->prog) == BPF_PROG_TYPE_SYSCALL)
+ return 0;
+ fallthrough;
default:
return __check_ptr_off_reg(env, reg, regno, false);
}
diff --git a/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c b/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
index f02012a2fbaa..2250fc31574d 100644
--- a/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
+++ b/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
@@ -134,7 +134,6 @@ __noinline __weak int subprog_user_anon_mem(user_struct_t *t)
SEC("?tracepoint")
__failure __log_level(2)
-__msg("invalid bpf_context access")
__msg("Caller passes invalid args into func#1 ('subprog_user_anon_mem')")
int anon_user_mem_invalid(void *ctx)
{
--
2.52.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH bpf-next v1 2/4] selftests/bpf: Adjust syscall ctx variable offset tests
2026-03-17 11:18 [PATCH bpf-next v1 0/4] Allow variable offsets for syscall PTR_TO_CTX Kumar Kartikeya Dwivedi
2026-03-17 11:18 ` [PATCH bpf-next v1 1/4] bpf: Support " Kumar Kartikeya Dwivedi
@ 2026-03-17 11:18 ` Kumar Kartikeya Dwivedi
2026-03-17 18:24 ` Emil Tsalapatis
2026-03-17 11:18 ` [PATCH bpf-next v1 3/4] bpf: Reject modified syscall PTR_TO_CTX for global subprogs Kumar Kartikeya Dwivedi
2026-03-17 11:18 ` [PATCH bpf-next v1 4/4] selftests/bpf: Test modified syscall ctx for global subprog Kumar Kartikeya Dwivedi
3 siblings, 1 reply; 11+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-03-17 11:18 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Martin KaFai Lau, Eduard Zingerman, Tejun Heo, Dan Schatzberg,
kkd, kernel-team
Add various tests to exercise fixed and variable offsets on PTR_TO_CTX
for syscall programs, and cover disallowed cases for other program types
lacking convert_ctx_access callback. Load verifier_ctx with CAP_SYS_ADMIN
so that kfunc related logic can be tested. While at it, convert assembly
tests to C.
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
.../selftests/bpf/prog_tests/verifier.c | 34 +-
.../selftests/bpf/progs/verifier_ctx.c | 356 +++++++++++++++---
.../selftests/bpf/test_kmods/bpf_testmod.c | 2 +-
3 files changed, 325 insertions(+), 67 deletions(-)
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index 8cdfd74c95d7..04d5f46264a3 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -126,29 +126,35 @@ struct test_val {
__maybe_unused
static void run_tests_aux(const char *skel_name,
skel_elf_bytes_fn elf_bytes_factory,
- pre_execution_cb pre_execution_cb)
+ pre_execution_cb pre_execution_cb,
+ bool drop_sys_admin)
{
struct test_loader tester = {};
__u64 old_caps;
int err;
- /* test_verifier tests are executed w/o CAP_SYS_ADMIN, do the same here */
- err = cap_disable_effective(1ULL << CAP_SYS_ADMIN, &old_caps);
- if (err) {
- PRINT_FAIL("failed to drop CAP_SYS_ADMIN: %i, %s\n", err, strerror(-err));
- return;
+ if (drop_sys_admin) {
+ /* test_verifier tests are executed w/o CAP_SYS_ADMIN, do the same here */
+ err = cap_disable_effective(1ULL << CAP_SYS_ADMIN, &old_caps);
+ if (err) {
+ PRINT_FAIL("failed to drop CAP_SYS_ADMIN: %i, %s\n", err, strerror(-err));
+ return;
+ }
}
test_loader__set_pre_execution_cb(&tester, pre_execution_cb);
test_loader__run_subtests(&tester, skel_name, elf_bytes_factory);
test_loader_fini(&tester);
- err = cap_enable_effective(old_caps, NULL);
- if (err)
- PRINT_FAIL("failed to restore CAP_SYS_ADMIN: %i, %s\n", err, strerror(-err));
+ if (drop_sys_admin) {
+ err = cap_enable_effective(old_caps, NULL);
+ if (err)
+ PRINT_FAIL("failed to restore CAP_SYS_ADMIN: %i, %s\n", err, strerror(-err));
+ }
}
-#define RUN(skel) run_tests_aux(#skel, skel##__elf_bytes, NULL)
+#define RUN(skel) run_tests_aux(#skel, skel##__elf_bytes, NULL, true)
+#define RUN_WITH_CAP_SYS_ADMIN(skel) run_tests_aux(#skel, skel##__elf_bytes, NULL, false)
void test_verifier_align(void) { RUN(verifier_align); }
void test_verifier_and(void) { RUN(verifier_and); }
@@ -173,7 +179,7 @@ void test_verifier_cgroup_skb(void) { RUN(verifier_cgroup_skb); }
void test_verifier_cgroup_storage(void) { RUN(verifier_cgroup_storage); }
void test_verifier_const(void) { RUN(verifier_const); }
void test_verifier_const_or(void) { RUN(verifier_const_or); }
-void test_verifier_ctx(void) { RUN(verifier_ctx); }
+void test_verifier_ctx(void) { RUN_WITH_CAP_SYS_ADMIN(verifier_ctx); }
void test_verifier_ctx_sk_msg(void) { RUN(verifier_ctx_sk_msg); }
void test_verifier_d_path(void) { RUN(verifier_d_path); }
void test_verifier_default_trusted_ptr(void) { RUN_TESTS(verifier_default_trusted_ptr); }
@@ -293,7 +299,8 @@ void test_verifier_array_access(void)
{
run_tests_aux("verifier_array_access",
verifier_array_access__elf_bytes,
- init_array_access_maps);
+ init_array_access_maps,
+ true);
}
void test_verifier_async_cb_context(void) { RUN(verifier_async_cb_context); }
@@ -306,5 +313,6 @@ void test_verifier_value_ptr_arith(void)
{
run_tests_aux("verifier_value_ptr_arith",
verifier_value_ptr_arith__elf_bytes,
- init_value_ptr_arith_maps);
+ init_value_ptr_arith_maps,
+ true);
}
diff --git a/tools/testing/selftests/bpf/progs/verifier_ctx.c b/tools/testing/selftests/bpf/progs/verifier_ctx.c
index 371780290c0d..c054c8cb7242 100644
--- a/tools/testing/selftests/bpf/progs/verifier_ctx.c
+++ b/tools/testing/selftests/bpf/progs/verifier_ctx.c
@@ -4,6 +4,10 @@
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+const char ctx_strncmp_target[] = "ctx";
+const char ctx_snprintf_fmt[] = "";
SEC("tc")
__description("context stores via BPF_ATOMIC")
@@ -69,7 +73,6 @@ __naked void ctx_pointer_to_helper_1(void)
SEC("socket")
__description("pass modified ctx pointer to helper, 2")
__failure __msg("negative offset ctx ptr R1 off=-612 disallowed")
-__failure_unpriv __msg_unpriv("negative offset ctx ptr R1 off=-612 disallowed")
__naked void ctx_pointer_to_helper_2(void)
{
asm volatile (" \
@@ -295,77 +298,324 @@ padding_access("sk_reuseport", sk_reuseport_md, hash, 4);
SEC("syscall")
__description("syscall: write to ctx with fixed offset")
__success
-__naked void syscall_ctx_fixed_off_write(void)
+int syscall_ctx_fixed_off_write(void *ctx)
{
- asm volatile (" \
- r0 = 0; \
- *(u32*)(r1 + 0) = r0; \
- r1 += 4; \
- *(u32*)(r1 + 0) = r0; \
- exit; \
-" ::: __clobber_all);
+ char *p = ctx;
+
+ *(__u32 *)p = 0;
+ *(__u32 *)(p + 4) = 0;
+ return 0;
+}
+
+SEC("syscall")
+__description("syscall: read ctx with fixed offset")
+__success
+int syscall_ctx_fixed_off_read(void *ctx)
+{
+ char *p = ctx;
+ volatile __u32 val;
+
+ val = *(__u32 *)(p + 4);
+ (void)val;
+ return 0;
+}
+
+SEC("syscall")
+__description("syscall: read ctx with variable offset")
+__success
+int syscall_ctx_var_off_read(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+ volatile __u32 val;
+
+ off &= 0xfc;
+ p += off;
+ val = *(__u32 *)p;
+ (void)val;
+ return 0;
+}
+
+SEC("syscall")
+__description("syscall: write ctx with variable offset")
+__success
+int syscall_ctx_var_off_write(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off &= 0xfc;
+ p += off;
+ *(__u32 *)p = 0;
+ return 0;
+}
+
+SEC("syscall")
+__description("syscall: reject negative variable offset ctx access")
+__failure __msg("min value is negative")
+int syscall_ctx_neg_var_off(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off &= 4;
+ p -= off;
+ return *(__u32 *)p;
+}
+
+SEC("syscall")
+__description("syscall: reject unbounded variable offset ctx access")
+__failure __msg("unbounded memory access")
+int syscall_ctx_unbounded_var_off(void *ctx)
+{
+ __u64 off = (__u32)bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off <<= 2;
+ p += off;
+ return *(__u32 *)p;
+}
+
+SEC("syscall")
+__description("syscall: helper read ctx with fixed offset")
+__success
+int syscall_ctx_helper_fixed_off_read(void *ctx)
+{
+ char *p = ctx;
+
+ p += 4;
+ return bpf_strncmp(p, 4, ctx_strncmp_target);
+}
+
+SEC("syscall")
+__description("syscall: helper write ctx with fixed offset")
+__success
+int syscall_ctx_helper_fixed_off_write(void *ctx)
+{
+ char *p = ctx;
+
+ p += 4;
+ return bpf_probe_read_kernel(p, 4, 0);
+}
+
+SEC("syscall")
+__description("syscall: helper read ctx with variable offset")
+__success
+int syscall_ctx_helper_var_off_read(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off &= 0xfc;
+ p += off;
+ return bpf_strncmp(p, 4, ctx_strncmp_target);
+}
+
+SEC("syscall")
+__description("syscall: helper write ctx with variable offset")
+__success
+int syscall_ctx_helper_var_off_write(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off &= 0xfc;
+ p += off;
+ return bpf_probe_read_kernel(p, 4, 0);
+}
+
+SEC("syscall")
+__description("syscall: helper read zero-sized ctx access")
+__success
+int syscall_ctx_helper_zero_sized_read(void *ctx)
+{
+ return bpf_snprintf(0, 0, ctx_snprintf_fmt, ctx, 0);
+}
+
+SEC("syscall")
+__description("syscall: helper write zero-sized ctx access")
+__success
+int syscall_ctx_helper_zero_sized_write(void *ctx)
+{
+ return bpf_probe_read_kernel(ctx, 0, 0);
+}
+
+SEC("syscall")
+__description("syscall: kfunc access ctx with fixed offset")
+__success
+int syscall_ctx_kfunc_fixed_off(void *ctx)
+{
+ char *p = ctx;
+
+ p += 4;
+ bpf_kfunc_call_test_mem_len_pass1(p, 4);
+ return 0;
+}
+
+SEC("syscall")
+__description("syscall: kfunc access ctx with variable offset")
+__success
+int syscall_ctx_kfunc_var_off(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off &= 0xfc;
+ p += off;
+ bpf_kfunc_call_test_mem_len_pass1(p, 4);
+ return 0;
+}
+
+SEC("syscall")
+__description("syscall: kfunc access zero-sized ctx")
+__success
+int syscall_ctx_kfunc_zero_sized(void *ctx)
+{
+ bpf_kfunc_call_test_mem_len_pass1(ctx, 0);
+ return 0;
}
/*
- * Test that program types without convert_ctx_access can dereference
- * their ctx pointer after adding a fixed offset. Variable and negative
- * offsets should still be rejected.
+ * For non-syscall program types without convert_ctx_access, direct ctx
+ * dereference is still allowed after adding a fixed offset, while variable
+ * and negative direct accesses reject.
+ *
+ * Passing ctx as a helper or kfunc memory argument is only permitted for
+ * syscall programs, so the helper and kfunc cases below validate rejection
+ * for non-syscall ctx pointers at fixed, variable, and zero-sized accesses.
*/
-#define no_rewrite_ctx_access(type, name, off, ld_op) \
+#define no_rewrite_ctx_access(type, name, off, load_t) \
SEC(type) \
__description(type ": read ctx at fixed offset") \
__success \
- __naked void no_rewrite_##name##_fixed(void) \
+ int no_rewrite_##name##_fixed(void *ctx) \
{ \
- asm volatile (" \
- r1 += %[__off]; \
- r0 = *(" #ld_op " *)(r1 + 0); \
- r0 = 0; \
- exit;" \
- : \
- : __imm_const(__off, off) \
- : __clobber_all); \
+ char *p = ctx; \
+ volatile load_t val; \
+ \
+ val = *(load_t *)(p + off); \
+ (void)val; \
+ return 0; \
} \
SEC(type) \
__description(type ": reject variable offset ctx access") \
__failure __msg("variable ctx access var_off=") \
- __naked void no_rewrite_##name##_var(void) \
+ int no_rewrite_##name##_var(void *ctx) \
{ \
- asm volatile (" \
- r6 = r1; \
- call %[bpf_get_prandom_u32]; \
- r1 = r6; \
- r0 &= 4; \
- r1 += r0; \
- r0 = *(" #ld_op " *)(r1 + 0); \
- r0 = 0; \
- exit;" \
- : \
- : __imm(bpf_get_prandom_u32) \
- : __clobber_all); \
+ __u64 off_var = bpf_get_prandom_u32(); \
+ char *p = ctx; \
+ \
+ off_var &= 4; \
+ p += off_var; \
+ return *(load_t *)p; \
} \
SEC(type) \
__description(type ": reject negative offset ctx access") \
- __failure __msg("negative offset ctx ptr") \
- __naked void no_rewrite_##name##_neg(void) \
+ __failure __msg("invalid bpf_context access") \
+ int no_rewrite_##name##_neg(void *ctx) \
{ \
- asm volatile (" \
- r1 += %[__neg_off]; \
- r0 = *(" #ld_op " *)(r1 + 0); \
- r0 = 0; \
- exit;" \
- : \
- : __imm_const(__neg_off, -(off)) \
- : __clobber_all); \
+ char *p = ctx; \
+ \
+ p -= 612; \
+ return *(load_t *)p; \
+ } \
+ SEC(type) \
+ __description(type ": reject helper read ctx at fixed offset") \
+ __failure __msg("dereference of modified ctx ptr") \
+ int no_rewrite_##name##_helper_read_fixed(void *ctx) \
+ { \
+ char *p = ctx; \
+ \
+ p += off; \
+ return bpf_strncmp(p, 4, ctx_strncmp_target); \
+ } \
+ SEC(type) \
+ __description(type ": reject helper write ctx at fixed offset") \
+ __failure __msg("dereference of modified ctx ptr") \
+ int no_rewrite_##name##_helper_write_fixed(void *ctx) \
+ { \
+ char *p = ctx; \
+ \
+ p += off; \
+ return bpf_probe_read_kernel(p, 4, 0); \
+ } \
+ SEC(type) \
+ __description(type ": reject helper read ctx with variable offset") \
+ __failure __msg("variable ctx access var_off=") \
+ int no_rewrite_##name##_helper_read_var(void *ctx) \
+ { \
+ __u64 off_var = bpf_get_prandom_u32(); \
+ char *p = ctx; \
+ \
+ off_var &= 4; \
+ p += off_var; \
+ return bpf_strncmp(p, 4, ctx_strncmp_target); \
+ } \
+ SEC(type) \
+ __description(type ": reject helper write ctx with variable offset") \
+ __failure __msg("variable ctx access var_off=") \
+ int no_rewrite_##name##_helper_write_var(void *ctx) \
+ { \
+ __u64 off_var = bpf_get_prandom_u32(); \
+ char *p = ctx; \
+ \
+ off_var &= 4; \
+ p += off_var; \
+ return bpf_probe_read_kernel(p, 4, 0); \
+ } \
+ SEC(type) \
+ __description(type ": reject helper read zero-sized ctx access") \
+ __failure __msg("R4 type=ctx expected=fp") \
+ int no_rewrite_##name##_helper_read_zero(void *ctx) \
+ { \
+ return bpf_snprintf(0, 0, ctx_snprintf_fmt, ctx, 0); \
+ } \
+ SEC(type) \
+ __description(type ": reject helper write zero-sized ctx access") \
+ __failure __msg("R1 type=ctx expected=fp") \
+ int no_rewrite_##name##_helper_write_zero(void *ctx) \
+ { \
+ return bpf_probe_read_kernel(ctx, 0, 0); \
+ } \
+ SEC(type) \
+ __description(type ": reject kfunc ctx at fixed offset") \
+ __failure __msg("dereference of modified ctx ptr") \
+ int no_rewrite_##name##_kfunc_fixed(void *ctx) \
+ { \
+ char *p = ctx; \
+ \
+ p += off; \
+ bpf_kfunc_call_test_mem_len_pass1(p, 4); \
+ return 0; \
+ } \
+ SEC(type) \
+ __description(type ": reject kfunc ctx with variable offset") \
+ __failure __msg("variable ctx access var_off=") \
+ int no_rewrite_##name##_kfunc_var(void *ctx) \
+ { \
+ __u64 off_var = bpf_get_prandom_u32(); \
+ char *p = ctx; \
+ \
+ off_var &= 4; \
+ p += off_var; \
+ bpf_kfunc_call_test_mem_len_pass1(p, 4); \
+ return 0; \
+ } \
+ SEC(type) \
+ __description(type ": reject kfunc zero-sized ctx access") \
+ __failure __msg("R1 type=ctx expected=fp") \
+ int no_rewrite_##name##_kfunc_zero(void *ctx) \
+ { \
+ bpf_kfunc_call_test_mem_len_pass1(ctx, 0); \
+ return 0; \
}
-no_rewrite_ctx_access("syscall", syscall, 4, u32);
-no_rewrite_ctx_access("kprobe", kprobe, 8, u64);
-no_rewrite_ctx_access("tracepoint", tp, 8, u64);
-no_rewrite_ctx_access("raw_tp", raw_tp, 8, u64);
-no_rewrite_ctx_access("raw_tracepoint.w", raw_tp_w, 8, u64);
-no_rewrite_ctx_access("fentry/bpf_modify_return_test", fentry, 8, u64);
-no_rewrite_ctx_access("cgroup/dev", cgroup_dev, 4, u32);
-no_rewrite_ctx_access("netfilter", netfilter, offsetof(struct bpf_nf_ctx, skb), u64);
+no_rewrite_ctx_access("kprobe", kprobe, 8, __u64);
+no_rewrite_ctx_access("tracepoint", tp, 8, __u64);
+no_rewrite_ctx_access("raw_tp", raw_tp, 8, __u64);
+no_rewrite_ctx_access("raw_tracepoint.w", raw_tp_w, 8, __u64);
+no_rewrite_ctx_access("fentry/bpf_modify_return_test", fentry, 8, __u64);
+no_rewrite_ctx_access("cgroup/dev", cgroup_dev, 4, __u32);
+no_rewrite_ctx_access("netfilter", netfilter, offsetof(struct bpf_nf_ctx, skb), __u64);
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index 94edbd2afa67..f91b484d80f2 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -716,6 +716,7 @@ BTF_ID_FLAGS(func, bpf_iter_testmod_seq_next, KF_ITER_NEXT | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_iter_testmod_seq_destroy, KF_ITER_DESTROY)
BTF_ID_FLAGS(func, bpf_iter_testmod_seq_value)
BTF_ID_FLAGS(func, bpf_kfunc_common_test)
+BTF_ID_FLAGS(func, bpf_kfunc_call_test_mem_len_pass1)
BTF_ID_FLAGS(func, bpf_kfunc_dynptr_test)
BTF_ID_FLAGS(func, bpf_kfunc_nested_acquire_nonzero_offset_test, KF_ACQUIRE)
BTF_ID_FLAGS(func, bpf_kfunc_nested_acquire_zero_offset_test, KF_ACQUIRE)
@@ -1280,7 +1281,6 @@ BTF_ID_FLAGS(func, bpf_kfunc_call_test2)
BTF_ID_FLAGS(func, bpf_kfunc_call_test3)
BTF_ID_FLAGS(func, bpf_kfunc_call_test4)
BTF_ID_FLAGS(func, bpf_kfunc_call_test5)
-BTF_ID_FLAGS(func, bpf_kfunc_call_test_mem_len_pass1)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_mem_len_fail1)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_mem_len_fail2)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_acquire, KF_ACQUIRE | KF_RET_NULL)
--
2.52.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH bpf-next v1 3/4] bpf: Reject modified syscall PTR_TO_CTX for global subprogs
2026-03-17 11:18 [PATCH bpf-next v1 0/4] Allow variable offsets for syscall PTR_TO_CTX Kumar Kartikeya Dwivedi
2026-03-17 11:18 ` [PATCH bpf-next v1 1/4] bpf: Support " Kumar Kartikeya Dwivedi
2026-03-17 11:18 ` [PATCH bpf-next v1 2/4] selftests/bpf: Adjust syscall ctx variable offset tests Kumar Kartikeya Dwivedi
@ 2026-03-17 11:18 ` Kumar Kartikeya Dwivedi
2026-03-17 16:50 ` Emil Tsalapatis
2026-03-17 11:18 ` [PATCH bpf-next v1 4/4] selftests/bpf: Test modified syscall ctx for global subprog Kumar Kartikeya Dwivedi
3 siblings, 1 reply; 11+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-03-17 11:18 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Martin KaFai Lau, Eduard Zingerman, Tejun Heo, Dan Schatzberg,
kkd, kernel-team
Now that syscall programs allow modified offsets to be passed as a
context argument, we must make sure that they are passed unmodified to
global subprogs. The reasoning is that we can have inconsistent and
potentially unsafe max_ctx_offset based checks if the global subprog is
used to access the ctx, or when it is replaced by an extension prog.
We'll need a post-processing pass to correctly identify the
max_ctx_offset across the call graph, but it's extra work for little
functional gain, hence just reject this case.
We have to make this change now, since we opened up the checks made in
check_func_arg_reg_off for BPF_PROG_TYPE_SYSCALL's PTR_TO_CTX in past
commits.
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
kernel/bpf/verifier.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 50639bb69d91..3f34510ec183 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -10778,6 +10778,17 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
bpf_log(log, "arg#%d expects pointer to ctx\n", i);
return -EINVAL;
}
+ /*
+ * We should not allow modified offset ctx to be passed
+ * into global subprogs, to avoid messing up the math of
+ * max_ctx_offset for the whole program. Supporting this
+ * will require a post-verification pass, not worth it.
+ */
+ if (resolve_prog_type(env->prog) == BPF_PROG_TYPE_SYSCALL &&
+ (!tnum_is_const(reg->var_off) || reg->var_off.value)) {
+ bpf_log(log, "arg#%d of syscall prog must have zero offset\n", i);
+ return -EINVAL;
+ }
} else if (base_type(arg->arg_type) == ARG_PTR_TO_MEM) {
ret = check_func_arg_reg_off(env, reg, regno, ARG_DONTCARE);
if (ret < 0)
--
2.52.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH bpf-next v1 4/4] selftests/bpf: Test modified syscall ctx for global subprog
2026-03-17 11:18 [PATCH bpf-next v1 0/4] Allow variable offsets for syscall PTR_TO_CTX Kumar Kartikeya Dwivedi
` (2 preceding siblings ...)
2026-03-17 11:18 ` [PATCH bpf-next v1 3/4] bpf: Reject modified syscall PTR_TO_CTX for global subprogs Kumar Kartikeya Dwivedi
@ 2026-03-17 11:18 ` Kumar Kartikeya Dwivedi
2026-03-17 16:13 ` Emil Tsalapatis
3 siblings, 1 reply; 11+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-03-17 11:18 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Martin KaFai Lau, Eduard Zingerman, Tejun Heo, Dan Schatzberg,
kkd, kernel-team
Ensure that global subprogs can only accept an unmodified PTR_TO_CTX for
syscall programs. For all other program types, fixed or variable offsets
on PTR_TO_CTX is rejected when passed into an argument of any call
instruction type, through the unified logic of check_func_arg_reg_off.
Finally, add a positive example of a case that should succeed with all
our previous changes.
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
.../bpf/progs/verifier_global_subprogs.c | 42 +++++++++++++++++++
1 file changed, 42 insertions(+)
diff --git a/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c b/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
index 2250fc31574d..0c14adaddb90 100644
--- a/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
+++ b/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
@@ -357,6 +357,48 @@ int arg_tag_ctx_syscall(void *ctx)
return tracing_subprog_void(ctx) + tracing_subprog_u64(ctx) + tp_whatever(ctx);
}
+__weak int syscall_array_bpf_for(void *ctx __arg_ctx)
+{
+ int *arr = ctx;
+ int i;
+
+ bpf_for(i, 0, 100)
+ arr[i] *= i;
+
+ return 0;
+}
+
+SEC("?syscall")
+__success __log_level(2)
+int arg_tag_ctx_syscall_bpf_for(void *ctx)
+{
+ return syscall_array_bpf_for(ctx);
+}
+
+SEC("?syscall")
+__failure __log_level(2)
+__msg("arg#0 of syscall prog must have zero offset")
+int arg_tag_ctx_syscall_fixed_off_bad(void *ctx)
+{
+ char *p = ctx;
+
+ p += 8;
+ return subprog_ctx_tag(p);
+}
+
+SEC("?syscall")
+__failure __log_level(2)
+__msg("arg#0 of syscall prog must have zero offset")
+int arg_tag_ctx_syscall_var_off_bad(void *ctx)
+{
+ __u64 off = bpf_get_prandom_u32();
+ char *p = ctx;
+
+ off &= 4;
+ p += off;
+ return subprog_ctx_tag(p);
+}
+
__weak int subprog_dynptr(struct bpf_dynptr *dptr)
{
long *d, t, buf[1] = {};
--
2.52.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v1 1/4] bpf: Support variable offsets for syscall PTR_TO_CTX
2026-03-17 11:18 ` [PATCH bpf-next v1 1/4] bpf: Support " Kumar Kartikeya Dwivedi
@ 2026-03-17 12:09 ` bot+bpf-ci
2026-03-17 16:45 ` Emil Tsalapatis
1 sibling, 0 replies; 11+ messages in thread
From: bot+bpf-ci @ 2026-03-17 12:09 UTC (permalink / raw)
To: memxor, bpf
Cc: tj, dschatzberg, ast, andrii, daniel, martin.lau, eddyz87, kkd,
kernel-team, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 1980 bytes --]
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
[ ... ]
> @@ -9710,6 +9716,11 @@
> * still need to do checks instead of returning.
> */
> return __check_ptr_off_reg(env, reg, regno, true);
> + case PTR_TO_CTX:
> + /* Allow fixed and variable offsets for syscall context. */
> + if (resolve_prog_type(env->prog) == BPF_PROG_TYPE_SYSCALL)
> + return 0;
> + fallthrough;
> default:
> return __check_ptr_off_reg(env, reg, regno, false);
> }
Since check_func_arg_reg_off now returns 0 unconditionally for syscall
PTR_TO_CTX, can a modified (offset) PTR_TO_CTX be passed into a global
subprog via btf_check_func_arg_match?
The global subprog is verified independently with a fresh PTR_TO_CTX
at offset 0, so its max_ctx_offset only reflects its own local accesses.
But at runtime the subprog receives the caller's shifted pointer. For
example:
main program:
r1 = PTR_TO_CTX
r1 += 400
call global_subprog(r1)
global subprog (verified independently):
accesses ctx[0..100] -> max_ctx_offset = 100
runtime:
subprog gets ctx_base+400, accesses ctx_base+400..500
In bpf_prog_test_run_syscall, the buffer is allocated based on
max_ctx_offset:
ctx = memdup_user(ctx_in, ctx_size_in);
With max_ctx_offset = 100 instead of the needed 500, a 100-byte buffer
would be accepted and the subprog would read or write 400 bytes past
the end.
It looks like the series patch 3/4 ("bpf: Reject modified syscall
PTR_TO_CTX for global subprogs") adds a check in btf_check_func_arg_match
to reject this case. Would it make sense to fold that restriction into
this patch so the intermediate state does not have the gap?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/23192407921
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v1 4/4] selftests/bpf: Test modified syscall ctx for global subprog
2026-03-17 11:18 ` [PATCH bpf-next v1 4/4] selftests/bpf: Test modified syscall ctx for global subprog Kumar Kartikeya Dwivedi
@ 2026-03-17 16:13 ` Emil Tsalapatis
0 siblings, 0 replies; 11+ messages in thread
From: Emil Tsalapatis @ 2026-03-17 16:13 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi, bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Martin KaFai Lau, Eduard Zingerman, Tejun Heo, Dan Schatzberg,
kkd, kernel-team
On Tue Mar 17, 2026 at 7:18 AM EDT, Kumar Kartikeya Dwivedi wrote:
> Ensure that global subprogs can only accept an unmodified PTR_TO_CTX for
> syscall programs. For all other program types, fixed or variable offsets
> on PTR_TO_CTX is rejected when passed into an argument of any call
> instruction type, through the unified logic of check_func_arg_reg_off.
>
> Finally, add a positive example of a case that should succeed with all
> our previous changes.
>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> ---
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
> .../bpf/progs/verifier_global_subprogs.c | 42 +++++++++++++++++++
> 1 file changed, 42 insertions(+)
>
> diff --git a/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c b/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
> index 2250fc31574d..0c14adaddb90 100644
> --- a/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
> +++ b/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
> @@ -357,6 +357,48 @@ int arg_tag_ctx_syscall(void *ctx)
> return tracing_subprog_void(ctx) + tracing_subprog_u64(ctx) + tp_whatever(ctx);
> }
>
> +__weak int syscall_array_bpf_for(void *ctx __arg_ctx)
> +{
> + int *arr = ctx;
> + int i;
> +
> + bpf_for(i, 0, 100)
> + arr[i] *= i;
> +
> + return 0;
> +}
> +
> +SEC("?syscall")
> +__success __log_level(2)
> +int arg_tag_ctx_syscall_bpf_for(void *ctx)
> +{
> + return syscall_array_bpf_for(ctx);
> +}
> +
> +SEC("?syscall")
> +__failure __log_level(2)
> +__msg("arg#0 of syscall prog must have zero offset")
> +int arg_tag_ctx_syscall_fixed_off_bad(void *ctx)
> +{
> + char *p = ctx;
> +
> + p += 8;
> + return subprog_ctx_tag(p);
> +}
> +
> +SEC("?syscall")
> +__failure __log_level(2)
> +__msg("arg#0 of syscall prog must have zero offset")
> +int arg_tag_ctx_syscall_var_off_bad(void *ctx)
> +{
> + __u64 off = bpf_get_prandom_u32();
> + char *p = ctx;
> +
> + off &= 4;
> + p += off;
> + return subprog_ctx_tag(p);
> +}
> +
> __weak int subprog_dynptr(struct bpf_dynptr *dptr)
> {
> long *d, t, buf[1] = {};
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v1 1/4] bpf: Support variable offsets for syscall PTR_TO_CTX
2026-03-17 11:18 ` [PATCH bpf-next v1 1/4] bpf: Support " Kumar Kartikeya Dwivedi
2026-03-17 12:09 ` bot+bpf-ci
@ 2026-03-17 16:45 ` Emil Tsalapatis
1 sibling, 0 replies; 11+ messages in thread
From: Emil Tsalapatis @ 2026-03-17 16:45 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi, bpf
Cc: Tejun Heo, Dan Schatzberg, Alexei Starovoitov, Andrii Nakryiko,
Daniel Borkmann, Martin KaFai Lau, Eduard Zingerman, kkd,
kernel-team
On Tue Mar 17, 2026 at 7:18 AM EDT, Kumar Kartikeya Dwivedi wrote:
> Allow accessing PTR_TO_CTX with variable offsets in syscall programs.
> Fixed offsets are already enabled for all program types that do not
> convert their ctx accesses, since the changes we made in the commit
> de6c7d99f898 ("bpf: Relax fixed offset check for PTR_TO_CTX"). Note
> that we also lift the restriction on passing syscall context into
> helpers, which was not permitted before, and passing modified syscall
> context into kfuncs.
>
> The structure of check_mem_access can be mostly shared and preserved,
> but we must use check_mem_region_access to correctly verify access with
> variable offsets.
>
> The check made in check_helper_mem_access is hardened to only allow
> PTR_TO_CTX for syscall programs to be passed in as helper memory. This
> was the original intention of the existing code anyway, and it makes
> little sense for other program types' context to be utilized as a memory
> buffer. In case a convincing example presents itself in the future, this
> check can be relaxed further.
>
> We also no longer use the last-byte access to simulate helper memory
> access, but instead go through check_mem_region_access. Since this no
> longer updates our max_ctx_offset, we must do so manually, to keep track
> of the maximum offset at which the program ctx may be accessed.
>
> Cc: Tejun Heo <tj@kernel.org>
> Cc: Dan Schatzberg <dschatzberg@meta.com>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> ---
One nit (along with the bot's point which seems correct, patch 3 can be rolled
into this one). Given those:
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
> kernel/bpf/verifier.c | 51 +++++++++++--------
> .../bpf/progs/verifier_global_subprogs.c | 1 -
> 2 files changed, 31 insertions(+), 21 deletions(-)
>
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 01c18f4268de..50639bb69d91 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -7843,6 +7843,7 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
> * Program types that don't rewrite ctx accesses can safely
> * dereference ctx pointers with fixed offsets.
> */
> + bool var_off_ok = resolve_prog_type(env->prog) == BPF_PROG_TYPE_SYSCALL;
> bool fixed_off_ok = !env->ops->convert_ctx_access;
> struct bpf_retval_range range;
> struct bpf_insn_access_aux info = {
> @@ -7857,15 +7858,25 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
> return -EACCES;
> }
>
> - err = __check_ptr_off_reg(env, reg, regno, fixed_off_ok);
> - if (err < 0)
> - return err;
> + if (var_off_ok) {
> + err = check_mem_region_access(env, regno, off, size, U16_MAX, false);
> + if (err)
> + return err;
> + } else {
> + err = __check_ptr_off_reg(env, reg, regno, fixed_off_ok);
> + if (err < 0)
> + return err;
> + }
>
> /*
> * Fold the register's constant offset into the insn offset so
> - * that is_valid_access() sees the true effective offset.
> + * that is_valid_access() sees the true effective offset. If the
> + * register's offset is not constant, then the maximum possible
> + * offset is simulated.
> */
> - if (fixed_off_ok)
> + if (var_off_ok)
> + off += reg->umax_value;
> + else if (fixed_off_ok)
Nit: Can we move the offset adjustment into the if-else above? We're
essentially repeating the if (var_off_ok) check twice.
> off += reg->var_off.value;
> err = check_ctx_access(env, insn_idx, off, size, t, &info);
> if (err)
> @@ -8442,22 +8453,16 @@ static int check_helper_mem_access(struct bpf_verifier_env *env, int regno,
> return check_ptr_to_btf_access(env, regs, regno, 0,
> access_size, BPF_READ, -1);
> case PTR_TO_CTX:
> - /* in case the function doesn't know how to access the context,
> - * (because we are in a program of type SYSCALL for example), we
> - * can not statically check its size.
> - * Dynamically check it now.
> - */
> - if (!env->ops->convert_ctx_access) {
> - int offset = access_size - 1;
> -
> - /* Allow zero-byte read from PTR_TO_CTX */
> - if (access_size == 0)
> - return zero_size_allowed ? 0 : -EACCES;
> -
> - return check_mem_access(env, env->insn_idx, regno, offset, BPF_B,
> - access_type, -1, false, false);
> + /* Only permit reading or writing syscall context using helper calls. */
> + if (resolve_prog_type(env->prog) == BPF_PROG_TYPE_SYSCALL) {
> + int err = check_mem_region_access(env, regno, 0, access_size, U16_MAX,
> + zero_size_allowed);
> + if (err)
> + return err;
> + if (env->prog->aux->max_ctx_offset < reg->umax_value + access_size)
> + env->prog->aux->max_ctx_offset = reg->umax_value + access_size;
> + return 0;
> }
> -
> fallthrough;
> default: /* scalar_value or invalid ptr */
> /* Allow zero-byte read from NULL, regardless of pointer type */
> @@ -9401,6 +9406,7 @@ static const struct bpf_reg_types mem_types = {
> PTR_TO_MEM | MEM_RINGBUF,
> PTR_TO_BUF,
> PTR_TO_BTF_ID | PTR_TRUSTED,
> + PTR_TO_CTX,
> },
> };
>
> @@ -9710,6 +9716,11 @@ static int check_func_arg_reg_off(struct bpf_verifier_env *env,
> * still need to do checks instead of returning.
> */
> return __check_ptr_off_reg(env, reg, regno, true);
> + case PTR_TO_CTX:
> + /* Allow fixed and variable offsets for syscall context. */
> + if (resolve_prog_type(env->prog) == BPF_PROG_TYPE_SYSCALL)
> + return 0;
> + fallthrough;
> default:
> return __check_ptr_off_reg(env, reg, regno, false);
> }
> diff --git a/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c b/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
> index f02012a2fbaa..2250fc31574d 100644
> --- a/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
> +++ b/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c
> @@ -134,7 +134,6 @@ __noinline __weak int subprog_user_anon_mem(user_struct_t *t)
>
> SEC("?tracepoint")
> __failure __log_level(2)
> -__msg("invalid bpf_context access")
> __msg("Caller passes invalid args into func#1 ('subprog_user_anon_mem')")
> int anon_user_mem_invalid(void *ctx)
> {
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v1 3/4] bpf: Reject modified syscall PTR_TO_CTX for global subprogs
2026-03-17 11:18 ` [PATCH bpf-next v1 3/4] bpf: Reject modified syscall PTR_TO_CTX for global subprogs Kumar Kartikeya Dwivedi
@ 2026-03-17 16:50 ` Emil Tsalapatis
2026-03-17 18:46 ` Kumar Kartikeya Dwivedi
0 siblings, 1 reply; 11+ messages in thread
From: Emil Tsalapatis @ 2026-03-17 16:50 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi, bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Martin KaFai Lau, Eduard Zingerman, Tejun Heo, Dan Schatzberg,
kkd, kernel-team
On Tue Mar 17, 2026 at 7:18 AM EDT, Kumar Kartikeya Dwivedi wrote:
> Now that syscall programs allow modified offsets to be passed as a
> context argument, we must make sure that they are passed unmodified to
> global subprogs. The reasoning is that we can have inconsistent and
> potentially unsafe max_ctx_offset based checks if the global subprog is
> used to access the ctx, or when it is replaced by an extension prog.
> We'll need a post-processing pass to correctly identify the
> max_ctx_offset across the call graph, but it's extra work for little
> functional gain, hence just reject this case.
>
> We have to make this change now, since we opened up the checks made in
> check_func_arg_reg_off for BPF_PROG_TYPE_SYSCALL's PTR_TO_CTX in past
> commits.
>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
Whether we end up merging it into patch #1 or not, the logic is correct.
> ---
> kernel/bpf/verifier.c | 11 +++++++++++
> 1 file changed, 11 insertions(+)
>
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 50639bb69d91..3f34510ec183 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -10778,6 +10778,17 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
> bpf_log(log, "arg#%d expects pointer to ctx\n", i);
> return -EINVAL;
> }
> + /*
> + * We should not allow modified offset ctx to be passed
> + * into global subprogs, to avoid messing up the math of
> + * max_ctx_offset for the whole program. Supporting this
> + * will require a post-verification pass, not worth it.
> + */
> + if (resolve_prog_type(env->prog) == BPF_PROG_TYPE_SYSCALL &&
> + (!tnum_is_const(reg->var_off) || reg->var_off.value)) {
> + bpf_log(log, "arg#%d of syscall prog must have zero offset\n", i);
> + return -EINVAL;
> + }
> } else if (base_type(arg->arg_type) == ARG_PTR_TO_MEM) {
> ret = check_func_arg_reg_off(env, reg, regno, ARG_DONTCARE);
> if (ret < 0)
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v1 2/4] selftests/bpf: Adjust syscall ctx variable offset tests
2026-03-17 11:18 ` [PATCH bpf-next v1 2/4] selftests/bpf: Adjust syscall ctx variable offset tests Kumar Kartikeya Dwivedi
@ 2026-03-17 18:24 ` Emil Tsalapatis
0 siblings, 0 replies; 11+ messages in thread
From: Emil Tsalapatis @ 2026-03-17 18:24 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi, bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Martin KaFai Lau, Eduard Zingerman, Tejun Heo, Dan Schatzberg,
kkd, kernel-team
On Tue Mar 17, 2026 at 7:18 AM EDT, Kumar Kartikeya Dwivedi wrote:
> Add various tests to exercise fixed and variable offsets on PTR_TO_CTX
> for syscall programs, and cover disallowed cases for other program types
> lacking convert_ctx_access callback. Load verifier_ctx with CAP_SYS_ADMIN
> so that kfunc related logic can be tested. While at it, convert assembly
> tests to C.
>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
> ---
> .../selftests/bpf/prog_tests/verifier.c | 34 +-
> .../selftests/bpf/progs/verifier_ctx.c | 356 +++++++++++++++---
> .../selftests/bpf/test_kmods/bpf_testmod.c | 2 +-
> 3 files changed, 325 insertions(+), 67 deletions(-)
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
> index 8cdfd74c95d7..04d5f46264a3 100644
> --- a/tools/testing/selftests/bpf/prog_tests/verifier.c
> +++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
> @@ -126,29 +126,35 @@ struct test_val {
> __maybe_unused
> static void run_tests_aux(const char *skel_name,
> skel_elf_bytes_fn elf_bytes_factory,
> - pre_execution_cb pre_execution_cb)
> + pre_execution_cb pre_execution_cb,
> + bool drop_sys_admin)
> {
> struct test_loader tester = {};
> __u64 old_caps;
> int err;
>
> - /* test_verifier tests are executed w/o CAP_SYS_ADMIN, do the same here */
> - err = cap_disable_effective(1ULL << CAP_SYS_ADMIN, &old_caps);
> - if (err) {
> - PRINT_FAIL("failed to drop CAP_SYS_ADMIN: %i, %s\n", err, strerror(-err));
> - return;
> + if (drop_sys_admin) {
> + /* test_verifier tests are executed w/o CAP_SYS_ADMIN, do the same here */
> + err = cap_disable_effective(1ULL << CAP_SYS_ADMIN, &old_caps);
> + if (err) {
> + PRINT_FAIL("failed to drop CAP_SYS_ADMIN: %i, %s\n", err, strerror(-err));
> + return;
> + }
> }
>
> test_loader__set_pre_execution_cb(&tester, pre_execution_cb);
> test_loader__run_subtests(&tester, skel_name, elf_bytes_factory);
> test_loader_fini(&tester);
>
> - err = cap_enable_effective(old_caps, NULL);
> - if (err)
> - PRINT_FAIL("failed to restore CAP_SYS_ADMIN: %i, %s\n", err, strerror(-err));
> + if (drop_sys_admin) {
> + err = cap_enable_effective(old_caps, NULL);
> + if (err)
> + PRINT_FAIL("failed to restore CAP_SYS_ADMIN: %i, %s\n", err, strerror(-err));
> + }
> }
>
> -#define RUN(skel) run_tests_aux(#skel, skel##__elf_bytes, NULL)
> +#define RUN(skel) run_tests_aux(#skel, skel##__elf_bytes, NULL, true)
> +#define RUN_WITH_CAP_SYS_ADMIN(skel) run_tests_aux(#skel, skel##__elf_bytes, NULL, false)
>
> void test_verifier_align(void) { RUN(verifier_align); }
> void test_verifier_and(void) { RUN(verifier_and); }
> @@ -173,7 +179,7 @@ void test_verifier_cgroup_skb(void) { RUN(verifier_cgroup_skb); }
> void test_verifier_cgroup_storage(void) { RUN(verifier_cgroup_storage); }
> void test_verifier_const(void) { RUN(verifier_const); }
> void test_verifier_const_or(void) { RUN(verifier_const_or); }
> -void test_verifier_ctx(void) { RUN(verifier_ctx); }
> +void test_verifier_ctx(void) { RUN_WITH_CAP_SYS_ADMIN(verifier_ctx); }
> void test_verifier_ctx_sk_msg(void) { RUN(verifier_ctx_sk_msg); }
> void test_verifier_d_path(void) { RUN(verifier_d_path); }
> void test_verifier_default_trusted_ptr(void) { RUN_TESTS(verifier_default_trusted_ptr); }
> @@ -293,7 +299,8 @@ void test_verifier_array_access(void)
> {
> run_tests_aux("verifier_array_access",
> verifier_array_access__elf_bytes,
> - init_array_access_maps);
> + init_array_access_maps,
> + true);
> }
> void test_verifier_async_cb_context(void) { RUN(verifier_async_cb_context); }
>
> @@ -306,5 +313,6 @@ void test_verifier_value_ptr_arith(void)
> {
> run_tests_aux("verifier_value_ptr_arith",
> verifier_value_ptr_arith__elf_bytes,
> - init_value_ptr_arith_maps);
> + init_value_ptr_arith_maps,
> + true);
> }
> diff --git a/tools/testing/selftests/bpf/progs/verifier_ctx.c b/tools/testing/selftests/bpf/progs/verifier_ctx.c
> index 371780290c0d..c054c8cb7242 100644
> --- a/tools/testing/selftests/bpf/progs/verifier_ctx.c
> +++ b/tools/testing/selftests/bpf/progs/verifier_ctx.c
> @@ -4,6 +4,10 @@
> #include "vmlinux.h"
> #include <bpf/bpf_helpers.h>
> #include "bpf_misc.h"
> +#include "../test_kmods/bpf_testmod_kfunc.h"
> +
> +const char ctx_strncmp_target[] = "ctx";
> +const char ctx_snprintf_fmt[] = "";
>
> SEC("tc")
> __description("context stores via BPF_ATOMIC")
> @@ -69,7 +73,6 @@ __naked void ctx_pointer_to_helper_1(void)
> SEC("socket")
> __description("pass modified ctx pointer to helper, 2")
> __failure __msg("negative offset ctx ptr R1 off=-612 disallowed")
> -__failure_unpriv __msg_unpriv("negative offset ctx ptr R1 off=-612 disallowed")
> __naked void ctx_pointer_to_helper_2(void)
> {
> asm volatile (" \
> @@ -295,77 +298,324 @@ padding_access("sk_reuseport", sk_reuseport_md, hash, 4);
> SEC("syscall")
> __description("syscall: write to ctx with fixed offset")
> __success
> -__naked void syscall_ctx_fixed_off_write(void)
> +int syscall_ctx_fixed_off_write(void *ctx)
> {
> - asm volatile (" \
> - r0 = 0; \
> - *(u32*)(r1 + 0) = r0; \
> - r1 += 4; \
> - *(u32*)(r1 + 0) = r0; \
> - exit; \
> -" ::: __clobber_all);
> + char *p = ctx;
> +
> + *(__u32 *)p = 0;
> + *(__u32 *)(p + 4) = 0;
> + return 0;
> +}
> +
> +SEC("syscall")
> +__description("syscall: read ctx with fixed offset")
> +__success
> +int syscall_ctx_fixed_off_read(void *ctx)
> +{
> + char *p = ctx;
> + volatile __u32 val;
> +
> + val = *(__u32 *)(p + 4);
> + (void)val;
> + return 0;
> +}
> +
> +SEC("syscall")
> +__description("syscall: read ctx with variable offset")
> +__success
> +int syscall_ctx_var_off_read(void *ctx)
> +{
> + __u64 off = bpf_get_prandom_u32();
> + char *p = ctx;
> + volatile __u32 val;
> +
> + off &= 0xfc;
> + p += off;
> + val = *(__u32 *)p;
> + (void)val;
> + return 0;
> +}
> +
> +SEC("syscall")
> +__description("syscall: write ctx with variable offset")
> +__success
> +int syscall_ctx_var_off_write(void *ctx)
> +{
> + __u64 off = bpf_get_prandom_u32();
> + char *p = ctx;
> +
> + off &= 0xfc;
> + p += off;
> + *(__u32 *)p = 0;
> + return 0;
> +}
> +
> +SEC("syscall")
> +__description("syscall: reject negative variable offset ctx access")
> +__failure __msg("min value is negative")
> +int syscall_ctx_neg_var_off(void *ctx)
> +{
> + __u64 off = bpf_get_prandom_u32();
> + char *p = ctx;
> +
> + off &= 4;
> + p -= off;
> + return *(__u32 *)p;
> +}
> +
> +SEC("syscall")
> +__description("syscall: reject unbounded variable offset ctx access")
> +__failure __msg("unbounded memory access")
> +int syscall_ctx_unbounded_var_off(void *ctx)
> +{
> + __u64 off = (__u32)bpf_get_prandom_u32();
> + char *p = ctx;
> +
> + off <<= 2;
Unrelated to this patch: These kinds of shifts are pretty frequent in
array arithmetic by the compiler, is it worth it/tractable to account
for them during bounds calculation?
> + p += off;
> + return *(__u32 *)p;
> +}
> +
> +SEC("syscall")
> +__description("syscall: helper read ctx with fixed offset")
> +__success
> +int syscall_ctx_helper_fixed_off_read(void *ctx)
> +{
> + char *p = ctx;
> +
> + p += 4;
> + return bpf_strncmp(p, 4, ctx_strncmp_target);
> +}
> +
> +SEC("syscall")
> +__description("syscall: helper write ctx with fixed offset")
> +__success
> +int syscall_ctx_helper_fixed_off_write(void *ctx)
> +{
> + char *p = ctx;
> +
> + p += 4;
> + return bpf_probe_read_kernel(p, 4, 0);
> +}
> +
> +SEC("syscall")
> +__description("syscall: helper read ctx with variable offset")
> +__success
> +int syscall_ctx_helper_var_off_read(void *ctx)
> +{
> + __u64 off = bpf_get_prandom_u32();
> + char *p = ctx;
> +
> + off &= 0xfc;
> + p += off;
> + return bpf_strncmp(p, 4, ctx_strncmp_target);
> +}
> +
> +SEC("syscall")
> +__description("syscall: helper write ctx with variable offset")
> +__success
> +int syscall_ctx_helper_var_off_write(void *ctx)
> +{
> + __u64 off = bpf_get_prandom_u32();
> + char *p = ctx;
> +
> + off &= 0xfc;
> + p += off;
> + return bpf_probe_read_kernel(p, 4, 0);
> +}
> +
> +SEC("syscall")
> +__description("syscall: helper read zero-sized ctx access")
> +__success
> +int syscall_ctx_helper_zero_sized_read(void *ctx)
> +{
> + return bpf_snprintf(0, 0, ctx_snprintf_fmt, ctx, 0);
> +}
> +
> +SEC("syscall")
> +__description("syscall: helper write zero-sized ctx access")
> +__success
> +int syscall_ctx_helper_zero_sized_write(void *ctx)
> +{
> + return bpf_probe_read_kernel(ctx, 0, 0);
> +}
> +
> +SEC("syscall")
> +__description("syscall: kfunc access ctx with fixed offset")
> +__success
> +int syscall_ctx_kfunc_fixed_off(void *ctx)
> +{
> + char *p = ctx;
> +
> + p += 4;
> + bpf_kfunc_call_test_mem_len_pass1(p, 4);
> + return 0;
> +}
> +
> +SEC("syscall")
> +__description("syscall: kfunc access ctx with variable offset")
> +__success
> +int syscall_ctx_kfunc_var_off(void *ctx)
> +{
> + __u64 off = bpf_get_prandom_u32();
> + char *p = ctx;
> +
> + off &= 0xfc;
> + p += off;
> + bpf_kfunc_call_test_mem_len_pass1(p, 4);
> + return 0;
> +}
> +
> +SEC("syscall")
> +__description("syscall: kfunc access zero-sized ctx")
> +__success
> +int syscall_ctx_kfunc_zero_sized(void *ctx)
> +{
> + bpf_kfunc_call_test_mem_len_pass1(ctx, 0);
> + return 0;
> }
>
> /*
> - * Test that program types without convert_ctx_access can dereference
> - * their ctx pointer after adding a fixed offset. Variable and negative
> - * offsets should still be rejected.
> + * For non-syscall program types without convert_ctx_access, direct ctx
> + * dereference is still allowed after adding a fixed offset, while variable
> + * and negative direct accesses reject.
> + *
> + * Passing ctx as a helper or kfunc memory argument is only permitted for
> + * syscall programs, so the helper and kfunc cases below validate rejection
> + * for non-syscall ctx pointers at fixed, variable, and zero-sized accesses.
> */
> -#define no_rewrite_ctx_access(type, name, off, ld_op) \
> +#define no_rewrite_ctx_access(type, name, off, load_t) \
> SEC(type) \
> __description(type ": read ctx at fixed offset") \
> __success \
> - __naked void no_rewrite_##name##_fixed(void) \
> + int no_rewrite_##name##_fixed(void *ctx) \
> { \
> - asm volatile (" \
> - r1 += %[__off]; \
> - r0 = *(" #ld_op " *)(r1 + 0); \
> - r0 = 0; \
> - exit;" \
> - : \
> - : __imm_const(__off, off) \
> - : __clobber_all); \
> + char *p = ctx; \
> + volatile load_t val; \
> + \
> + val = *(load_t *)(p + off); \
> + (void)val; \
> + return 0; \
> } \
> SEC(type) \
> __description(type ": reject variable offset ctx access") \
> __failure __msg("variable ctx access var_off=") \
> - __naked void no_rewrite_##name##_var(void) \
> + int no_rewrite_##name##_var(void *ctx) \
> { \
> - asm volatile (" \
> - r6 = r1; \
> - call %[bpf_get_prandom_u32]; \
> - r1 = r6; \
> - r0 &= 4; \
> - r1 += r0; \
> - r0 = *(" #ld_op " *)(r1 + 0); \
> - r0 = 0; \
> - exit;" \
> - : \
> - : __imm(bpf_get_prandom_u32) \
> - : __clobber_all); \
> + __u64 off_var = bpf_get_prandom_u32(); \
> + char *p = ctx; \
> + \
> + off_var &= 4; \
> + p += off_var; \
> + return *(load_t *)p; \
> } \
> SEC(type) \
> __description(type ": reject negative offset ctx access") \
> - __failure __msg("negative offset ctx ptr") \
> - __naked void no_rewrite_##name##_neg(void) \
> + __failure __msg("invalid bpf_context access") \
> + int no_rewrite_##name##_neg(void *ctx) \
> { \
> - asm volatile (" \
> - r1 += %[__neg_off]; \
> - r0 = *(" #ld_op " *)(r1 + 0); \
> - r0 = 0; \
> - exit;" \
> - : \
> - : __imm_const(__neg_off, -(off)) \
> - : __clobber_all); \
> + char *p = ctx; \
> + \
> + p -= 612; \
> + return *(load_t *)p; \
> + } \
> + SEC(type) \
> + __description(type ": reject helper read ctx at fixed offset") \
> + __failure __msg("dereference of modified ctx ptr") \
> + int no_rewrite_##name##_helper_read_fixed(void *ctx) \
> + { \
> + char *p = ctx; \
> + \
> + p += off; \
> + return bpf_strncmp(p, 4, ctx_strncmp_target); \
> + } \
> + SEC(type) \
> + __description(type ": reject helper write ctx at fixed offset") \
> + __failure __msg("dereference of modified ctx ptr") \
> + int no_rewrite_##name##_helper_write_fixed(void *ctx) \
> + { \
> + char *p = ctx; \
> + \
> + p += off; \
> + return bpf_probe_read_kernel(p, 4, 0); \
> + } \
> + SEC(type) \
> + __description(type ": reject helper read ctx with variable offset") \
> + __failure __msg("variable ctx access var_off=") \
> + int no_rewrite_##name##_helper_read_var(void *ctx) \
> + { \
> + __u64 off_var = bpf_get_prandom_u32(); \
> + char *p = ctx; \
> + \
> + off_var &= 4; \
> + p += off_var; \
> + return bpf_strncmp(p, 4, ctx_strncmp_target); \
> + } \
> + SEC(type) \
> + __description(type ": reject helper write ctx with variable offset") \
> + __failure __msg("variable ctx access var_off=") \
> + int no_rewrite_##name##_helper_write_var(void *ctx) \
> + { \
> + __u64 off_var = bpf_get_prandom_u32(); \
> + char *p = ctx; \
> + \
> + off_var &= 4; \
> + p += off_var; \
> + return bpf_probe_read_kernel(p, 4, 0); \
> + } \
> + SEC(type) \
> + __description(type ": reject helper read zero-sized ctx access") \
> + __failure __msg("R4 type=ctx expected=fp") \
> + int no_rewrite_##name##_helper_read_zero(void *ctx) \
> + { \
> + return bpf_snprintf(0, 0, ctx_snprintf_fmt, ctx, 0); \
> + } \
> + SEC(type) \
> + __description(type ": reject helper write zero-sized ctx access") \
> + __failure __msg("R1 type=ctx expected=fp") \
> + int no_rewrite_##name##_helper_write_zero(void *ctx) \
> + { \
> + return bpf_probe_read_kernel(ctx, 0, 0); \
> + } \
> + SEC(type) \
> + __description(type ": reject kfunc ctx at fixed offset") \
> + __failure __msg("dereference of modified ctx ptr") \
> + int no_rewrite_##name##_kfunc_fixed(void *ctx) \
> + { \
> + char *p = ctx; \
> + \
> + p += off; \
> + bpf_kfunc_call_test_mem_len_pass1(p, 4); \
> + return 0; \
> + } \
> + SEC(type) \
> + __description(type ": reject kfunc ctx with variable offset") \
> + __failure __msg("variable ctx access var_off=") \
> + int no_rewrite_##name##_kfunc_var(void *ctx) \
> + { \
> + __u64 off_var = bpf_get_prandom_u32(); \
> + char *p = ctx; \
> + \
> + off_var &= 4; \
> + p += off_var; \
> + bpf_kfunc_call_test_mem_len_pass1(p, 4); \
> + return 0; \
> + } \
> + SEC(type) \
> + __description(type ": reject kfunc zero-sized ctx access") \
> + __failure __msg("R1 type=ctx expected=fp") \
> + int no_rewrite_##name##_kfunc_zero(void *ctx) \
> + { \
> + bpf_kfunc_call_test_mem_len_pass1(ctx, 0); \
> + return 0; \
> }
>
> -no_rewrite_ctx_access("syscall", syscall, 4, u32);
> -no_rewrite_ctx_access("kprobe", kprobe, 8, u64);
> -no_rewrite_ctx_access("tracepoint", tp, 8, u64);
> -no_rewrite_ctx_access("raw_tp", raw_tp, 8, u64);
> -no_rewrite_ctx_access("raw_tracepoint.w", raw_tp_w, 8, u64);
> -no_rewrite_ctx_access("fentry/bpf_modify_return_test", fentry, 8, u64);
> -no_rewrite_ctx_access("cgroup/dev", cgroup_dev, 4, u32);
> -no_rewrite_ctx_access("netfilter", netfilter, offsetof(struct bpf_nf_ctx, skb), u64);
> +no_rewrite_ctx_access("kprobe", kprobe, 8, __u64);
> +no_rewrite_ctx_access("tracepoint", tp, 8, __u64);
> +no_rewrite_ctx_access("raw_tp", raw_tp, 8, __u64);
> +no_rewrite_ctx_access("raw_tracepoint.w", raw_tp_w, 8, __u64);
> +no_rewrite_ctx_access("fentry/bpf_modify_return_test", fentry, 8, __u64);
> +no_rewrite_ctx_access("cgroup/dev", cgroup_dev, 4, __u32);
> +no_rewrite_ctx_access("netfilter", netfilter, offsetof(struct bpf_nf_ctx, skb), __u64);
>
> char _license[] SEC("license") = "GPL";
> diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
> index 94edbd2afa67..f91b484d80f2 100644
> --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
> +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
> @@ -716,6 +716,7 @@ BTF_ID_FLAGS(func, bpf_iter_testmod_seq_next, KF_ITER_NEXT | KF_RET_NULL)
> BTF_ID_FLAGS(func, bpf_iter_testmod_seq_destroy, KF_ITER_DESTROY)
> BTF_ID_FLAGS(func, bpf_iter_testmod_seq_value)
> BTF_ID_FLAGS(func, bpf_kfunc_common_test)
> +BTF_ID_FLAGS(func, bpf_kfunc_call_test_mem_len_pass1)
> BTF_ID_FLAGS(func, bpf_kfunc_dynptr_test)
> BTF_ID_FLAGS(func, bpf_kfunc_nested_acquire_nonzero_offset_test, KF_ACQUIRE)
> BTF_ID_FLAGS(func, bpf_kfunc_nested_acquire_zero_offset_test, KF_ACQUIRE)
> @@ -1280,7 +1281,6 @@ BTF_ID_FLAGS(func, bpf_kfunc_call_test2)
> BTF_ID_FLAGS(func, bpf_kfunc_call_test3)
> BTF_ID_FLAGS(func, bpf_kfunc_call_test4)
> BTF_ID_FLAGS(func, bpf_kfunc_call_test5)
> -BTF_ID_FLAGS(func, bpf_kfunc_call_test_mem_len_pass1)
> BTF_ID_FLAGS(func, bpf_kfunc_call_test_mem_len_fail1)
> BTF_ID_FLAGS(func, bpf_kfunc_call_test_mem_len_fail2)
> BTF_ID_FLAGS(func, bpf_kfunc_call_test_acquire, KF_ACQUIRE | KF_RET_NULL)
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v1 3/4] bpf: Reject modified syscall PTR_TO_CTX for global subprogs
2026-03-17 16:50 ` Emil Tsalapatis
@ 2026-03-17 18:46 ` Kumar Kartikeya Dwivedi
0 siblings, 0 replies; 11+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-03-17 18:46 UTC (permalink / raw)
To: Emil Tsalapatis
Cc: bpf, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Martin KaFai Lau, Eduard Zingerman, Tejun Heo, Dan Schatzberg,
kkd, kernel-team
On Tue, 17 Mar 2026 at 17:50, Emil Tsalapatis <emil@etsalapatis.com> wrote:
>
> On Tue Mar 17, 2026 at 7:18 AM EDT, Kumar Kartikeya Dwivedi wrote:
> > Now that syscall programs allow modified offsets to be passed as a
> > context argument, we must make sure that they are passed unmodified to
> > global subprogs. The reasoning is that we can have inconsistent and
> > potentially unsafe max_ctx_offset based checks if the global subprog is
> > used to access the ctx, or when it is replaced by an extension prog.
> > We'll need a post-processing pass to correctly identify the
> > max_ctx_offset across the call graph, but it's extra work for little
> > functional gain, hence just reject this case.
> >
> > We have to make this change now, since we opened up the checks made in
> > check_func_arg_reg_off for BPF_PROG_TYPE_SYSCALL's PTR_TO_CTX in past
> > commits.
> >
> > Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
>
> Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
>
> Whether we end up merging it into patch #1 or not, the logic is correct.
>
> > ---
> > kernel/bpf/verifier.c | 11 +++++++++++
> > 1 file changed, 11 insertions(+)
> >
> > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > index 50639bb69d91..3f34510ec183 100644
> > --- a/kernel/bpf/verifier.c
> > +++ b/kernel/bpf/verifier.c
> > @@ -10778,6 +10778,17 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
> > bpf_log(log, "arg#%d expects pointer to ctx\n", i);
> > return -EINVAL;
> > }
> > + /*
> > + * We should not allow modified offset ctx to be passed
> > + * into global subprogs, to avoid messing up the math of
> > + * max_ctx_offset for the whole program. Supporting this
> > + * will require a post-verification pass, not worth it.
> > + */
> > + if (resolve_prog_type(env->prog) == BPF_PROG_TYPE_SYSCALL &&
> > + (!tnum_is_const(reg->var_off) || reg->var_off.value)) {
> > + bpf_log(log, "arg#%d of syscall prog must have zero offset\n", i);
> > + return -EINVAL;
> > + }
Discussed a bit with Emil offlist and we need to make similar checks
for tail calls as well.
Repeating this rejection pattern after opening up the constraints in
check_func_arg_reg_off can be repetitive and error prone, so a better
idea we discussed is making it conditional on arg_type == PTR_TO_CTX
in check_func_arg_reg_off. In summary, whenever we pass it as a ctx
argument type to anything, we must pass it unmodified, in other cases,
it can be passed with a modified offset. This will handle its escaping
into global subprogs and tail calls, and in the future, indirect
function calls in a natural manner.
> > } else if (base_type(arg->arg_type) == ARG_PTR_TO_MEM) {
> > ret = check_func_arg_reg_off(env, reg, regno, ARG_DONTCARE);
> > if (ret < 0)
>
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-03-17 18:46 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-17 11:18 [PATCH bpf-next v1 0/4] Allow variable offsets for syscall PTR_TO_CTX Kumar Kartikeya Dwivedi
2026-03-17 11:18 ` [PATCH bpf-next v1 1/4] bpf: Support " Kumar Kartikeya Dwivedi
2026-03-17 12:09 ` bot+bpf-ci
2026-03-17 16:45 ` Emil Tsalapatis
2026-03-17 11:18 ` [PATCH bpf-next v1 2/4] selftests/bpf: Adjust syscall ctx variable offset tests Kumar Kartikeya Dwivedi
2026-03-17 18:24 ` Emil Tsalapatis
2026-03-17 11:18 ` [PATCH bpf-next v1 3/4] bpf: Reject modified syscall PTR_TO_CTX for global subprogs Kumar Kartikeya Dwivedi
2026-03-17 16:50 ` Emil Tsalapatis
2026-03-17 18:46 ` Kumar Kartikeya Dwivedi
2026-03-17 11:18 ` [PATCH bpf-next v1 4/4] selftests/bpf: Test modified syscall ctx for global subprog Kumar Kartikeya Dwivedi
2026-03-17 16:13 ` Emil Tsalapatis
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.