Skip to content

Commit 8000517

Browse files
Keep the artifact loadable on musl when built with a current toolchain
Motivation: The release images are CentOS 6/7, but the FIPS profile is built downstream on whatever distro people have, and so is the new CI leg. On Ubuntu 22.04/24.04 and Debian 13 the artifact acquires glibc-only imports from code this project does not compile with its own flags: - __*_chk: Debian-derived gcc defines _FORTIFY_SOURCE=2 by default; APR, BoringSSL and the packaged libstdc++.a/libgcc.a all import them. - __libc_single_threaded: libstdc++ 11+ headers on glibc 2.32+. - __isoc23_strto*/__isoc23_*scanf: glibc 2.38 redirects the integer parsers and scanf under _GNU_SOURCE, whatever -std says. - _dl_find_object: libgcc_eh.a from gcc 12, used while unwinding. The library still loads and initialises on Alpine (nothing on the load path calls them), but `ldd` fails and so does docker/musl-verify. Modifications: - -U_FORTIFY_SOURCE on every Linux compile line, including APR's and the BoringSSL cmake flags. No-op on the RHEL images, which never fortify by default, so the released artifacts do not change. - Weak, default-visibility fallbacks in musl_compat.c for the rest, following the existing pattern. The strto*/scanf ones call the plain symbols through asm labels: a literal strtol() in the fallback is redirected too and would recurse on musl. `__restrict`, not `restrict`: Debian 7's GCC 4.9 is gnu90. _dl_find_object returns "not found"; nothing here throws (BoringSSL is -fno-exceptions, the rest is C). - scripts/check_musl_compat.sh asserts the fallbacks are defined. Result: Artifacts built on a modern distro pass the Alpine runtime check, and their glibc floor drops (2.38 -> 2.34 on Debian 13).
1 parent a948ab1 commit 8000517

5 files changed

Lines changed: 153 additions & 12 deletions

File tree

