mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH bpf v2] bpf: Validate BTF repeated field counts before expansion
@ 2026-06-10  8:14 Paul Moses
  2026-06-10  8:18 ` Kumar Kartikeya Dwivedi
  0 siblings, 1 reply; 2+ messages in thread
From: Paul Moses @ 2026-06-10  8:14 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 multiplication before copying repeated fields and reject 
impossible counts.

Add a raw BTF test that exercises repeated special-field expansion with a
large array count. The compact element layout keeps the array byte size
representable while the repeated field count overflows the old u32 capacity
calculation in btf_repeat_fields().

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>

---
v1..v2
1. Combine fix and test
2. Drop check_add_overflow
---
 kernel/bpf/btf.c                             |  8 ++---
 tools/testing/selftests/bpf/prog_tests/btf.c | 37 ++++++++++++++++++++
 2 files changed, 40 insertions(+), 5 deletions(-)

diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index a62d78581207..7a28886f1307 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;
 	u32 cur;
 
 	/* Ensure not repeating fields that should not be repeated. */
@@ -3686,10 +3686,8 @@ 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_mul_overflow(field_cnt, repeat_cnt + 1, &total_cnt) ||
+	    total_cnt > (u32)info_cnt)
 		return -E2BIG;
 
 	cur = field_cnt;
diff --git a/tools/testing/selftests/bpf/prog_tests/btf.c b/tools/testing/selftests/bpf/prog_tests/btf.c
index 054ecb6b1e9f..9fcbc554e351 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf.c
@@ -4258,6 +4258,43 @@ static struct btf_raw_test raw_tests[] = {
 	.max_entries = 1,
 },
 
+{
+	.descr = "struct test repeated fields count overflow",
+	.raw_types = {
+		BTF_TYPE_INT_ENC(NAME_TBD, BTF_INT_SIGNED, 0, 32, 4),	/* [1] */
+		BTF_STRUCT_ENC(NAME_TBD, 0, 0),				/* [2] */
+		BTF_TYPE_TAG_ENC(NAME_TBD, 2),				/* [3] */
+		BTF_PTR_ENC(3),						/* [4] */
+		BTF_TYPE_ARRAY_ENC(4, 1, 1),				/* [5] */
+		BTF_STRUCT_ENC(NAME_TBD, 10, 8),			/* [6] */
+		BTF_MEMBER_ENC(NAME_TBD, 5, 0),
+		BTF_MEMBER_ENC(NAME_TBD, 5, 0),
+		BTF_MEMBER_ENC(NAME_TBD, 5, 0),
+		BTF_MEMBER_ENC(NAME_TBD, 5, 0),
+		BTF_MEMBER_ENC(NAME_TBD, 5, 0),
+		BTF_MEMBER_ENC(NAME_TBD, 5, 0),
+		BTF_MEMBER_ENC(NAME_TBD, 5, 0),
+		BTF_MEMBER_ENC(NAME_TBD, 5, 0),
+		BTF_MEMBER_ENC(NAME_TBD, 5, 0),
+		BTF_MEMBER_ENC(NAME_TBD, 5, 0),
+		BTF_TYPE_ARRAY_ENC(6, 1, 0x1999999aU),			/* [7] */
+		BTF_STRUCT_ENC(NAME_TBD, 2, 8 + 8 * 0x1999999aU),	/* [8] */
+		BTF_MEMBER_ENC(NAME_TBD, 4, 0),
+		BTF_MEMBER_ENC(NAME_TBD, 7, 64),
+		BTF_END_RAW,
+	},
+	BTF_STR_SEC("\0int\0prog_test_ref_kfunc\0kptr_untrusted\0elem"
+		    "\0p0\0p1\0p2\0p3\0p4\0p5\0p6\0p7\0p8\0p9"
+		    "\0outer\0trigger\0elems"),
+	.map_type = BPF_MAP_TYPE_ARRAY,
+	.map_name = "repeat_fields",
+	.key_size = sizeof(int),
+	.value_size = 8 + 8 * 0x1999999aU,
+	.key_type_id = 1,
+	.value_type_id = 8,
+	.max_entries = 1,
+	.btf_load_err = true,
+},
 }; /* struct btf_raw_test raw_tests[] */
 
 static const char *get_next_str(const char *start, const char *end)
-- 
2.54.0



^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: [PATCH bpf v2] bpf: Validate BTF repeated field counts before expansion
  2026-06-10  8:14 [PATCH bpf v2] bpf: Validate BTF repeated field counts before expansion Paul Moses
@ 2026-06-10  8:18 ` Kumar Kartikeya Dwivedi
  0 siblings, 0 replies; 2+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-06-10  8:18 UTC (permalink / raw)
  To: Paul Moses, martin.lau, ast, daniel, andrii, eddyz87, memxor, bpf
  Cc: song, yonghong.song, jolsa, houtao1, linux-kernel, stable

