[PATCH bpf-next v1 3/4] selftests/bpf: Fix resolve_btfids test reads of BTF ID sets in PIE builds
Ihor Solodrai <[email protected]>
| Newsgroups | org.kernel.vger.linux-kbuild,org.kernel.vger.bpf |
|---|---|
| Message-ID | <[email protected]> |
TL;DR
On aarch64 with gcc toolchain, when test_progs is linked as a PIE,
reads of BTF ID array by C name return garbage because the GNU
assembler on aarch64 unconditionally folds .local symbol references
into section+addend form, and GOT slots cannot carry an addend per the
AArch64 ELF spec.
Fix by marking the test's BTF ID objects with hidden visibility, which
makes gcc emit a direct access that bypasses the GOT entirely.
Details below.
The subsequent patches adding kfunc checks to resolve_btfids test may
cause test failures on aarch64 / gcc-15:
test_resolve_btfids:FAIL:kfunc_set_flags actual 13 != expected 1
test_resolve_btfids:FAIL:kfunc_set_cnt actual 0 != expected 4
The test defines its BTF ID sets with the same macros as the kernel
and reads them back directly by C name (in the same way as the kernel
code does).
test_kfunc_set is a .local symbol emitted into .BTF_ids by inline asm
and declared to the compiler as a plain default-visibility
extern, that is:
extern struct btf_id_set8 test_kfunc_set;
Depending on the build environment, test_progs may be linked as a
position-independent executable (for example, gcc defaults to -fpie
[1]). In a PIE, taking the address of a default-visibility extern is
routed through the GOT (Global Offset Table) [2].
The GNU assembler's adjust_reloc_syms() pass (gas/write.c [3])
replaces references to local symbols with the corresponding section
symbol, folding the symbol's offset into the relocation addend. On
aarch64 this conversion is unconditional: tc_fix_adjustable() is
defined to 1 for all fixups (gas/config/tc-aarch64.h [4]), so even
GOT-generating relocations are subject to it. The resulting object
file therefore contains:
R_AARCH64_ADR_GOT_PAGE .BTF_ids + 0x54
R_AARCH64_LD64_GOT_LO12_NC .BTF_ids + 0x54
However, the AArch64 ELF specification mandates that GOT-generating
relocations must have a zero addend [5]. The +0x54 is therefore not
honored: the linker creates a GOT slot pointing at the .BTF_ids base,
and every access through that slot reads offset 0 instead of 0x54.
This is purely a read-side problem, specific to the PIE test binary on
aarch64 with gcc toolchain. resolve_btfids patches the set header
correctly and the .BTF_ids bytes in test_progs are correct. vmlinux is
unaffected because it is built with -fno-PIE [6] and reaches .BTF_ids
with direct, addend-preserving relocations rather than the GOT. clang
is unaffected because LLVM's assembler retains the original symbol for
GOT relocations instead of converting to section+addend [7].
To mitigate this issue, mark the test's .local BTF ID objects
(test_list_local and test_set) hidden with a visibility pragma so that
gcc treats them as non-interposable and emits a direct access instead
of a GOT load. test_list_global is .globl, which the assembler does not
fold into section+addend, so it is left at default visibility. This
keeps the natural by-name access, works in both PIE and non-PIE builds,
and needs no change to the BTF_ID macros or resolve_btfids.
[1] https://gcc.gnu.org/onlinedocs/gnat_ugn/Position-Independent-Executable-PIE-Enabled-by-Default-on-Linux.html
[2] https://gcc.gnu.org/wiki/Visibility
[3] https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gas/write.c#l922
[4] https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gas/config/tc-aarch64.h#l279
[5] https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst#5733relocation-operations
[6] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Makefile?h=v7.1-rc6#n593
[7] https://github.com/llvm/llvm-project/blob/4b3bc46d1d794b8ed78b75ccd35a6cc30235bf31/llvm/lib/MC/ELFObjectWriter.cpp#L1213-L1224
Acked-by: Andrii Nakryiko <[email protected]>
Reviewed-by: Emil Tsalapatis <[email protected]>
Signed-off-by: Ihor Solodrai <[email protected]>
---
.../selftests/bpf/prog_tests/resolve_btfids.c | 29 +++++++++++++++----
1 file changed, 24 insertions(+), 5 deletions(-)
diff --git a/tools/testing/selftests/bpf/prog_tests/resolve_btfids.c b/tools/testing/selftests/bpf/prog_tests/resolve_btfids.c
index e549780697c7..6bcadee50bb8 100644
--- a/tools/testing/selftests/bpf/prog_tests/resolve_btfids.c
+++ b/tools/testing/selftests/bpf/prog_tests/resolve_btfids.c
@@ -34,6 +34,24 @@ asm (
".balign 4, 0; \n"
".popsection; \n");
+/*
+ * test_list_local and test_set are .local symbols placed in .BTF_ids by
+ * inline asm, and are read here directly by C name. To the compiler they
+ * are plain, default-visibility extern objects.
+ *
+ * When test_progs is linked as a position-independent executable (PIE),
+ * taking the address of such an extern is routed through the GOT. The
+ * GNU assembler on aarch64 unconditionally converts references to .local
+ * symbols into section + addend form (".BTF_ids + <offset>"), but a GOT
+ * slot cannot carry an addend (the AArch64 ELF spec mandates zero), so
+ * the linker resolves it to the .BTF_ids base.
+ *
+ * Mark them hidden so the compiler treats them as non-interposable and
+ * emits a direct, addend-preserving PC-relative access instead of a GOT
+ * load, in both PIE and non-PIE builds. test_list_global is .globl and
+ * not affected, so it is left at default visibility.
+ */
+#pragma GCC visibility push(hidden)
BTF_ID_LIST(test_list_local)
BTF_ID_UNUSED
BTF_ID(typedef, S)
@@ -43,24 +61,25 @@ BTF_ID(struct, S)
BTF_ID(union, U)
BTF_ID(func, func)
-extern __u32 test_list_global[];
-BTF_ID_LIST_GLOBAL(test_list_global, 1)
-BTF_ID_UNUSED
+BTF_SET_START(test_set)
BTF_ID(typedef, S)
BTF_ID(typedef, T)
BTF_ID(typedef, U)
BTF_ID(struct, S)
BTF_ID(union, U)
BTF_ID(func, func)
+BTF_SET_END(test_set)
+#pragma GCC visibility pop
-BTF_SET_START(test_set)
+extern __u32 test_list_global[];
+BTF_ID_LIST_GLOBAL(test_list_global, 1)
+BTF_ID_UNUSED
BTF_ID(typedef, S)
BTF_ID(typedef, T)
BTF_ID(typedef, U)
BTF_ID(struct, S)
BTF_ID(union, U)
BTF_ID(func, func)
-BTF_SET_END(test_set)
static int
__resolve_symbol(struct btf *btf, int type_id)
--
2.54.0