Configuration Information:
Machine: aarch64 (Apple silicon)
OS: darwin25.4.0 (built), running macOS 26.6.2 (25G83)
Bash Version: 5.3
Patch Level: 15
Release Status: release
Build: Homebrew bottle (bash 5.3.15), linked against Homebrew gettext 1.0
(libintl.8.dylib)
Locale environment: LANG unset, LC_CTYPE=UTF-8, no LC_ALL
Description:
Bash intermittently dies with SIGSEGV in the child process of a command
substitution, before exec, while restoring the locale after a command that
ran with a temporary `LC_ALL=C` assignment.
On restore, sv_locale calls libintl's setlocale, which on macOS asks
CoreFoundation for the preferred languages; CoreFoundation is not usable in
the child of a multi-threaded process after fork, and the process crashes.
macOS's crash report says so explicitly:
"CoreFoundation": "*** multi-threaded process forked ***"
"libsystem_c.dylib": "crashed on child side of fork pre-exec"
EXC_BAD_ACCESS (SIGSEGV), KERN_INVALID_ADDRESS
Crashing thread (the process has one thread), innermost first:
libsystem_trace.dylib _os_log_preferences_refresh
libsystem_trace.dylib os_log_type_enabled
CoreFoundation -[CFPrefsSearchListSource
alreadylocked_copyValueForKey:]
CoreFoundation -[CFPrefsSource copyValueForKey:]
CoreFoundation ...
_CFPreferencesCopyAppValueWithContainerAndConfiguration
Foundation LocaleCache.preferredLanguages(forCurrentUser:)
Foundation +[NSLocale _preferredLanguagesForCurrentUser:]
CoreFoundation CFLocaleCopyPreferredLanguages
libintl.8.dylib libintl_setlocale
bash reset_locale_vars
bash set_locale_var
bash sv_locale
bash dispose_temporary_env
bash dispose_used_env_vars
bash execute_command_internal
bash parse_and_execute
bash command_substitute
bash param_expand
bash expand_word_internal
bash call_expand_word_internal
bash expand_string_assignment
This looks like the same class of problem as the May 2024 thread on
bug-bash ("+initialize may have been in progress" / gettext after fork on
macOS, https://lists.gnu.org/archive/html/bug-bash/2024-05/msg00011.html),
where the fix was an early gettext() call in the parent.
This path is different: the CoreFoundation call comes from setlocale() in
the child, while restoring locale variables after a temporary environment,
and it still happens in 5.3.15.
The script that hits it uses the pattern below in several places, inside
`$(...)` and `<(...)`, under `set -euo pipefail`, in a long-running script
that also sources a few libraries.
Measured on one workload: 5 of 65 runs exited with status 139 within an
hour; more crash reports with the same stack appeared in that hour than
there were failed runs, so some crashing children may not fail the script.
Repeat-By:
I don't have a minimal reproducer yet.
The pattern is a command substitution whose command carries a temporary
locale assignment, for example:
x=$(some_function_or_pipeline | LC_ALL=C sort -u)
mapfile -t a < <(printf '%s\n' "${items[@]}" | LC_ALL=C sort -u)
A loop of 3,000 fresh `bash -c 'x=$(LC_ALL=C sort </dev/null)'` runs did
not crash, so it seems to need a parent that has already become
multi-threaded (presumably through an earlier setlocale() that initialised
CoreFoundation).
I can run a candidate patch or a debug build against the failing workload,
and send full .ips crash reports.
Fix:
Workaround I am about to test: `env LC_ALL=C sort` instead of `LC_ALL=C
sort`, so bash has no temporary locale assignment to restore in the forked
child.
In bash, presumably: avoid calling setlocale() (or anything reaching
CoreFoundation) in a forked child before exec on macOS, or make sure the
parent has initialised the locale machinery in a way that keeps the child
safe, as the 2024 fix did for gettext().
Building bash against its bundled libintl reportedly avoided the 2024
crash; I have not tried that for this one.
Best,
Jakub Bocheński