Skip to content

Commit c9444e4

Browse files
committed
fix: promote ghc-internal to RTLD_GLOBAL to prevent duplicate loading
When ghc-iserv loads user code that depends on ghc-internal via dlopen, the dynamic linker may load a second copy of libHSghc-internal.so if the original was loaded with RTLD_LOCAL (which is required by Note [RTLD_LOCAL] in rts/Linker.c for symbol override behavior). This causes heap corruption because: 1. The first copy's init_ghc_hs_iface() initializes ghc_hs_iface in the RTS 2. User code compiled against the second copy creates closures with info tables from the second copy 3. The GC uses ghc_hs_iface (pointing to first copy's info tables) to validate closures 4. The mismatch causes "strange closure type" errors during GC This fix uses dladdr to find the shared library containing init_ghc_hs_iface (a known symbol from ghc-internal), then uses dlopen with RTLD_NOLOAD | RTLD_GLOBAL to promote it to global scope before running any Haskell code. See Note [Promoting ghc-internal to RTLD_GLOBAL] in iservmain.c.
1 parent 1241092 commit c9444e4

File tree

2 files changed

+56
-0
lines changed

2 files changed

+56
-0
lines changed

rts/RtsStartup.c

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,46 @@
7070
#include <locale.h>
7171
#endif
7272

73+
/*
74+
* Note [Promoting Boot Libraries to RTLD_GLOBAL]
75+
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
76+
* Boot libraries (ghc-internal, etc.) are loaded by the system linker at
77+
* program startup with RTLD_LOCAL (default). When user code is later
78+
* dlopen'd, the dynamic linker can't see these existing copies and loads
79+
* FRESH copies, causing duplicate global state.
80+
*
81+
* With ghc-internal, this causes GC crashes ("strange closure type")
82+
* because the RTS's ghc_hs_iface pointer references info tables from
83+
* the first copy, while user code uses info tables from the second copy.
84+
*
85+
* Fix: Promote boot libraries to RTLD_GLOBAL scope early in RTS init,
86+
* before any user code can trigger dlopen.
87+
*
88+
* This is a no-op on:
89+
* - Windows (different linking model)
90+
* - Systems without RTLD_NOLOAD
91+
* - Static executables (symbols already global)
92+
*/
93+
#if !defined(mingw32_HOST_OS) && defined(RTLD_NOLOAD)
94+
#include <dlfcn.h>
95+
96+
static void promoteBootLibrariesToGlobal(void)
97+
{
98+
extern void init_ghc_hs_iface(void);
99+
Dl_info info;
100+
if (dladdr((void*)&init_ghc_hs_iface, &info) && info.dli_fname) {
101+
void* handle = dlopen(info.dli_fname, RTLD_NOW | RTLD_NOLOAD | RTLD_GLOBAL);
102+
IF_DEBUG(linker,
103+
if (handle) {
104+
debugBelch("RTS: promoted ghc-internal (%s) to RTLD_GLOBAL\n",
105+
info.dli_fname);
106+
}
107+
);
108+
(void)handle;
109+
}
110+
}
111+
#endif
112+
73113
// Count of how many outstanding hs_init()s there have been.
74114
static StgWord hs_init_count = 0;
75115
static bool rts_shutdown = false;
@@ -267,6 +307,12 @@ hs_init_ghc(int *argc, char **argv[], RtsConfig rts_config)
267307

268308
init_ghc_hs_iface();
269309

310+
#if !defined(mingw32_HOST_OS) && defined(RTLD_NOLOAD)
311+
/* Promote boot libraries to RTLD_GLOBAL for dynamic code loading.
312+
* See Note [Promoting Boot Libraries to RTLD_GLOBAL] */
313+
promoteBootLibrariesToGlobal();
314+
#endif
315+
270316
/* Initialise the stats department, phase 0 */
271317
initStats0();
272318

utils/ghc-iserv/cbits/iservmain.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44

55
#include <HsFFI.h>
66

7+
/*
8+
* Note [Boot library symbol visibility]
9+
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
10+
* Boot library promotion to RTLD_GLOBAL is now handled in RTS initialization.
11+
* See Note [Promoting Boot Libraries to RTLD_GLOBAL] in rts/RtsStartup.c.
12+
*
13+
* This ensures all GHC API programs (not just ghc-iserv) properly export
14+
* boot library symbols before any user code is loaded via dlopen.
15+
*/
16+
717
int main (int argc, char *argv[])
818
{
919
RtsConfig conf = defaultRtsConfig;

0 commit comments

Comments
 (0)