‎boringssl-static/pom.xml‎

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,14 @@
9090
<msvcSslIncludeDirs>${boringsslSourceDir}/include</msvcSslIncludeDirs>
9191
<msvcSslLibDirs>${boringsslHome}</msvcSslLibDirs>
9292
<msvcSslLibs>ssl.lib;crypto.lib</msvcSslLibs>
93-
<cflags>-Werror -fno-omit-frame-pointer -fvisibility=hidden -Wunused -Wno-unused-value -O3</cflags>
93+
<!-- -U_FORTIFY_SOURCE: Debian-derived gcc (Ubuntu in particular) defines _FORTIFY_SOURCE=2 by
94+
default, which rewrites memcpy/sprintf/fprintf/... into glibc's __*_chk variants. musl does
95+
not export those, so an artifact built on such a host has undefined __*_chk imports that
96+
make `ldd` fail on Alpine (observed: __fprintf_chk, __memcpy_chk, __sprintf_chk from this
97+
module, plus __fgets_chk/__vsnprintf_chk from BoringSSL). It is a no-op on the RHEL-family
98+
release images and on macOS. Same flag in the FIPS and linux-aarch64 profiles and in the
99+
BoringSSL cmake flags below. See docs/musl-compatibility.md -->
100+
<cflags>-Werror -fno-omit-frame-pointer -fvisibility=hidden -Wunused -Wno-unused-value -O3 -U_FORTIFY_SOURCE</cflags>
94101
<cppflags>-DHAVE_OPENSSL -I${boringsslSourceDir}/include</cppflags>
95102
<ldflags>-L${boringsslHome} -lssl -lcrypto</ldflags>
96103
<skipJapicmp>true</skipJapicmp>
@@ -251,9 +258,9 @@
251258
<then>
252259
<!-- On *nix, add ASM flags to disable executable stack -->
253260
<property name="cmakeAsmFlags" value="-Wa,--noexecstack" />
254-
<property name="cmakeCFlags" value="-w -std=c99 -O3 -fno-omit-frame-pointer" />
261+
<property name="cmakeCFlags" value="-w -std=c99 -O3 -fno-omit-frame-pointer -U_FORTIFY_SOURCE" />
255262
<!-- We need to define __STDC_CONSTANT_MACROS and __STDC_FORMAT_MACROS when building boringssl on centos 6 -->
256-
<property name="cmakeCxxFlags" value="-w -O3 -fno-omit-frame-pointer -Wno-error=maybe-uninitialized -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS" />
263+
<property name="cmakeCxxFlags" value="-w -O3 -fno-omit-frame-pointer -Wno-error=maybe-uninitialized -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -U_FORTIFY_SOURCE" />
257264
</then>
258265
</elseif>
259266
<else>
@@ -453,7 +460,7 @@
453460
<configureArg>--with-apr=${aprHome}</configureArg>
454461
<configureArg>--with-static-libs</configureArg>
455462
<configureArg>--libdir=${project.build.directory}/native-build/target/lib</configureArg>
456-
<configureArg>CFLAGS=-O3 -Werror -fno-omit-frame-pointer -fvisibility=hidden -Wunused -Wno-unused-value</configureArg>
463+
<configureArg>CFLAGS=-O3 -Werror -fno-omit-frame-pointer -fvisibility=hidden -Wunused -Wno-unused-value -U_FORTIFY_SOURCE</configureArg>
457464
<configureArg>CPPFLAGS=-DHAVE_OPENSSL -I${boringsslCheckoutDir}/include</configureArg>
458465
<configureArg>LDFLAGS=-L${boringsslBuildDir} -lssl -lcrypto -ldecrepit -l:libstdc++.a -l:libgcc.a -l:libgcc_eh.a</configureArg>
459466
</configureArgs>
@@ -590,9 +597,9 @@
590597
<then>
591598
<!-- On *nix, add ASM flags to disable executable stack -->
592599
<property name="cmakeAsmFlags" value="-Wa,--noexecstack" />
593-
<property name="cmakeCFlags" value="-w -O3 -fno-omit-frame-pointer -Wno-error=stringop-overflow" />
600+
<property name="cmakeCFlags" value="-w -O3 -fno-omit-frame-pointer -Wno-error=stringop-overflow -U_FORTIFY_SOURCE" />
594601
<!-- We need to define __STDC_CONSTANT_MACROS and __STDC_FORMAT_MACROS when building boringssl on centos 6 -->
595-
<property name="cmakeCxxFlags" value="-w -O3 -fno-omit-frame-pointer -Wno-error=maybe-uninitialized -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS" />
602+
<property name="cmakeCxxFlags" value="-w -O3 -fno-omit-frame-pointer -Wno-error=maybe-uninitialized -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -U_FORTIFY_SOURCE" />
596603
</then>
597604
</elseif>
598605
<else>
@@ -990,9 +997,9 @@
990997
<then>
991998
<!-- On *nix, add ASM flags to disable executable stack -->
992999
<property name="cmakeAsmFlags" value="-Wa,--noexecstack" />
993-
<property name="cmakeCFlags" value="-O3 -fno-omit-frame-pointer" />
1000+
<property name="cmakeCFlags" value="-O3 -fno-omit-frame-pointer -U_FORTIFY_SOURCE" />
9941001
<!-- We need to define __STDC_CONSTANT_MACROS and __STDC_FORMAT_MACROS when building boringssl on centos 6 -->
995-
<property name="cmakeCxxFlags" value="-O3 -fno-omit-frame-pointer -Wno-error=maybe-uninitialized -Wno-error=shadow -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS" />
1002+
<property name="cmakeCxxFlags" value="-O3 -fno-omit-frame-pointer -Wno-error=maybe-uninitialized -Wno-error=shadow -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -U_FORTIFY_SOURCE" />
9961003
</then>
9971004
</elseif>
9981005
<else>
@@ -1162,7 +1169,7 @@
11621169
<configureArg>--with-apr=${aprHome}</configureArg>
11631170
<configureArg>--with-static-libs</configureArg>
11641171
<configureArg>--libdir=${project.build.directory}/native-build/target/lib</configureArg>
1165-
<configureArg>CFLAGS=-O3 -Werror -fno-omit-frame-pointer -fvisibility=hidden -Wunused -Wno-unused-value</configureArg>
1172+
<configureArg>CFLAGS=-O3 -Werror -fno-omit-frame-pointer -fvisibility=hidden -Wunused -Wno-unused-value -U_FORTIFY_SOURCE</configureArg>
11661173
<configureArg>CPPFLAGS=-DHAVE_OPENSSL -I${boringsslSourceDir}/include</configureArg>
11671174
<!-- Keep in sync with the boringssl-static-default profile: the two Linux
11681175
profiles duplicate the whole native build, so a change to one does not apply

