Hi, On macOS, the setlocale replacement in libintl (and in gnulib's setlocale module) calls CFLocaleCopyPreferredLanguages() every time setlocale (LC_ALL, "") falls back for LC_MESSAGES. Programs that fork() without exec() and change the locale again in the child then call CoreFoundation in the child. Apple's fork(2) says frameworks are unsafe there, and with bash it intermittently crashes with SIGSEGV. Shells do this all the time: bash restores the locale with setlocale (LC_ALL, "") after `LANG=C some_builtin` in a subshell.
It happens when all of these hold: - AppleLocale is a name that libc rejects. This is the case when the Region in System Settings is not the language's default region: English + Finland gives "en_US@rg=fizzzz", English + Germany gives "en_DE" (see bash bug https://savannah.gnu.org/bugs/?68453). - LANG and LC_* are unset. - setlocale (LC_ALL, "") runs in a fork()ed child. setlocale_single (LC_MESSAGES, "en_US@rg=fizzzz") then fails, and the fallback "Take the primary language preference" calls CFLocaleCopyPreferredLanguages(). The AppleLocale name and the language list in langprefs.c are cached, but this result is not, so it is looked up again on every call, including in the child. It is in gettext-runtime/intl/setlocale.c (line 1514) and in gnulib lib/setlocale.c (line 1515) in current git. Crash stack, the same in all 24 crash reports I have (macOS 27.2, build 26B123, Apple M3 Max): libsystem_trace _os_log_preferences_refresh <- EXC_BAD_ACCESS libsystem_trace os_log_type_enabled CoreFoundation -[CFPrefsSearchListSource alreadylocked_copyValueForKey:] ... CoreFoundation CFLocaleCopyPreferredLanguages bash libintl_setlocale bash reset_locale_vars bash sv_locale bash dispose_temporary_env The crash reports also say "*** single-threaded process forked ***" (CoreFoundation) and "crashed on child side of fork pre-exec" (libsystem_c). The faulting access is a byte read in an unmapped gap next to read-only shared memory regions, so it looks like os_log keeps a pointer to memory that the child did not inherit. It is intermittent because os_log refreshes its preferences only now and then: in one process, 5000 restores called CFLocaleCopyPreferredLanguages 5004 times but _os_log_preferences_refresh only 7 times. Reproducer, with any bash that uses libintl on macOS (for example Homebrew's); the loop is from https://github.com/NixOS/nixpkgs/pull/565511: LOOP='find /usr/bin -maxdepth 1 -type f -print0 | head -c 4000 | while IFS= read -r -d "" f; do LANG=C read -r -n 4 -u 3 m 3<"$f" 2>/dev/null done' for i in $(seq 1 200); do env -i HOME=/homeless-shelter PATH=/usr/bin:/bin bash -c "$LOOP" done 2>&1 | grep -c 'Segmentation fault' Crashes in 200 runs: - bash 5.3.20 linked against gettext 1.0's libintl.8.dylib: 15 to 23 - bash 5.3.20 with its bundled libintl (from gettext 0.21.1): 28 to 36 - bash devel, whose lib/intl/setlocale.c is the one from gettext 1.0: 28 I could not make a small C program crash that only calls setlocale (LC_ALL, "") in fork()ed children, so the reproducer needs bash. Suggested fix: cache the result of that lookup, as langprefs.c does. The first setlocale (LC_ALL, "") normally happens at program start, before any fork(), so children would reuse the cached value. The attached patch against current git does that. I tested the same change on gettext 1.0: bash linked against the patched libintl had 0 crashes in 600 runs, CoreFoundation was no longer called when the locale was restored, LC_MESSAGES still ends up as "en", and msgfmt's German and Finnish messages are unchanged. The patch is meant as an example of how this could be fixed. One limitation of a lazy cache: it does not help if the first lookup happens in a child, for example when the program starts with LANG set and a child later unsets it. AI disclosure: Claude Opus 5.5 has been used in the process. Thanks, Mika Tammi
0001-intl-Don-t-call-CoreFoundation-in-fork-ed-children-o.patch
Description: Binary data
