Skip to content

Commit e30baa6

Browse files
Keep the artifact loadable on musl when built with a current toolchain
Motivation: Built on a current distro (Debian 13 here) instead of the CentOS release images, the artifact imports glibc-only symbols. It still loads on Alpine under the JVM, which binds lazily, but `ldd`, LD_BIND_NOW and docker/musl-verify fail. Building with an empty musl_compat.c and asking Alpine's loader what is missing gives six symbols: - __isoc23_strtol (APR), __isoc23_strtoul (static libstdc++), __isoc23_strtoull (BoringSSL): glibc 2.38 redirects strtol and friends there under _GNU_SOURCE, and no -D switches it off. - __libc_single_threaded: static libstdc++, glibc 2.32+. - _dl_find_object: libgcc_eh.a from gcc 12, glibc 2.35+. - arc4random: libstdc++'s std::random_device, glibc 2.36+, dragged in by the FIPS profile's newer BoringSSL. Nothing calls it. Modifications: - FIPS link line: -Wl,--gc-sections. The linker drops the unreachable random_device code and the arc4random import with it; the library gets 117 KB smaller and the FIPS integrity check still passes. - Weak fallbacks in musl_compat.c for the other five, which sit in live code. The strto* ones reach the plain symbols through asm labels, since a literal strtol() there would be redirected too and recurse on musl. `__restrict`, not `restrict`: Debian 7's GCC 4.9 is gnu90. - scripts/check_musl_compat.sh asserts the fallbacks are defined, and knows the __isoc99_* scanf names musl does export. Result: Artifacts built on Debian 13 resolve completely on Alpine. No-op on the CentOS release images, whose glibc predates all of these.
1 parent 10883d9 commit e30baa6

4 files changed

Lines changed: 64 additions & 2 deletions

File tree

‎boringssl-static/pom.xml‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,10 @@
443443
<configureArg>--libdir=${project.build.directory}/native-build/target/lib</configureArg>
444444
<configureArg>CFLAGS=-O3 -Werror -fno-omit-frame-pointer -fvisibility=hidden -Wunused -Wno-unused-value</configureArg>
445445
<configureArg>CPPFLAGS=-DHAVE_OPENSSL -I${boringsslCheckoutDir}/include</configureArg>
446-
<configureArg>LDFLAGS=-L${boringsslBuildDir} -lssl -lcrypto -ldecrepit -l:libstdc++.a -l:libgcc.a -l:libgcc_eh.a</configureArg>
446+
<!-- gc-sections: this BoringSSL drags in libstdc++'s std::random_device, which nothing
447+
calls and which imports arc4random (glibc 2.36+, absent from musl). Dropping
448+
the dead code removes the import. See docs/musl-compatibility.md -->
449+
<configureArg>LDFLAGS=-L${boringsslBuildDir} -Wl,--gc-sections -lssl -lcrypto -ldecrepit -l:libstdc++.a -l:libgcc.a -l:libgcc_eh.a</configureArg>
447450
</configureArgs>
448451
</configuration>
449452
</execution>

‎docs/musl-compatibility.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ Error relocating <lib>.so: __getauxval: symbol not found
102102
| `__isinf`, `__isnan` | APR-era glibc math aliases | no |
103103
| `__strdup` | APR | no |
104104
| `__pthread_key_create` | APR | no — but it is imported **WEAK**, so it may stay unresolved harmlessly |
105+
| `__libc_single_threaded` | libstdc++ 11+ headers on glibc 2.32+ read this byte to skip atomic refcounting; C++ in BoringSSL imports it as a **data** symbol | no. Defined as `0`, the conservative value |
106+
| `__isoc23_strtol`, `__isoc23_strtoul`, `__isoc23_strtoull` | glibc 2.38+ redirects `strtol` and friends there under `_GNU_SOURCE`, and no `-D` switches it off; from APR, the static `libstdc++.a` and BoringSSL's libcrypto respectively | no. Forwarded to the plain names via asm labels (a literal `strtol()` in the fallback would be redirected too and recurse on musl) |
107+
| `_dl_find_object` | `libgcc_eh.a` from gcc 12 on, when built against glibc 2.35+, uses it to find `.eh_frame` while unwinding | no. Stub returns -1 ("not found"); nothing in the artifact throws |
105108
106109
### Class C — Class B inside an ELF init constructor → **JVM crash, not an exception**
107110
@@ -438,6 +441,9 @@ Other notes:
438441
*before* hawtjni. Use the `native-jar` target (phase `package`) or `process-classes`.
439442
- Ant's `<exec>` does not echo silent commands, so absence of `strip`/`patchelf` output in the
440443
log does **not** mean they did not run. Verify on the artifact instead.
444+
- The FIPS profile links with `-Wl,--gc-sections`. Its newer BoringSSL drags in libstdc++'s
445+
`std::random_device`, which nothing calls and which imports `arc4random` (glibc 2.36+, absent
446+
from musl). Dropping the dead code removes the import, so no fallback is needed for it.
441447
- Link flags are set per profile and are duplicated: the x86_64 default profile sets
442448
`hawtjniLdflags` in the `ldflags-setup` antrun execution, while the FIPS and `linux-aarch64`
443449
profiles hardcode `LDFLAGS` in their hawtjni `configureArgs`. Changing one does not change the

