selftests/bpf: Add syscall ctx variable offset tests

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. Unfortunately, ctx_pointer_to_helper_2's unpriv case conflicts
with usage of kfuncs in the file and cannot be run.

Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
Acked-by: Puranjay Mohan <puranjay@kernel.org>
Acked-by: Mykyta Yatsenko <yatsenko@meta.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20260406194403.1649608-5-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Kumar Kartikeya Dwivedi 2026-04-06 21:43:58 +02:00 committed by Alexei Starovoitov
parent 02f500ce01
commit 5a34139b27
2 changed files with 267 additions and 6 deletions

View File

@ -4,6 +4,10 @@
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
#include "../test_kmods/bpf_testmod_kfunc.h"
static const char ctx_strncmp_target[] = "ctx";
static 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 (" \
@ -292,7 +295,7 @@ padding_access("cgroup/post_bind4", bpf_sock, dst_port, 2);
__failure __msg("invalid bpf_context access")
padding_access("sk_reuseport", sk_reuseport_md, hash, 4);
SEC("syscall")
SEC("?syscall")
__description("syscall: write to ctx with fixed offset")
__success
int syscall_ctx_fixed_off_write(void *ctx)
@ -304,6 +307,174 @@ int syscall_ctx_fixed_off_write(void *ctx)
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;
}
/*
* For non-syscall program types without convert_ctx_access, direct ctx
* dereference is still allowed after adding a fixed offset, while variable
@ -314,7 +485,7 @@ int syscall_ctx_fixed_off_write(void *ctx)
* for non-syscall ctx pointers at fixed, variable, and zero-sized accesses.
*/
#define no_rewrite_ctx_access(type, name, off, load_t) \
SEC(type) \
SEC("?" type) \
__description(type ": read ctx at fixed offset") \
__success \
int no_rewrite_##name##_fixed(void *ctx) \
@ -326,7 +497,7 @@ int syscall_ctx_fixed_off_write(void *ctx)
(void)val; \
return 0; \
} \
SEC(type) \
SEC("?" type) \
__description(type ": reject variable offset ctx access") \
__failure __msg("variable ctx access var_off=") \
int no_rewrite_##name##_var(void *ctx) \
@ -338,7 +509,7 @@ int syscall_ctx_fixed_off_write(void *ctx)
p += off_var; \
return *(load_t *)p; \
} \
SEC(type) \
SEC("?" type) \
__description(type ": reject negative offset ctx access") \
__failure __msg("invalid bpf_context access") \
int no_rewrite_##name##_neg(void *ctx) \
@ -347,6 +518,96 @@ int syscall_ctx_fixed_off_write(void *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("kprobe", kprobe, 8, u64);

View File

@ -723,6 +723,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)
@ -1287,7 +1288,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)