On Wed Jun 10, 2026 at 10:14 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 multiplication before copying repeated fields and reject
> impossible counts.
>
> Add a raw BTF test that exercises repeated special-field expansion with a
> large array count. The compact element layout keeps the array byte size
> representable while the repeated field count overflows the old u32 capacity
> calculation in btf_repeat_fields().
>
> 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>
>
> ---
> v1..v2
> 1. Combine fix and test
> 2. Drop check_add_overflow
> ---
>  kernel/bpf/btf.c                             |  8 ++---
>  tools/testing/selftests/bpf/prog_tests/btf.c | 37 ++++++++++++++++++++
>  2 files changed, 40 insertions(+), 5 deletions(-)
>
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index a62d78581207..7a28886f1307 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;
>  	u32 cur;
>
>  	/* Ensure not repeating fields that should not be repeated. */
> @@ -3686,10 +3686,8 @@ 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_mul_overflow(field_cnt, repeat_cnt + 1, &total_cnt) ||
> +	    total_cnt > (u32)info_cnt)

I already applied both of these to bpf-next, it seems patchwork bot didn't
reply. Yeah, check_add_overflow() isn't strictly necessary, though let us let it
be now.

Thanks

>  		return -E2BIG;
>
>  	cur = field_cnt;
> diff --git a/tools/testing/selftests/bpf/prog_tests/btf.c b/tools/testing/selftests/bpf/prog_tests/btf.c
> index 054ecb6b1e9f..9fcbc554e351 100644
> --- a/tools/testing/selftests/bpf/prog_tests/btf.c
> +++ b/tools/testing/selftests/bpf/prog_tests/btf.c
> @@ -4258,6 +4258,43 @@ static struct btf_raw_test raw_tests[] = {
>  	.max_entries = 1,
>  },
>
> +{
> +	.descr = "struct test repeated fields count overflow",
> +	.raw_types = {
> +		BTF_TYPE_INT_ENC(NAME_TBD, BTF_INT_SIGNED, 0, 32, 4),	/* [1] */
> +		BTF_STRUCT_ENC(NAME_TBD, 0, 0),				/* [2] */
> +		BTF_TYPE_TAG_ENC(NAME_TBD, 2),				/* [3] */
> +		BTF_PTR_ENC(3),						/* [4] */
> +		BTF_TYPE_ARRAY_ENC(4, 1, 1),				/* [5] */
> +		BTF_STRUCT_ENC(NAME_TBD, 10, 8),			/* [6] */
> +		BTF_MEMBER_ENC(NAME_TBD, 5, 0),
> +		BTF_MEMBER_ENC(NAME_TBD, 5, 0),
> +		BTF_MEMBER_ENC(NAME_TBD, 5, 0),
> +		BTF_MEMBER_ENC(NAME_TBD, 5, 0),
> +		BTF_MEMBER_ENC(NAME_TBD, 5, 0),
> +		BTF_MEMBER_ENC(NAME_TBD, 5, 0),
> +		BTF_MEMBER_ENC(NAME_TBD, 5, 0),
> +		BTF_MEMBER_ENC(NAME_TBD, 5, 0),
> +		BTF_MEMBER_ENC(NAME_TBD, 5, 0),
> +		BTF_MEMBER_ENC(NAME_TBD, 5, 0),
> +		BTF_TYPE_ARRAY_ENC(6, 1, 0x1999999aU),			/* [7] */
> +		BTF_STRUCT_ENC(NAME_TBD, 2, 8 + 8 * 0x1999999aU),	/* [8] */
> +		BTF_MEMBER_ENC(NAME_TBD, 4, 0),
> +		BTF_MEMBER_ENC(NAME_TBD, 7, 64),
> +		BTF_END_RAW,
> +	},
> +	BTF_STR_SEC("\0int\0prog_test_ref_kfunc\0kptr_untrusted\0elem"
> +		    "\0p0\0p1\0p2\0p3\0p4\0p5\0p6\0p7\0p8\0p9"
> +		    "\0outer\0trigger\0elems"),
> +	.map_type = BPF_MAP_TYPE_ARRAY,
> +	.map_name = "repeat_fields",
> +	.key_size = sizeof(int),
> +	.value_size = 8 + 8 * 0x1999999aU,
> +	.key_type_id = 1,
> +	.value_type_id = 8,
> +	.max_entries = 1,
> +	.btf_load_err = true,
> +},
>  }; /* struct btf_raw_test raw_tests[] */
>
>  static const char *get_next_str(const char *start, const char *end)


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-06-10  8:18 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-10  8:14 [PATCH bpf v2] bpf: Validate BTF repeated field counts before expansion Paul Moses
2026-06-10  8:18 ` Kumar Kartikeya Dwivedi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®