‎docs/musl-compatibility.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ 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+
| `__sprintf_chk`, `__fprintf_chk` | Debian-derived toolchains ship a fortified `libstdc++.a` / `libgcc.a`; `cp-demangle.o` comes in via the verbose terminate handler | no. Our own code is compiled `-U_FORTIFY_SOURCE`, these two cover the archives we do not build |
106+
| `__libc_single_threaded` | libstdc++ 11+ headers on glibc 2.32+ read this byte to skip atomic refcounting; C++ in BoringSSL (seen with the `fips-20260721` branch on Ubuntu 22.04) imports it as a **data** symbol | no. Defined as `0`, the conservative value |
107+
| `__isoc23_strto{l,ul,ll,ull,imax,umax}`, `__isoc23_{s,vs}scanf` | glibc 2.38+ redirects the integer parsers and scanf family there under `_GNU_SOURCE`; APR and the packaged `libstdc++.a` on any glibc 2.38+ builder such as Debian 13 (`strtoull` only on x86_64) | no. Forwarded to the plain names via asm labels (a literal `strtol()` in the fallback would be redirected too and recurse on musl) |
108+
| `_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 |
105109
106110
### Class C — Class B inside an ELF init constructor → **JVM crash, not an exception**
107111
@@ -438,6 +442,11 @@ Other notes:
438442
*before* hawtjni. Use the `native-jar` target (phase `package`) or `process-classes`.
439443
- Ant's `<exec>` does not echo silent commands, so absence of `strip`/`patchelf` output in the
440444
log does **not** mean they did not run. Verify on the artifact instead.
445+
- `-U_FORTIFY_SOURCE` is on every Linux compile line (module `cflags`, the FIPS and
446+
`linux-aarch64` `CFLAGS`, and the BoringSSL cmake flags of all three). Debian-derived gcc
447+
defines `_FORTIFY_SOURCE=2` by default and rewrites libc calls into `__*_chk` variants musl
448+
does not export: the artifact still loads (nothing on the load path calls them) but `ldd`
449+
on Alpine fails; the modern-distro CI legs showed exactly that. No-op on the RHEL release images.
441450
- Link flags are set per profile and are duplicated: the x86_64 default profile sets
442451
`hawtjniLdflags` in the `ldflags-setup` antrun execution, while the FIPS and `linux-aarch64`
443452
profiles hardcode `LDFLAGS` in their hawtjni `configureArgs`. Changing one does not change the

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

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,11 @@
5151
#endif
5252

5353
#include <fcntl.h>
54+
#include <inttypes.h>
5455
#include <math.h>
56+
#include <stdarg.h>
5557
#include <stdio.h>
58+
#include <stdlib.h>
5659
#include <string.h>
5760
#include <unistd.h>
5861

@@ -147,4 +150,126 @@ TCN_MUSL_COMPAT char *__strdup(const char *str) {
147150
return strdup(str);
148151
}
149152

153+
/*
154+
* _FORTIFY_SOURCE entry points. Everything this project compiles passes -U_FORTIFY_SOURCE, so
155+
* none of our own objects reference these. Two archives we link but do not build can: on a
156+
* Debian-derived toolchain the packaged libstdc++.a (cp-demangle.o, floating_to_chars.o,
157+
* debug.o) and libgcc.a (_eprintf.o) are themselves fortified, and cp-demangle.o is pulled in
158+
* through the verbose terminate handler. The result is an undefined __sprintf_chk that makes
159+
* `ldd` fail on Alpine even though nothing on the load path calls it. Same shape as the rest
160+
* of this file: weak so glibc's own definition wins on glibc, defined here so musl has one.
161+
*
162+
* Semantics follow glibc: slen is the destination size, or (size_t) -1 when unknown.
163+
*/
164+
TCN_MUSL_COMPAT int __sprintf_chk(char *s, int flag, size_t slen, const char *format, ...) {
165+
va_list ap;
166+
int written;
167+
(void) flag;
168+
va_start(ap, format);
169+
if (slen == (size_t) -1) {
170+
written = vsprintf(s, format, ap);
171+
} else {
172+
written = vsnprintf(s, slen, format, ap);
173+
}
174+
va_end(ap);
175+
return written;
176+
}
177+
178+
TCN_MUSL_COMPAT int __fprintf_chk(FILE *stream, int flag, const char *format, ...) {
179+
va_list ap;
180+
int written;
181+
(void) flag;
182+
va_start(ap, format);
183+
written = vfprintf(stream, format, ap);
184+
va_end(ap);
185+
return written;
186+
}
187+
188+
/*
189+
* glibc 2.32+ exports this byte, and libstdc++ 11+ headers read it (ext/atomicity.h,
190+
* __gnu_cxx::__is_single_threaded) to skip atomic reference counting while a process is still
191+
* single-threaded. Any C++ compiled against such headers -- BoringSSL's libssl on a current
192+
* toolchain -- imports it as a plain data symbol, and musl has no such thing. Zero is the
193+
* conservative value: "not single-threaded", so the atomic path is always taken. On glibc the
194+
* libc definition wins as usual. The JVM is multi-threaded long before this library loads, so
195+
* the value could never legitimately be nonzero here anyway.
196+
*/
197+
TCN_MUSL_COMPAT char __libc_single_threaded = 0;
198+
199+
/*
200+
* glibc 2.38 made the integer parsers and the scanf family C23-conformant (binary "0b"
201+
* prefixes) under a new symbol version, and redirects every call to an __isoc23_* name
202+
* whenever _GNU_SOURCE is defined, whatever -std says. Anything built on glibc >= 2.38
203+
* (Ubuntu 24.04, Debian 13) imports them: APR (sockaddr.o, apr_strings.o), the packaged libstdc++.a
204+
* (eh_alloc.o, debug.o), and on x86_64 something else again brings in strtoull. musl exports
205+
* only the plain names. The whole family is covered here so the next builder does not find
206+
* the next member.
207+
*
208+
* The bodies must call the PLAIN symbols. A literal strtol() here is subject to the same
209+
* redirect, so on musl it would resolve to this very function and recurse. The asm labels
210+
* bind each reference to the unversioned name, which both libcs export.
211+
*
212+
* __restrict, not restrict: the Debian 7 image compiles with GCC 4.9, whose default is gnu90,
213+
* where `restrict` is not a keyword.
214+
*/
215+
extern long tcn_plain_strtol(const char *, char **, int) __asm__("strtol");
216+
extern unsigned long tcn_plain_strtoul(const char *, char **, int) __asm__("strtoul");
217+
extern long long tcn_plain_strtoll(const char *, char **, int) __asm__("strtoll");
218+
extern unsigned long long tcn_plain_strtoull(const char *, char **, int) __asm__("strtoull");
219+
extern intmax_t tcn_plain_strtoimax(const char *, char **, int) __asm__("strtoimax");
220+
extern uintmax_t tcn_plain_strtoumax(const char *, char **, int) __asm__("strtoumax");
221+
extern int tcn_plain_vsscanf(const char *, const char *, va_list) __asm__("vsscanf");
222+
223+
TCN_MUSL_COMPAT long __isoc23_strtol(const char *__restrict nptr, char **__restrict endptr, int base) {
224+
return tcn_plain_strtol(nptr, endptr, base);
225+
}
226+
227+
TCN_MUSL_COMPAT unsigned long __isoc23_strtoul(const char *__restrict nptr, char **__restrict endptr, int base) {
228+
return tcn_plain_strtoul(nptr, endptr, base);
229+
}
230+
231+
TCN_MUSL_COMPAT long long __isoc23_strtoll(const char *__restrict nptr, char **__restrict endptr, int base) {
232+
return tcn_plain_strtoll(nptr, endptr, base);
233+
}
234+
235+
TCN_MUSL_COMPAT unsigned long long __isoc23_strtoull(const char *__restrict nptr, char **__restrict endptr, int base) {
236+
return tcn_plain_strtoull(nptr, endptr, base);
237+
}
238+
239+
TCN_MUSL_COMPAT intmax_t __isoc23_strtoimax(const char *__restrict nptr, char **__restrict endptr, int base) {
240+
return tcn_plain_strtoimax(nptr, endptr, base);
241+
}
242+
243+
TCN_MUSL_COMPAT uintmax_t __isoc23_strtoumax(const char *__restrict nptr, char **__restrict endptr, int base) {
244+
return tcn_plain_strtoumax(nptr, endptr, base);
245+
}
246+
247+
TCN_MUSL_COMPAT int __isoc23_vsscanf(const char *__restrict str, const char *__restrict format, va_list ap) {
248+
return tcn_plain_vsscanf(str, format, ap);
249+
}
250+
251+
TCN_MUSL_COMPAT int __isoc23_sscanf(const char *__restrict str, const char *__restrict format, ...) {
252+
va_list ap;
253+
int matched;
254+
va_start(ap, format);
255+
matched = tcn_plain_vsscanf(str, format, ap);
256+
va_end(ap);
257+
return matched;
258+
}
259+
260+
/*
261+
* glibc 2.35 added _dl_find_object, and libgcc_eh.a from gcc 12 on (unwind-dw2-fde-dip.o)
262+
* calls it to locate a frame's .eh_frame when unwinding. musl has no equivalent. Returning
263+
* -1 means "no object found": the unwinder then reports no FDE and a C++ exception would
264+
* terminate instead of propagating. Nothing here throws - BoringSSL compiles its C++ with
265+
* -fno-exceptions, APR and this module are C - so the only observable effect is that the
266+
* symbol resolves. Declared with a void * result on purpose: the real struct only exists in
267+
* glibc >= 2.35 headers and the release image is glibc 2.12.
268+
*/
269+
TCN_MUSL_COMPAT int _dl_find_object(void *address, void *result) {
270+
(void) address;
271+
(void) result;
272+
return -1;
273+
}
274+
150275
#endif /* __linux__ */

‎pom.xml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -766,7 +766,7 @@
766766
Also ensure that we can use locks as detection fails when cross-compiling.
767767
See https://github.com/netty/netty-tcnative/issues/974
768768
-->
769-
<arg line="--disable-shared --prefix=${aprHome} --host=aarch64-linux-gnu CC=aarch64-none-linux-gnu-gcc CFLAGS='-O3 -fno-omit-frame-pointer -fPIC -DHAVE_PTHREAD_RWLOCKS=1' ac_cv_have_decl_sys_siglist=no ac_cv_file__dev_zero=yes ac_cv_func_setpgrp_void=yes apr_cv_tcp_nodelay_with_cork=yes ac_cv_sizeof_struct_iovec=8" />
769+
<arg line="--disable-shared --prefix=${aprHome} --host=aarch64-linux-gnu CC=aarch64-none-linux-gnu-gcc CFLAGS='-O3 -fno-omit-frame-pointer -fPIC -DHAVE_PTHREAD_RWLOCKS=1 -U_FORTIFY_SOURCE' ac_cv_have_decl_sys_siglist=no ac_cv_file__dev_zero=yes ac_cv_func_setpgrp_void=yes apr_cv_tcp_nodelay_with_cork=yes ac_cv_sizeof_struct_iovec=8" />
770770
</exec>
771771
<!--
772772
Make will fail when it tries to use the gen_test_char program.
@@ -790,7 +790,7 @@
790790
Disable the detection of sys_siglist and just use apr's own implementation to workaround problems when trying to use static jars on alpine linux
791791
See https://github.com/netty/netty-tcnative/issues/853
792792
-->
793-
<arg line="--disable-shared --prefix=${aprHome} CFLAGS='-O3 -fno-omit-frame-pointer -fPIC' ${macOsxDeploymentTarget} ac_cv_have_decl_sys_siglist=no" />
793+
<arg line="--disable-shared --prefix=${aprHome} CFLAGS='-O3 -fno-omit-frame-pointer -fPIC -U_FORTIFY_SOURCE' ${macOsxDeploymentTarget} ac_cv_have_decl_sys_siglist=no" />
794794
</exec>
795795
<exec executable="make" failonerror="true" dir="${aprSourceDir}" resolveexecutable="true" />
796796
<exec executable="make" failonerror="true" dir="${aprSourceDir}" resolveexecutable="true">

‎scripts/check_musl_compat.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ __stack_chk_fail __stack_chk_guard __tls_get_addr
101101

102102
# The fallbacks openssl-dynamic/src/main/c/musl_compat.c defines. Asserting these are *defined*
103103
# rather than undefined catches the compatibility file being dropped or excluded from the link.
104-
MUSL_COMPAT_SYMS='__getauxval fopen64 __isinf __isnan __strdup'
104+
MUSL_COMPAT_SYMS='__getauxval fopen64 __isinf __isnan __strdup __sprintf_chk __fprintf_chk __libc_single_threaded __isoc23_strtol __isoc23_strtoul __isoc23_strtoll __isoc23_strtoull __isoc23_strtoimax __isoc23_strtoumax __isoc23_sscanf __isoc23_vsscanf _dl_find_object'
105105

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

0 commit comments

Comments
 (0)