https://bugs.winehq.org/show_bug.cgi?id=57005
--- Comment #2 from Brendan Shanks bshanks@codeweavers.com --- The crash is in get_initial_environment(), because 'main_envp' is invalid. main() passes the value (not address!) of 'environ' into __wine_main(), where it's saved as 'main_envp'.
The problem is that 'environ' can change when setenv()/putenv() are called, in case the existing buffer isn't big enough and needs to be realloc()'ed. It's pretty easy to trigger this by calling setenv() with a large name/value. I found that the putenv() to set WINELOADER in init_paths() was causing 'environ' to change.
So 'environ' changes, but 'main_envp' does not and becomes dangling. get_initial_environment() runs later, it's the only function that uses 'main_envp' and crashes when it does. The fix is pretty simple, access 'environ' directly rather than caching it in 'main_envp'.
It seems totally circumstantial that this only crashes when installed and not from the build directory, maybe some difference in the environment variables being used.
I'm also not sure why this was triggered by 7b82f507bda6b94f6876256c278d3b3637e95b8c (which uses zero-fill sections instead of the preloader when possible). There could be some malloc behavior that's conditional on a newer deployment target for the binary (the preloader had to target 10.7). Or, something about the preloader may have resulted in 'environ' pointing to a buffer that was always large enough or could be resized in-place.
An added wrinkle is that on macOS, environ(7) states that environ cannot be used by shared libraries (like ntdll.so), instead you must use _NSGetEnviron(). I investigated this further and found that in LC_MAIN binaries (built when targeting 10.8 or later), environ is present in dylibs and appears to be fully functional (it's always equal to *_NSGetEnviron()). But in LC_UNIXTHREAD binaries (10.7 or earlier), environ is present but does not get updated correctly. If the main process 'environ' changes, environ in the dylib does not. _NSGetEnviron() must be used in this case to avoid a use-after-free.
When the preloader is being used (still supported although not preferred) it is an LC_UNIXTHREAD binary, so Wine needs to use _NSGetEnviron(). This needs to be fixed in msv1_0 and in ntdll/unix/loader.c (when spawning the server).