‎openssl-dynamic/src/main/c/musl_compat.c‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,55 @@ TCN_MUSL_COMPAT char *__strdup(const char *str) {
147147
return strdup(str);
148148
}
149149

150+
/*
151+
* glibc 2.38 made strtol and friends C23-conformant under a new symbol version, and redirects
152+
* every call to an __isoc23_* name whenever _GNU_SOURCE is defined, whatever -std says; no -D
153+
* switches it off. Built on such a glibc, APR imports __isoc23_strtol, the static libstdc++
154+
* __isoc23_strtoul and BoringSSL's libcrypto __isoc23_strtoull. musl exports only the plain
155+
* names.
156+
*
157+
* The bodies must call the PLAIN symbols. A literal strtol() here is subject to the same
158+
* redirect, so on musl it would resolve to this very function and recurse. The asm labels
159+
* bind each reference to the unversioned name, which both libcs export.
160+
*
161+
* __restrict, not restrict: the Debian 7 image compiles with GCC 4.9, whose default is gnu90,
162+
* where `restrict` is not a keyword.
163+
*/
164+
extern long tcn_plain_strtol(const char *, char **, int) __asm__("strtol");
165+
extern unsigned long tcn_plain_strtoul(const char *, char **, int) __asm__("strtoul");
166+
extern unsigned long long tcn_plain_strtoull(const char *, char **, int) __asm__("strtoull");
167+
168+
TCN_MUSL_COMPAT long __isoc23_strtol(const char *__restrict nptr, char **__restrict endptr, int base) {
169+
return tcn_plain_strtol(nptr, endptr, base);
170+
}
171+
172+
TCN_MUSL_COMPAT unsigned long __isoc23_strtoul(const char *__restrict nptr, char **__restrict endptr, int base) {
173+
return tcn_plain_strtoul(nptr, endptr, base);
174+
}
175+
176+
TCN_MUSL_COMPAT unsigned long long __isoc23_strtoull(const char *__restrict nptr, char **__restrict endptr, int base) {
177+
return tcn_plain_strtoull(nptr, endptr, base);
178+
}
179+
180+
/*
181+
* glibc 2.32+ exports this byte, and libstdc++ reads it (ext/atomicity.h) to skip atomic
182+
* reference counting while a process is still single-threaded. The static libstdc++ imports
183+
* it as a plain data symbol; musl has no such thing. Zero is the conservative value: "not
184+
* single-threaded", so the atomic path is always taken, which is also the truth inside a JVM.
185+
*/
186+
TCN_MUSL_COMPAT char __libc_single_threaded = 0;
187+
188+
/*
189+
* glibc 2.35 added _dl_find_object, and libgcc_eh.a from gcc 12 on calls it to locate a
190+
* frame's .eh_frame when unwinding. musl has no equivalent. Returning -1 means "no object
191+
* found": a C++ exception would then terminate instead of propagating, and nothing in this
192+
* library lets one escape. Declared with void * on purpose: the real struct only exists in
193+
* glibc >= 2.35 headers and the release image is glibc 2.12.
194+
*/
195+
TCN_MUSL_COMPAT int _dl_find_object(void *address, void *result) {
196+
(void) address;
197+
(void) result;
198+
return -1;
199+
}
200+
150201
#endif /* __linux__ */

‎scripts/check_musl_compat.sh‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,13 @@ __ctype_b_loc __ctype_get_mb_cur_max __ctype_tolower_loc __ctype_toupper_loc
9797
__errno_location __fpclassify __fpclassifyf __fpclassifyl
9898
__h_errno_location __libc_start_main __signbit __signbitf __signbitl
9999
__stack_chk_fail __stack_chk_guard __tls_get_addr
100+
__isoc99_fscanf __isoc99_fwscanf __isoc99_scanf __isoc99_sscanf __isoc99_swscanf __isoc99_vfscanf
101+
__isoc99_vfwscanf __isoc99_vscanf __isoc99_vsscanf __isoc99_vswscanf __isoc99_vwscanf __isoc99_wscanf
100102
'
101103

102104
# The fallbacks openssl-dynamic/src/main/c/musl_compat.c defines. Asserting these are *defined*
103105
# rather than undefined catches the compatibility file being dropped or excluded from the link.
104-
MUSL_COMPAT_SYMS='__getauxval fopen64 __isinf __isnan __strdup'
106+
MUSL_COMPAT_SYMS='__getauxval fopen64 __isinf __isnan __strdup __libc_single_threaded __isoc23_strtol __isoc23_strtoul __isoc23_strtoull _dl_find_object'
105107

106108
rc=0
107109
MACHINE=$("$READELF" -h "$SO" 2>/dev/null | sed -n 's/.*Machine:[[:space:]]*//p')

0 commit comments

Comments
 (0)