* [PATCH bpf v2] bpf: Check variable-offset accesses against special fields
@ 2026-11-08 15:29 Shihuang Liu
2026-11-08 16:01 ` bot+bpf-ci
2026-11-08 16:11 ` Alexei Starovoitov
0 siblings, 2 replies; 4+ messages in thread
From: Shihuang Liu @ 2026-11-08 15:29 UTC (permalink / raw)
To: bpf
Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
yonghong.song, jolsa, emil, ihor.solodrai, Shihuang Liu
btf_struct_access() rejects direct access to special fields (kptr,
spin_lock, list/rbtree nodes, ...) of program allocated objects, but
for a variable offset it only checks min_off.
With compiler-generated BTF the check at min_off is enough: the
register cannot take every value in [min_off, max_off], since
check_variable_offset() keeps the access inside one array with a
step that is a multiple of the element size, and btf_repeat_fields()
repeats the special fields for every element. Every reachable
offset therefore selects the same member of some element.
The check breaks for hand-written BTF with overlapping members.
btf_struct_check_meta() only rejects offsets that go backwards, so
this layout is accepted:
struct node {
int arr[4]; /* offset 0, size 16 */
struct foo __kptr *p; /* offset 8 */
};
A 4-byte store to arr[i], i in [0, 3], passes the check at offset 0 and
overwrites p at run time when i == 2 or 3; the release path
(bpf_obj_free_fields()) then frees the corrupted pointer.
Check every offset the register can take (min_off + k * step)
against the record. Constant offsets keep the existing check.
Since only reachable offsets are checked, accesses that stay on a
non-special member of every element (e.g. a store to arr[i].data
with step == sizeof(elem)) keep working.
Fixes: 89e72f81a455 ("bpf: Varying offset access support for PTR_TO_BTF_ID pointers")
Link: https://lore.kernel.org/bpf/20261107135419.81491-1-shlomojune6@gmail.com/
Signed-off-by: Shihuang Liu <shlomojune6@gmail.com>
---
Changes since v1:
- Check only the reachable offsets (min_off + k * step) instead of the
whole [min_off, max_off + size) range
- Rework the commit message
kernel/bpf/btf.c | 42 +++++++++++++++++++++++++++++++++++-------
1 file changed, 35 insertions(+), 7 deletions(-)
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 8bf39098ba97a..738625d8e5879 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -8056,6 +8056,21 @@ error:
return -EINVAL;
}
+static bool btf_field_is_accessed(const struct bpf_reg_state *reg,
+ s64 min_off, s64 max_off, int size,
+ const struct btf_field *field)
+{
+ s64 first, last;
+
+ first = max_t(s64, min_off, (s64)field->offset - size + 1);
+ last = min_t(s64, max_off, (s64)field->offset + field->size - 1);
+ if (first > last)
+ return false;
+
+ first += imod(min_off - first, reg->step);
+ return first <= last;
+}
+
int btf_struct_access(struct bpf_verifier_log *log,
const struct bpf_reg_state *reg,
int _off, int size, enum bpf_access_type atype __maybe_unused,
@@ -8091,15 +8106,28 @@ int btf_struct_access(struct bpf_verifier_log *log,
if (!meta)
break;
rec = meta->record;
+ if (min_off == max_off) {
+ for (i = 0; i < rec->cnt; i++) {
+ struct btf_field *field = &rec->fields[i];
+ u32 offset = field->offset;
+
+ if (off < offset + field->size && offset < off + size) {
+ bpf_log(log, "direct access to %s is disallowed\n",
+ btf_field_type_name(field->type));
+ return -EACCES;
+ }
+ }
+ break;
+ }
+
for (i = 0; i < rec->cnt; i++) {
struct btf_field *field = &rec->fields[i];
- u32 offset = field->offset;
- if (off < offset + field->size && offset < off + size) {
- bpf_log(log,
- "direct access to %s is disallowed\n",
- btf_field_type_name(field->type));
- return -EACCES;
- }
+
+ if (!btf_field_is_accessed(reg, min_off, max_off, size, field))
+ continue;
+ bpf_log(log, "direct access to %s is disallowed\n",
+ btf_field_type_name(field->type));
+ return -EACCES;
}
break;
}
^ permalink raw reply related [flat|nested] 4+ messages in thread* Re: [PATCH bpf v2] bpf: Check variable-offset accesses against special fields
2026-11-08 15:29 [PATCH bpf v2] bpf: Check variable-offset accesses against special fields Shihuang Liu
@ 2026-11-08 16:01 ` bot+bpf-ci
2026-11-08 16:11 ` Alexei Starovoitov
1 sibling, 0 replies; 4+ messages in thread
From: bot+bpf-ci @ 2026-11-08 16:01 UTC (permalink / raw)
To: shlomojune6, bpf
Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
yonghong.song, jolsa, emil, ihor.solodrai, shlomojune6, ast,
andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2233 bytes --]
> bpf: Check variable-offset accesses against special fields
TL;DR: Nit only: the constant-offset loop in btf_struct_access() duplicates
the variable-offset loop, and the review asks whether it could be dropped so
one loop handles both cases.
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
[ ... ]
> @@ -8091,15 +8106,28 @@ int btf_struct_access(struct bpf_verifier_log *log,
> if (!meta)
> break;
> rec = meta->record;
> + if (min_off == max_off) {
> + for (i = 0; i < rec->cnt; i++) {
> + struct btf_field *field = &rec->fields[i];
> + u32 offset = field->offset;
> +
> + if (off < offset + field->size && offset < off + size) {
> + bpf_log(log, "direct access to %s is disallowed\n",
> + btf_field_type_name(field->type));
> + return -EACCES;
> + }
> + }
> + break;
> + }
> +
> for (i = 0; i < rec->cnt; i++) {
> struct btf_field *field = &rec->fields[i];
> - u32 offset = field->offset;
> - if (off < offset + field->size && offset < off + size) {
> - bpf_log(log,
> - "direct access to %s is disallowed\n",
> - btf_field_type_name(field->type));
> - return -EACCES;
> - }
> +
> + if (!btf_field_is_accessed(reg, min_off, max_off, size, field))
> + continue;
> + bpf_log(log, "direct access to %s is disallowed\n",
> + btf_field_type_name(field->type));
> + return -EACCES;
> }
> break;
> }
This isn't a bug, but the two loops over rec->fields[] are near copies of
each other, including the same bpf_log() message.
When min_off == max_off, btf_field_is_accessed() gives the same answer as
the constant-offset loop: first and last reduce to the plain overlap window
around min_off, and imod(min_off - first, reg->step) is imod(0, step), so
the result is just first <= last.
Since btf_field_is_accessed() returns the same result when min_off ==
max_off, could the constant-offset block be dropped so that a single loop
handles both cases?
---
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/42495726162
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [PATCH bpf v2] bpf: Check variable-offset accesses against special fields
2026-11-08 15:29 [PATCH bpf v2] bpf: Check variable-offset accesses against special fields Shihuang Liu
2026-11-08 16:01 ` bot+bpf-ci
@ 2026-11-08 16:11 ` Alexei Starovoitov
2026-11-09 9:47 ` J4f J4f
1 sibling, 1 reply; 4+ messages in thread
From: Alexei Starovoitov @ 2026-11-08 16:11 UTC (permalink / raw)
To: Shihuang Liu, bpf
Cc: daniel, andrii, eddyz87, memxor, martin.lau, song, yonghong.song,
jolsa, emil, ihor.solodrai
On Sun, Nov 08, 2026 at 07:29 AM Shihuang Liu <shlomojune6@gmail.com> wrote:
> Check every offset the register can take (min_off + k * step)
> against the record. Constant offsets keep the existing check.
No need for the helper and the second loop.
reg->step is never 0. ___mark_reg_known() sets it to 1 and
reg_bounds_sanity_check() reports 0 as a verifier bug.
So a constant offset is the k == 0 case of the same check.
Something like this (compile tested only):
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 8bf3909..2265362 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -8094,7 +8094,12 @@ int btf_struct_access(struct bpf_verifier_log *log,
for (i = 0; i < rec->cnt; i++) {
struct btf_field *field = &rec->fields[i];
u32 offset = field->offset;
- if (off < offset + field->size && offset < off + size) {
+ /* An access at 'lo' or above ends after the start of the field. */
+ s64 lo = max(min_off, (s64)offset - size + 1);
+
+ /* Round up to min_off + k * step. The step is never 0. */
+ lo += imod(min_off - lo, reg->step);
+ if (lo <= max_off && lo < offset + field->size) {
bpf_log(log,
"direct access to %s is disallowed\n",
btf_field_type_name(field->type));
The store to arr[i] from the commit log. It loads today. With the fix
the verifier says "direct access to kptr is disallowed".
clang can't emit such BTF. Compile the struct with p at offset 16.
After __open() move p to offset 8 in bpf_object__btf(), then load.
The store to arr[i].data from my reply to v1. It has to keep loading.
pw-bot: cr
^ permalink raw reply related [flat|nested] 4+ messages in thread* Re: [PATCH bpf v2] bpf: Check variable-offset accesses against special fields
2026-11-08 16:11 ` Alexei Starovoitov
@ 2026-11-09 9:47 ` J4f J4f
0 siblings, 0 replies; 4+ messages in thread
From: J4f J4f @ 2026-11-09 9:47 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: bpf, daniel, andrii, eddyz87, memxor, martin.lau, song,
yonghong.song, jolsa, emil, ihor.solodrai
On Mon, 9 Nov 2026 at 00:11, Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> On Sun, Nov 08, 2026 at 07:29 AM Shihuang Liu <shlomojune6@gmail.com> wrote:
> > Check every offset the register can take (min_off + k * step)
> > against the record. Constant offsets keep the existing check.
>
> No need for the helper and the second loop.
> reg->step is never 0. ___mark_reg_known() sets it to 1 and
> reg_bounds_sanity_check() reports 0 as a verifier bug.
> So a constant offset is the k == 0 case of the same check.
>
> Something like this (compile tested only):
>
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index 8bf3909..2265362 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
> @@ -8094,7 +8094,12 @@ int btf_struct_access(struct bpf_verifier_log *log,
> for (i = 0; i < rec->cnt; i++) {
> struct btf_field *field = &rec->fields[i];
> u32 offset = field->offset;
> - if (off < offset + field->size && offset < off + size) {
> + /* An access at 'lo' or above ends after the start of the field. */
> + s64 lo = max(min_off, (s64)offset - size + 1);
> +
> + /* Round up to min_off + k * step. The step is never 0. */
> + lo += imod(min_off - lo, reg->step);
> + if (lo <= max_off && lo < offset + field->size) {
> bpf_log(log,
> "direct access to %s is disallowed\n",
> btf_field_type_name(field->type));
>
>
> The store to arr[i] from the commit log. It loads today. With the fix
> the verifier says "direct access to kptr is disallowed".
> clang can't emit such BTF. Compile the struct with p at offset 16.
> After __open() move p to offset 8 in bpf_object__btf(), then load.
>
> The store to arr[i].data from my reply to v1. It has to keep loading.
>
> pw-bot: cr
Thanks for the review and the suggestion.
Sending v3 shortly.
Thanks,
Shihuang
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-11-09 9:47 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-08 15:29 [PATCH bpf v2] bpf: Check variable-offset accesses against special fields Shihuang Liu
2026-11-08 16:01 ` bot+bpf-ci
2026-11-08 16:11 ` Alexei Starovoitov
2026-11-09 9:47 ` J4f J4f
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.