* [BUG] libbpf: BTF dedup merges arrays with different element types
@ 2026-09-06 10:23 Mingpei CAO
2026-09-06 21:27 ` Alexei Starovoitov
2026-09-07 13:10 ` [PATCH bpf 0/2] libbpf: Fix array comparison in BTF dedup Mingpei CAO
0 siblings, 2 replies; 7+ messages in thread
From: Mingpei CAO @ 2026-09-06 10:23 UTC (permalink / raw)
To: bpf; +Cc: andrii, eddyz87
Hello,
I found a deduplication bug in libbpf's linker. In the recursive identical
type check, libbpf obtains both array descriptors from the first type.
Arrays with the same length but different element types can therefore be
treated as identical. In a valid two-source BPF program, this changed a
CO-RE access from tls_array[0].limit0 to tls_array[0].blob and caused the
verifier to reject.
The two source files and full build and run logs are available on request.
AI assistance was used in preparing this report, I independently reviewed
the source and results.
Affected versions
-----------------
The faulty recursive array comparison was introduced in libbpf v1.6.0. I
verified it in v1.6.0, v1.6.1, v1.6.2, v1.6.3, v1.7.0, current libbpf
master, and current bpf-next as of September 6, 2026. v1.5.1 uses the
earlier two-array comparison and does not contain this root cause. No fixed
release was known at the time of testing.
Trigger conditions
------------------
The reproducer consists of two source files, cu-a.c and cu-b.c. Within each
pipeline, each file is compiled once, producing two compilation units that
are then linked. Both files define struct thread_struct___dedup_probe with
the same name, size, member names, and member offsets.
In cu-a.c, both arrays contain struct wrong_desc, whose only member is the
64-bit field blob. In cu-b.c, the first array also contains wrong_desc,
while the second contains struct desc_struct___dedup_probe, whose first
member is the 16-bit field limit0 followed by padding. Both element records
are exactly eight bytes, so no size adjustment is involved.
The BPF program is defined in cu-b.c and has one CO-RE relocation for:
thread->tls_array[0].limit0
The first matching array establishes the hypothetical type mapping that
reaches the faulty recursive comparison for the second array.
Root cause
----------
In btf_dedup_identical_types(), the BTF_KIND_ARRAY branch at
src/btf.c:4829-4830 reads t1 twice:
a1 = btf_array(t1);
a2 = btf_array(t1);
btf_compat_array(t1, t2) checks common array metadata and the element
count, but intentionally leaves referenced types for the following
recursive checks. Because a1 and a2 both point into t1, their index and
element type IDs are equal, those checks are skipped, and the function
returns true without examining t2's referenced types.
The linker calls btf__dedup() from finalize_btf() in src/linker.c.
Observed results
----------------
I compiled cu-a.c and cu-b.c with clang 18.1.3. For libbpf, each source
file was compiled to one BPF ELF object before linking. For Aya, the same
two source files were compiled to LLVM bitcode before linking with
bpf-linker v0.11.0. I tested both resulting programs in an x86-64 Linux
6.8.12 QEMU guest.
The current libbpf linker collapsed the two outer records into one. The
CO-RE access string remained 0:1:0:0, but its remapped root made that
string describe tls_array[0].blob instead of tls_array[0].limit0. The
matching libbpf loader completed user-space relocation, submitted
BPF_PROG_LOAD, and the verifier rejected the poisoned unresolved CO-RE
relocation with EINVAL. This reproduced with both the BPF JIT enabled and
disabled.
Aya bpf-linker retained both outer records and the intended limit0 access.
The matching Aya loader submitted BPF_PROG_LOAD and the verifier accepted
the program with both the JIT enabled and disabled.
As a causal control, I rebuilt the libbpf linker with only the t1/t2 change
shown below. The same two input objects then retained both outer records
and the limit0 access. The matching libbpf loader accepted the program with
the JIT enabled and disabled.
Impact
------
The confirmed impact is incorrect BTF type identity and CO-RE metadata from
the libbpf linker, causing a valid multi-CU BPF program to be rejected.
Proposed fix
------------
The second array descriptor should be obtained from t2:
a1 = btf_array(t1);
a2 = btf_array(t2);
I applied this change to the assessed source and reran the libbpf linker and
loader controls. The output retained both types and the verifier accepted
the program.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [BUG] libbpf: BTF dedup merges arrays with different element types
2026-09-06 10:23 [BUG] libbpf: BTF dedup merges arrays with different element types Mingpei CAO
@ 2026-09-06 21:27 ` Alexei Starovoitov
2026-09-07 13:10 ` [PATCH bpf 0/2] libbpf: Fix array comparison in BTF dedup Mingpei CAO
1 sibling, 0 replies; 7+ messages in thread
From: Alexei Starovoitov @ 2026-09-06 21:27 UTC (permalink / raw)
To: Mingpei CAO; +Cc: bpf, Andrii Nakryiko, Eduard
On Sun, Sep 6, 2026 at 3:23 AM Mingpei CAO <caomingpei@gmail.com> wrote:
>
>
> Proposed fix
> ------------
>
> The second array descriptor should be obtained from t2:
>
> a1 = btf_array(t1);
> a2 = btf_array(t2);
>
> I applied this change to the assessed source and reran the libbpf linker and
> loader controls. The output retained both types and the verifier accepted
> the program.
Please submit the fix and selftest as two patches.
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH bpf 0/2] libbpf: Fix array comparison in BTF dedup
2026-09-06 10:23 [BUG] libbpf: BTF dedup merges arrays with different element types Mingpei CAO
2026-09-06 21:27 ` Alexei Starovoitov
@ 2026-09-07 13:10 ` Mingpei CAO
2026-09-07 13:10 ` [PATCH bpf 1/2] " Mingpei CAO
2026-09-07 13:10 ` [PATCH bpf 2/2] selftests/bpf: Test array element " Mingpei CAO
1 sibling, 2 replies; 7+ messages in thread
From: Mingpei CAO @ 2026-09-07 13:10 UTC (permalink / raw)
To: bpf; +Cc: alexei.starovoitov, andrii, eddyz87, Mingpei CAO
Follow up on "[BUG] libbpf: BTF dedup merges arrays with different element
types" with a fix and selftest as two patches.
Report: https://lore.kernel.org/bpf/CABzjXVz3iBuump-pXFkefZ5v+2uu4fG61zqRyWD4xmz3PuBycw@mail.gmail.com/
Validation with a focused runner using the existing selftest sources and
assertions:
- All 52 selected existing BTF cases passed before and after the fix.
- The new negative case failed before the fix and passed afterwards.
- The new positive case passed before and after the fix.
- One module test was skipped because bpf_testmod was unavailable.
The full test_progs suite was not run.
Mingpei CAO (2):
libbpf: Fix array comparison in BTF dedup
selftests/bpf: Test array element comparison in BTF dedup
tools/lib/bpf/btf.c | 2 +-
.../selftests/bpf/prog_tests/btf_dedup.c | 68 +++++++++++++++++++
2 files changed, 69 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_dedup.c
base-commit: df2908090cda368b01ff43709f51890076c56157
--
2.43.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH bpf 1/2] libbpf: Fix array comparison in BTF dedup
2026-09-07 13:10 ` [PATCH bpf 0/2] libbpf: Fix array comparison in BTF dedup Mingpei CAO
@ 2026-09-07 13:10 ` Mingpei CAO
2026-09-07 13:10 ` [PATCH bpf 2/2] selftests/bpf: Test array element " Mingpei CAO
1 sibling, 0 replies; 7+ messages in thread
From: Mingpei CAO @ 2026-09-07 13:10 UTC (permalink / raw)
To: bpf; +Cc: alexei.starovoitov, andrii, eddyz87, Mingpei CAO
btf_dedup_identical_types() reads both array descriptors from t1,
skipping comparisons of their referenced types. This can incorrectly
merge distinct structs and corrupt CO-RE relocation metadata.
Read the second descriptor from t2.
Fixes: 62e23f183839 ("libbpf: Improve BTF dedup handling of "identical" BTF types")
Closes: https://lore.kernel.org/bpf/CABzjXVz3iBuump-pXFkefZ5v+2uu4fG61zqRyWD4xmz3PuBycw@mail.gmail.com/
Assisted-by: LLM
Signed-off-by: Mingpei CAO <caomingpei@gmail.com>
---
tools/lib/bpf/btf.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c
index 8417de92d028..41cc3140925f 100644
--- a/tools/lib/bpf/btf.c
+++ b/tools/lib/bpf/btf.c
@@ -4827,7 +4827,7 @@ static bool btf_dedup_identical_types(struct btf_dedup *d, __u32 id1, __u32 id2,
return false;
a1 = btf_array(t1);
- a2 = btf_array(t1);
+ a2 = btf_array(t2);
if (a1->index_type != a2->index_type &&
!btf_dedup_identical_types(d, a1->index_type, a2->index_type, depth - 1))
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH bpf 2/2] selftests/bpf: Test array element comparison in BTF dedup
2026-09-07 13:10 ` [PATCH bpf 0/2] libbpf: Fix array comparison in BTF dedup Mingpei CAO
2026-09-07 13:10 ` [PATCH bpf 1/2] " Mingpei CAO
@ 2026-09-07 13:10 ` Mingpei CAO
2026-09-07 15:45 ` Alan Maguire
1 sibling, 1 reply; 7+ messages in thread
From: Mingpei CAO @ 2026-09-07 13:10 UTC (permalink / raw)
To: bpf; +Cc: alexei.starovoitov, andrii, eddyz87, Mingpei CAO
Exercise recursive array comparison with different and identical
element definitions. Check that distinct containers remain separate
and identical types are deduplicated, including their references.
The negative case fails before the fix and passes afterwards.
The positive case passes both before and after the fix.
Assisted-by: LLM
Signed-off-by: Mingpei CAO <caomingpei@gmail.com>
---
.../selftests/bpf/prog_tests/btf_dedup.c | 68 +++++++++++++++++++
1 file changed, 68 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_dedup.c
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_dedup.c b/tools/testing/selftests/bpf/prog_tests/btf_dedup.c
new file mode 100644
index 000000000000..3e889c0bed53
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/btf_dedup.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include <bpf/btf.h>
+#include "btf_helpers.h"
+
+static void test_array_element_types(bool identical)
+{
+ struct btf *btf;
+
+ btf = btf__new_empty();
+ if (!ASSERT_OK_PTR(btf, "btf_new"))
+ return;
+
+ /*
+ * Comparing the first fields maps array [3] to [6]. The second
+ * fields reuse [3], requiring an identical-type check of [6]/[7].
+ * Place container [5] before its elements [8]/[9] so they still
+ * have distinct IDs when their definitions are compared.
+ */
+ if (!ASSERT_EQ(btf__add_int(btf, "int", 4, BTF_INT_SIGNED), 1, "int") ||
+ !ASSERT_EQ(btf__add_struct(btf, "container", 8), 2, "container1") ||
+ !ASSERT_OK(btf__add_field(btf, "first", 3, 0, 0), "first1") ||
+ !ASSERT_OK(btf__add_field(btf, "second", 3, 32, 0), "second1") ||
+ !ASSERT_EQ(btf__add_array(btf, 1, 4, 1), 3, "array1") ||
+ !ASSERT_EQ(btf__add_struct(btf, "elem", 4), 4, "elem1") ||
+ !ASSERT_OK(btf__add_field(btf, "x", 1, 0, 0), "elem1_field") ||
+ !ASSERT_EQ(btf__add_struct(btf, "container", 8), 5, "container2") ||
+ !ASSERT_OK(btf__add_field(btf, "first", 6, 0, 0), "first2") ||
+ !ASSERT_OK(btf__add_field(btf, "second", 7, 32, 0), "second2") ||
+ !ASSERT_EQ(btf__add_array(btf, 1, 8, 1), 6, "array2") ||
+ !ASSERT_EQ(btf__add_array(btf, 1, 9, 1), 7, "array3") ||
+ !ASSERT_EQ(btf__add_struct(btf, "elem", 4), 8, "elem2") ||
+ !ASSERT_OK(btf__add_field(btf, "x", 1, 0, 0), "elem2_field") ||
+ !ASSERT_EQ(btf__add_struct(btf, "elem", 4), 9, "elem3") ||
+ !ASSERT_OK(btf__add_field(btf, identical ? "x" : "y", 1, 0, 0), "elem3_field"))
+ goto out;
+
+ if (!ASSERT_OK(btf__dedup(btf, NULL), "dedup"))
+ goto out;
+
+ /* Identical elements leave only the first four expected types. */
+ btf_validate_raw(btf, identical ? 4 : 7, (const char *[]) {
+ "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
+ "[2] STRUCT 'container' size=8 vlen=2\n"
+ "\t'first' type_id=3 bits_offset=0\n"
+ "\t'second' type_id=3 bits_offset=32",
+ "[3] ARRAY '(anon)' type_id=4 index_type_id=1 nr_elems=1",
+ "[4] STRUCT 'elem' size=4 vlen=1\n"
+ "\t'x' type_id=1 bits_offset=0",
+ "[5] STRUCT 'container' size=8 vlen=2\n"
+ "\t'first' type_id=3 bits_offset=0\n"
+ "\t'second' type_id=6 bits_offset=32",
+ "[6] ARRAY '(anon)' type_id=7 index_type_id=1 nr_elems=1",
+ "[7] STRUCT 'elem' size=4 vlen=1\n"
+ "\t'y' type_id=1 bits_offset=0",
+ });
+
+out:
+ btf__free(btf);
+}
+
+void test_btf_dedup(void)
+{
+ if (test__start_subtest("array_different_element_types"))
+ test_array_element_types(false);
+ if (test__start_subtest("array_identical_element_types"))
+ test_array_element_types(true);
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH bpf 2/2] selftests/bpf: Test array element comparison in BTF dedup
2026-09-07 13:10 ` [PATCH bpf 2/2] selftests/bpf: Test array element " Mingpei CAO
@ 2026-09-07 15:45 ` Alan Maguire
2026-09-07 16:30 ` Mingpei CAO
0 siblings, 1 reply; 7+ messages in thread
From: Alan Maguire @ 2026-09-07 15:45 UTC (permalink / raw)
To: Mingpei CAO, bpf; +Cc: alexei.starovoitov, andrii, eddyz87
On 07/09/2026 14:10, Mingpei CAO wrote:
> Exercise recursive array comparison with different and identical
> element definitions. Check that distinct containers remain separate
> and identical types are deduplicated, including their references.
>
> The negative case fails before the fix and passes afterwards.
> The positive case passes both before and after the fix.
>
> Assisted-by: LLM
> Signed-off-by: Mingpei CAO <caomingpei@gmail.com>
this test would be better placed in prog_tests/btf.c I think where there
are a number of other BTF dedup tests; something like the following
is roughly equivalent to what you have:
diff --git a/tools/testing/selftests/bpf/prog_tests/btf.c b/tools/testing/selftests/bpf/prog_tests/btf.c
index 67b9015cbd98..1c4a46e2a64d 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf.c
@@ -7062,6 +7062,94 @@ static struct btf_dedup_test dedup_tests[] = {
BTF_STR_SEC("\0int\0long int"),
},
},
+{
+ .descr = "dedup: array element comparison",
+ .input = {
+ .raw_types = {
+ /* signed int */
+ BTF_TYPE_INT_ENC(NAME_NTH(1), BTF_INT_SIGNED, 0, 32, 4),
/* [1] */
+ /* unsigned int */
+ BTF_TYPE_INT_ENC(NAME_NTH(2), 0, 0, 32, 4),
/* [2] */
+ /* signed int[1] */
+ BTF_TYPE_ARRAY_ENC(1, 1, 1),
/* [3] */
+ /* duplicate signed int[1] */
+ BTF_TYPE_ARRAY_ENC(1, 1, 1), /* [4] */
+ /* unsigned int[1] */
+ BTF_TYPE_ARRAY_ENC(2, 2, 1), /* [5] */
+ /* struct s { signed int a[1]; signed int b[1]; } */
+ BTF_STRUCT_ENC(NAME_NTH(3), 2, 8), /* [6] */
+ BTF_MEMBER_ENC(NAME_NTH(4), 3, 0),
+ BTF_MEMBER_ENC(NAME_NTH(5), 3, 32),
+ /* struct s { signed int a[1]; unsigned int b[1]; } */
+ BTF_STRUCT_ENC(NAME_NTH(3), 2, 8), /* [7] */
+ BTF_MEMBER_ENC(NAME_NTH(4), 4, 0),
+ BTF_MEMBER_ENC(NAME_NTH(5), 5, 32),
+ BTF_END_RAW,
+ },
+ BTF_STR_SEC("\0int\0unsigned int\0s\0a\0b"),
+ },
+ .expect = {
+ .raw_types = {
+ BTF_TYPE_INT_ENC(NAME_NTH(1), BTF_INT_SIGNED, 0, 32, 4), /* [1] */
+ BTF_TYPE_INT_ENC(NAME_NTH(2), 0, 0, 32, 4), /* [2] */
+ BTF_TYPE_ARRAY_ENC(1, 1, 1), /* [3] */
+ BTF_TYPE_ARRAY_ENC(2, 2, 1), /* [4] */
+ BTF_STRUCT_ENC(NAME_NTH(3), 2, 8), /* [5] */
+ BTF_MEMBER_ENC(NAME_NTH(4), 3, 0),
+ BTF_MEMBER_ENC(NAME_NTH(5), 3, 32),
+ BTF_STRUCT_ENC(NAME_NTH(3), 2, 8), /* [6] */
+ BTF_MEMBER_ENC(NAME_NTH(4), 3, 0),
+ BTF_MEMBER_ENC(NAME_NTH(5), 4, 32),
+ BTF_END_RAW,
+ },
+ BTF_STR_SEC("\0int\0unsigned int\0s\0a\0b"),
+ },
+},
+{
+ .descr = "dedup: identical array element comparison",
+ .input = {
+ .raw_types = {
+ /* int */
+ BTF_TYPE_INT_ENC(NAME_NTH(1), BTF_INT_SIGNED, 0, 32, 4), /* [1] */
+ /* struct container { struct elem first[1]; struct elem second[1]; } */
+ BTF_STRUCT_ENC(NAME_NTH(2), 2, 8), /* [2] */
+ BTF_MEMBER_ENC(NAME_NTH(3), 3, 0),
+ BTF_MEMBER_ENC(NAME_NTH(4), 3, 32),
+ /* struct elem[1] */
+ BTF_TYPE_ARRAY_ENC(4, 1, 1), /* [3] */
+ /* struct elem { int x; } */
+ BTF_STRUCT_ENC(NAME_NTH(5), 1, 4), /* [4] */
+ BTF_MEMBER_ENC(NAME_NTH(6), 1, 0),
+ /* duplicate struct container */
+ BTF_STRUCT_ENC(NAME_NTH(2), 2, 8), /* [5] */
+ BTF_MEMBER_ENC(NAME_NTH(3), 6, 0),
+ BTF_MEMBER_ENC(NAME_NTH(4), 7, 32),
+ /* duplicate struct elem[1] */
+ BTF_TYPE_ARRAY_ENC(8, 1, 1), /* [6] */
+ BTF_TYPE_ARRAY_ENC(9, 1, 1), /* [7] */
+ /* duplicate struct elem */
+ BTF_STRUCT_ENC(NAME_NTH(5), 1, 4), /* [8] */
+ BTF_MEMBER_ENC(NAME_NTH(6), 1, 0),
+ BTF_STRUCT_ENC(NAME_NTH(5), 1, 4), /* [9] */
+ BTF_MEMBER_ENC(NAME_NTH(6), 1, 0),
+ BTF_END_RAW,
+ },
+ BTF_STR_SEC("\0int\0container\0first\0second\0elem\0x"),
+ },
+ .expect = {
+ .raw_types = {
+ BTF_TYPE_INT_ENC(NAME_NTH(1), BTF_INT_SIGNED, 0, 32, 4), /* [1] */
+ BTF_STRUCT_ENC(NAME_NTH(2), 2, 8), /* [2] */
+ BTF_MEMBER_ENC(NAME_NTH(3), 3, 0),
+ BTF_MEMBER_ENC(NAME_NTH(4), 3, 32),
+ BTF_TYPE_ARRAY_ENC(4, 1, 1), /* [3] */
+ BTF_STRUCT_ENC(NAME_NTH(5), 1, 4), /* [4] */
+ BTF_MEMBER_ENC(NAME_NTH(6), 1, 0),
+ BTF_END_RAW,
+ },
+ BTF_STR_SEC("\0int\0container\0first\0second\0elem\0x"),
+ },
+},
{
.descr = "dedup: struct example #1",
/*
> ---
> .../selftests/bpf/prog_tests/btf_dedup.c | 68 +++++++++++++++++++
> 1 file changed, 68 insertions(+)
> create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_dedup.c
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/btf_dedup.c b/tools/testing/selftests/bpf/prog_tests/btf_dedup.c
> new file mode 100644
> index 000000000000..3e889c0bed53
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/btf_dedup.c
> @@ -0,0 +1,68 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <test_progs.h>
> +#include <bpf/btf.h>
> +#include "btf_helpers.h"
> +
> +static void test_array_element_types(bool identical)
> +{
> + struct btf *btf;
> +
> + btf = btf__new_empty();
> + if (!ASSERT_OK_PTR(btf, "btf_new"))
> + return;
> +
> + /*
> + * Comparing the first fields maps array [3] to [6]. The second
> + * fields reuse [3], requiring an identical-type check of [6]/[7].
> + * Place container [5] before its elements [8]/[9] so they still
> + * have distinct IDs when their definitions are compared.
> + */
> + if (!ASSERT_EQ(btf__add_int(btf, "int", 4, BTF_INT_SIGNED), 1, "int") ||
> + !ASSERT_EQ(btf__add_struct(btf, "container", 8), 2, "container1") ||
> + !ASSERT_OK(btf__add_field(btf, "first", 3, 0, 0), "first1") ||
> + !ASSERT_OK(btf__add_field(btf, "second", 3, 32, 0), "second1") ||
> + !ASSERT_EQ(btf__add_array(btf, 1, 4, 1), 3, "array1") ||
> + !ASSERT_EQ(btf__add_struct(btf, "elem", 4), 4, "elem1") ||
> + !ASSERT_OK(btf__add_field(btf, "x", 1, 0, 0), "elem1_field") ||
> + !ASSERT_EQ(btf__add_struct(btf, "container", 8), 5, "container2") ||
> + !ASSERT_OK(btf__add_field(btf, "first", 6, 0, 0), "first2") ||
> + !ASSERT_OK(btf__add_field(btf, "second", 7, 32, 0), "second2") ||
> + !ASSERT_EQ(btf__add_array(btf, 1, 8, 1), 6, "array2") ||
> + !ASSERT_EQ(btf__add_array(btf, 1, 9, 1), 7, "array3") ||
> + !ASSERT_EQ(btf__add_struct(btf, "elem", 4), 8, "elem2") ||
> + !ASSERT_OK(btf__add_field(btf, "x", 1, 0, 0), "elem2_field") ||
> + !ASSERT_EQ(btf__add_struct(btf, "elem", 4), 9, "elem3") ||
> + !ASSERT_OK(btf__add_field(btf, identical ? "x" : "y", 1, 0, 0), "elem3_field"))
> + goto out;
> +
> + if (!ASSERT_OK(btf__dedup(btf, NULL), "dedup"))
> + goto out;
> +
> + /* Identical elements leave only the first four expected types. */
> + btf_validate_raw(btf, identical ? 4 : 7, (const char *[]) {
> + "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
> + "[2] STRUCT 'container' size=8 vlen=2\n"
> + "\t'first' type_id=3 bits_offset=0\n"
> + "\t'second' type_id=3 bits_offset=32",
> + "[3] ARRAY '(anon)' type_id=4 index_type_id=1 nr_elems=1",
> + "[4] STRUCT 'elem' size=4 vlen=1\n"
> + "\t'x' type_id=1 bits_offset=0",
> + "[5] STRUCT 'container' size=8 vlen=2\n"
> + "\t'first' type_id=3 bits_offset=0\n"
> + "\t'second' type_id=6 bits_offset=32",
> + "[6] ARRAY '(anon)' type_id=7 index_type_id=1 nr_elems=1",
> + "[7] STRUCT 'elem' size=4 vlen=1\n"
> + "\t'y' type_id=1 bits_offset=0",
> + });
> +
> +out:
> + btf__free(btf);
> +}
> +
> +void test_btf_dedup(void)
> +{
> + if (test__start_subtest("array_different_element_types"))
> + test_array_element_types(false);
> + if (test__start_subtest("array_identical_element_types"))
> + test_array_element_types(true);
> +}
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH bpf 2/2] selftests/bpf: Test array element comparison in BTF dedup
2026-09-07 15:45 ` Alan Maguire
@ 2026-09-07 16:30 ` Mingpei CAO
0 siblings, 0 replies; 7+ messages in thread
From: Mingpei CAO @ 2026-09-07 16:30 UTC (permalink / raw)
To: Alan Maguire; +Cc: bpf, alexei.starovoitov, andrii, eddyz87
Thanks, Alan. I'll move the tests into prog_tests/btf.c and rerun them
against both the original and fixed libbpf for v2.
Alan Maguire <alan.maguire@oracle.com> 于2026年9月7日周一 23:46写道:
>
> On 07/09/2026 14:10, Mingpei CAO wrote:
> > Exercise recursive array comparison with different and identical
> > element definitions. Check that distinct containers remain separate
> > and identical types are deduplicated, including their references.
> >
> > The negative case fails before the fix and passes afterwards.
> > The positive case passes both before and after the fix.
> >
> > Assisted-by: LLM
> > Signed-off-by: Mingpei CAO <caomingpei@gmail.com>
>
> this test would be better placed in prog_tests/btf.c I think where there
> are a number of other BTF dedup tests; something like the following
> is roughly equivalent to what you have:
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/btf.c b/tools/testing/selftests/bpf/prog_tests/btf.c
> index 67b9015cbd98..1c4a46e2a64d 100644
> --- a/tools/testing/selftests/bpf/prog_tests/btf.c
> +++ b/tools/testing/selftests/bpf/prog_tests/btf.c
> @@ -7062,6 +7062,94 @@ static struct btf_dedup_test dedup_tests[] = {
> BTF_STR_SEC("\0int\0long int"),
> },
> },
> +{
> + .descr = "dedup: array element comparison",
> + .input = {
> + .raw_types = {
> + /* signed int */
> + BTF_TYPE_INT_ENC(NAME_NTH(1), BTF_INT_SIGNED, 0, 32, 4),
> /* [1] */
> + /* unsigned int */
> + BTF_TYPE_INT_ENC(NAME_NTH(2), 0, 0, 32, 4),
> /* [2] */
> + /* signed int[1] */
> + BTF_TYPE_ARRAY_ENC(1, 1, 1),
> /* [3] */
> + /* duplicate signed int[1] */
> + BTF_TYPE_ARRAY_ENC(1, 1, 1), /* [4] */
> + /* unsigned int[1] */
> + BTF_TYPE_ARRAY_ENC(2, 2, 1), /* [5] */
> + /* struct s { signed int a[1]; signed int b[1]; } */
> + BTF_STRUCT_ENC(NAME_NTH(3), 2, 8), /* [6] */
> + BTF_MEMBER_ENC(NAME_NTH(4), 3, 0),
> + BTF_MEMBER_ENC(NAME_NTH(5), 3, 32),
> + /* struct s { signed int a[1]; unsigned int b[1]; } */
> + BTF_STRUCT_ENC(NAME_NTH(3), 2, 8), /* [7] */
> + BTF_MEMBER_ENC(NAME_NTH(4), 4, 0),
> + BTF_MEMBER_ENC(NAME_NTH(5), 5, 32),
> + BTF_END_RAW,
> + },
> + BTF_STR_SEC("\0int\0unsigned int\0s\0a\0b"),
> + },
> + .expect = {
> + .raw_types = {
> + BTF_TYPE_INT_ENC(NAME_NTH(1), BTF_INT_SIGNED, 0, 32, 4), /* [1] */
> + BTF_TYPE_INT_ENC(NAME_NTH(2), 0, 0, 32, 4), /* [2] */
> + BTF_TYPE_ARRAY_ENC(1, 1, 1), /* [3] */
> + BTF_TYPE_ARRAY_ENC(2, 2, 1), /* [4] */
> + BTF_STRUCT_ENC(NAME_NTH(3), 2, 8), /* [5] */
> + BTF_MEMBER_ENC(NAME_NTH(4), 3, 0),
> + BTF_MEMBER_ENC(NAME_NTH(5), 3, 32),
> + BTF_STRUCT_ENC(NAME_NTH(3), 2, 8), /* [6] */
> + BTF_MEMBER_ENC(NAME_NTH(4), 3, 0),
> + BTF_MEMBER_ENC(NAME_NTH(5), 4, 32),
> + BTF_END_RAW,
> + },
> + BTF_STR_SEC("\0int\0unsigned int\0s\0a\0b"),
> + },
> +},
> +{
> + .descr = "dedup: identical array element comparison",
> + .input = {
> + .raw_types = {
> + /* int */
> + BTF_TYPE_INT_ENC(NAME_NTH(1), BTF_INT_SIGNED, 0, 32, 4), /* [1] */
> + /* struct container { struct elem first[1]; struct elem second[1]; } */
> + BTF_STRUCT_ENC(NAME_NTH(2), 2, 8), /* [2] */
> + BTF_MEMBER_ENC(NAME_NTH(3), 3, 0),
> + BTF_MEMBER_ENC(NAME_NTH(4), 3, 32),
> + /* struct elem[1] */
> + BTF_TYPE_ARRAY_ENC(4, 1, 1), /* [3] */
> + /* struct elem { int x; } */
> + BTF_STRUCT_ENC(NAME_NTH(5), 1, 4), /* [4] */
> + BTF_MEMBER_ENC(NAME_NTH(6), 1, 0),
> + /* duplicate struct container */
> + BTF_STRUCT_ENC(NAME_NTH(2), 2, 8), /* [5] */
> + BTF_MEMBER_ENC(NAME_NTH(3), 6, 0),
> + BTF_MEMBER_ENC(NAME_NTH(4), 7, 32),
> + /* duplicate struct elem[1] */
> + BTF_TYPE_ARRAY_ENC(8, 1, 1), /* [6] */
> + BTF_TYPE_ARRAY_ENC(9, 1, 1), /* [7] */
> + /* duplicate struct elem */
> + BTF_STRUCT_ENC(NAME_NTH(5), 1, 4), /* [8] */
> + BTF_MEMBER_ENC(NAME_NTH(6), 1, 0),
> + BTF_STRUCT_ENC(NAME_NTH(5), 1, 4), /* [9] */
> + BTF_MEMBER_ENC(NAME_NTH(6), 1, 0),
> + BTF_END_RAW,
> + },
> + BTF_STR_SEC("\0int\0container\0first\0second\0elem\0x"),
> + },
> + .expect = {
> + .raw_types = {
> + BTF_TYPE_INT_ENC(NAME_NTH(1), BTF_INT_SIGNED, 0, 32, 4), /* [1] */
> + BTF_STRUCT_ENC(NAME_NTH(2), 2, 8), /* [2] */
> + BTF_MEMBER_ENC(NAME_NTH(3), 3, 0),
> + BTF_MEMBER_ENC(NAME_NTH(4), 3, 32),
> + BTF_TYPE_ARRAY_ENC(4, 1, 1), /* [3] */
> + BTF_STRUCT_ENC(NAME_NTH(5), 1, 4), /* [4] */
> + BTF_MEMBER_ENC(NAME_NTH(6), 1, 0),
> + BTF_END_RAW,
> + },
> + BTF_STR_SEC("\0int\0container\0first\0second\0elem\0x"),
> + },
> +},
> {
> .descr = "dedup: struct example #1",
> /*
>
> > ---
> > .../selftests/bpf/prog_tests/btf_dedup.c | 68 +++++++++++++++++++
> > 1 file changed, 68 insertions(+)
> > create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_dedup.c
> >
> > diff --git a/tools/testing/selftests/bpf/prog_tests/btf_dedup.c b/tools/testing/selftests/bpf/prog_tests/btf_dedup.c
> > new file mode 100644
> > index 000000000000..3e889c0bed53
> > --- /dev/null
> > +++ b/tools/testing/selftests/bpf/prog_tests/btf_dedup.c
> > @@ -0,0 +1,68 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +#include <test_progs.h>
> > +#include <bpf/btf.h>
> > +#include "btf_helpers.h"
> > +
> > +static void test_array_element_types(bool identical)
> > +{
> > + struct btf *btf;
> > +
> > + btf = btf__new_empty();
> > + if (!ASSERT_OK_PTR(btf, "btf_new"))
> > + return;
> > +
> > + /*
> > + * Comparing the first fields maps array [3] to [6]. The second
> > + * fields reuse [3], requiring an identical-type check of [6]/[7].
> > + * Place container [5] before its elements [8]/[9] so they still
> > + * have distinct IDs when their definitions are compared.
> > + */
> > + if (!ASSERT_EQ(btf__add_int(btf, "int", 4, BTF_INT_SIGNED), 1, "int") ||
> > + !ASSERT_EQ(btf__add_struct(btf, "container", 8), 2, "container1") ||
> > + !ASSERT_OK(btf__add_field(btf, "first", 3, 0, 0), "first1") ||
> > + !ASSERT_OK(btf__add_field(btf, "second", 3, 32, 0), "second1") ||
> > + !ASSERT_EQ(btf__add_array(btf, 1, 4, 1), 3, "array1") ||
> > + !ASSERT_EQ(btf__add_struct(btf, "elem", 4), 4, "elem1") ||
> > + !ASSERT_OK(btf__add_field(btf, "x", 1, 0, 0), "elem1_field") ||
> > + !ASSERT_EQ(btf__add_struct(btf, "container", 8), 5, "container2") ||
> > + !ASSERT_OK(btf__add_field(btf, "first", 6, 0, 0), "first2") ||
> > + !ASSERT_OK(btf__add_field(btf, "second", 7, 32, 0), "second2") ||
> > + !ASSERT_EQ(btf__add_array(btf, 1, 8, 1), 6, "array2") ||
> > + !ASSERT_EQ(btf__add_array(btf, 1, 9, 1), 7, "array3") ||
> > + !ASSERT_EQ(btf__add_struct(btf, "elem", 4), 8, "elem2") ||
> > + !ASSERT_OK(btf__add_field(btf, "x", 1, 0, 0), "elem2_field") ||
> > + !ASSERT_EQ(btf__add_struct(btf, "elem", 4), 9, "elem3") ||
> > + !ASSERT_OK(btf__add_field(btf, identical ? "x" : "y", 1, 0, 0), "elem3_field"))
> > + goto out;
> > +
> > + if (!ASSERT_OK(btf__dedup(btf, NULL), "dedup"))
> > + goto out;
> > +
> > + /* Identical elements leave only the first four expected types. */
> > + btf_validate_raw(btf, identical ? 4 : 7, (const char *[]) {
> > + "[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED",
> > + "[2] STRUCT 'container' size=8 vlen=2\n"
> > + "\t'first' type_id=3 bits_offset=0\n"
> > + "\t'second' type_id=3 bits_offset=32",
> > + "[3] ARRAY '(anon)' type_id=4 index_type_id=1 nr_elems=1",
> > + "[4] STRUCT 'elem' size=4 vlen=1\n"
> > + "\t'x' type_id=1 bits_offset=0",
> > + "[5] STRUCT 'container' size=8 vlen=2\n"
> > + "\t'first' type_id=3 bits_offset=0\n"
> > + "\t'second' type_id=6 bits_offset=32",
> > + "[6] ARRAY '(anon)' type_id=7 index_type_id=1 nr_elems=1",
> > + "[7] STRUCT 'elem' size=4 vlen=1\n"
> > + "\t'y' type_id=1 bits_offset=0",
> > + });
> > +
> > +out:
> > + btf__free(btf);
> > +}
> > +
> > +void test_btf_dedup(void)
> > +{
> > + if (test__start_subtest("array_different_element_types"))
> > + test_array_element_types(false);
> > + if (test__start_subtest("array_identical_element_types"))
> > + test_array_element_types(true);
> > +}
>
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-09-07 16:30 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-06 10:23 [BUG] libbpf: BTF dedup merges arrays with different element types Mingpei CAO
2026-09-06 21:27 ` Alexei Starovoitov
2026-09-07 13:10 ` [PATCH bpf 0/2] libbpf: Fix array comparison in BTF dedup Mingpei CAO
2026-09-07 13:10 ` [PATCH bpf 1/2] " Mingpei CAO
2026-09-07 13:10 ` [PATCH bpf 2/2] selftests/bpf: Test array element " Mingpei CAO
2026-09-07 15:45 ` Alan Maguire
2026-09-07 16:30 ` Mingpei CAO
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox