An invalid BTF.ext subsection length can wrap the pointer addition used by
btf_ext_parse_sec_info() on 32-bit builds. The wrapped pointer passes the
bounds check and parsing then reads beyond the copied BTF.ext data.
Validate the offset and length with subtraction before forming the section
pointer.
Fixes: ae4ab4b4117d ("btf: expose API to work with raw btf_ext data")
Closes: https://issues.oss-fuzz.com/issues/477315119
Signed-off-by: Darren Carreras <carrerasdarren@gmail.com>
---
Changes in v5:
- Rebase onto bpf-next commit d114bb989367.
- Attach one diff-only patch; the v4 MIME body contained a duplicated diff and
could not be applied by CI.
Changes in v4:
- Drop the selftest because the malformed length is already rejected by the
old check on 64-bit CI; the behavioral divergence is specific to 32-bit.
- Correct the Fixes tag to the commit that introduced the pointer-based check.
Changes in v3:
- Remove the nested mbox envelope and mail headers from the Gmail attachment
so Patchwork's generated mbox applies with git am.
Changes in v2:
- Resend as plain text because Gmail mangled the v1 diff and Patchwork
reported "Patch is empty."
- Include the authorized DCO Signed-off-by line.
diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c
index 8417de92..744f33e2 100644
--- a/tools/lib/bpf/btf.c
+++ b/tools/lib/bpf/btf.c
@@ -3364,7 +3364,7 @@ static int btf_ext_parse_sec_info(struct btf_ext *btf_ext,
{
const struct btf_ext_info_sec *sinfo;
struct btf_ext_info *ext_info;
- __u32 info_left, record_size;
+ __u32 data_left, info_left, record_size;
size_t sec_cnt = 0;
void *info;
@@ -3377,16 +3377,17 @@ static int btf_ext_parse_sec_info(struct btf_ext *btf_ext,
return -EINVAL;
}
- /* The start of the info sec (including the __u32 record_size). */
- info = btf_ext->data + btf_ext->hdr->hdr_len + ext_sec->off;
- info_left = ext_sec->len;
-
- if (btf_ext->data + btf_ext->data_size < info + ext_sec->len) {
+ data_left = btf_ext->data_size - btf_ext->hdr->hdr_len;
+ if (ext_sec->off > data_left || ext_sec->len > data_left - ext_sec->off) {
pr_debug("%s section (off:%u len:%u) is beyond the end of the ELF section .BTF.ext\n",
ext_sec->desc, ext_sec->off, ext_sec->len);
return -EINVAL;
}
+ /* The start of the info sec (including the __u32 record_size). */
+ info = btf_ext->data + btf_ext->hdr->hdr_len + ext_sec->off;
+ info_left = ext_sec->len;
+
/* At least a record size */
if (info_left < sizeof(__u32)) {
pr_debug(".BTF.ext %s record size not found\n", ext_sec->desc);
On Tue, Aug 11, 2026 at 9:49 AM Darren Carreras
<carrerasdarren@gmail.com> wrote:
>
> An invalid BTF.ext subsection length can wrap the pointer addition used by
> btf_ext_parse_sec_info() on 32-bit builds. The wrapped pointer passes the
> bounds check and parsing then reads beyond the copied BTF.ext data.
>
> Validate the offset and length with subtraction before forming the section
> pointer.
>
> Fixes: ae4ab4b4117d ("btf: expose API to work with raw btf_ext data")
> Closes: https://issues.oss-fuzz.com/issues/477315119
> Signed-off-by: Darren Carreras <carrerasdarren@gmail.com>
> ---
> Changes in v5:
> - Rebase onto bpf-next commit d114bb989367.
> - Attach one diff-only patch; the v4 MIME body contained a duplicated diff and
> could not be applied by CI.
>
> Changes in v4:
> - Drop the selftest because the malformed length is already rejected by the
> old check on 64-bit CI; the behavioral divergence is specific to 32-bit.
> - Correct the Fixes tag to the commit that introduced the pointer-based check.
>
> Changes in v3:
> - Remove the nested mbox envelope and mail headers from the Gmail attachment
> so Patchwork's generated mbox applies with git am.
>
> Changes in v2:
> - Resend as plain text because Gmail mangled the v1 diff and Patchwork
> reported "Patch is empty."
> - Include the authorized DCO Signed-off-by line.
It's hard to review and reply because you didn't post patch inline
(please don't do this for subsequent contributions), but here's the
gist:
- /* The start of the info sec (including the __u32 record_size). */
- info = btf_ext->data + btf_ext->hdr->hdr_len + ext_sec->off;
- info_left = ext_sec->len;
-
- if (btf_ext->data + btf_ext->data_size < info + ext_sec->len) {
+ data_left = btf_ext->data_size - btf_ext->hdr->hdr_len;
From what I understand, you are trying to protect against corrupted
ELF that specifies invalid .BTF.ext ELF section size, is that right?
And your fix makes another assumption that btf_ext->hdr->hdr_len is
definitely not malformed, because otherwise this btf_ext->data_size -
btf_ext->hdr->hdr_len can underflow, no?
So in both cases, if ELF or BTF content is corrupted, there might be a problem.
+ if (ext_sec->off > data_left || ext_sec->len > data_left - ext_sec->off) {
pr_debug("%s section (off:%u len:%u) is beyond the end of the ELF
section .BTF.ext\n",
ext_sec->desc, ext_sec->off, ext_sec->len);
return -EINVAL;
}
+ /* The start of the info sec (including the __u32 record_size). */
+ info = btf_ext->data + btf_ext->hdr->hdr_len + ext_sec->off;
+ info_left = ext_sec->len;
+
© 2016 - 2026 Red Hat, Inc.