In kernel32/tests/loader.c, child_process will try to write to stdout after calling LdrShutdownProcess. LdrShutdownProcess calls DLL_PROCESS_DETACH on msvcrt, which calls msvcrt_free_io, which frees the ioinfo blocks. So to prevent use after free in this case, we keep the ioinfos for stdin/out/err static.
From: Yuxuan Shui yshui@codeweavers.com
In kernel32/tests/loader.c, child_process will try to write to stdout after calling LdrShutdownProcess. LdrShutdownProcess calls DLL_PROCESS_DETACH on msvcrt, which calls msvcrt_free_io, which frees the ioinfo blocks. So to prevent use after free in this case, we keep the ioinfos for stdin/out/err static. --- dlls/msvcrt/file.c | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-)
diff --git a/dlls/msvcrt/file.c b/dlls/msvcrt/file.c index 97bfc746abc..ced01937ffa 100644 --- a/dlls/msvcrt/file.c +++ b/dlls/msvcrt/file.c @@ -153,7 +153,19 @@ ioinfo MSVCRT___badioinfo = { INVALID_HANDLE_VALUE, WX_TEXT }; * array of pointers to ioinfo arrays [32] */ ioinfo * MSVCRT___pioinfo[MSVCRT_MAX_FILES/MSVCRT_FD_BLOCK_SIZE] = { 0 }; - +static ioinfo stdio_ioinfo[3]; +static RTL_CRITICAL_SECTION_DEBUG stdio_critsect_debug[3] = +{ + { 0, 0, &stdio_ioinfo[0].crit, + { &stdio_critsect_debug[0].ProcessLocksList, &stdio_critsect_debug[0].ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": stdin_cs") } }, + { 0, 0, &stdio_ioinfo[1].crit, + { &stdio_critsect_debug[1].ProcessLocksList, &stdio_critsect_debug[1].ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": stdout_cs") } }, + { 0, 0, &stdio_ioinfo[2].crit, + { &stdio_critsect_debug[2].ProcessLocksList, &stdio_critsect_debug[2].ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": stderr_cs") } }, +}; #if _MSVCR_VER >= 80
#if _MSVCR_VER >= 140 @@ -166,14 +178,17 @@ static inline void ioinfo_set_crit_init(ioinfo *info) { } #else + +#define EF_CRIT_INIT 0x01 + static inline BOOL ioinfo_is_crit_init(ioinfo *info) { - return info->exflag & 1; + return info->exflag & EF_CRIT_INIT; }
static inline void ioinfo_set_crit_init(ioinfo *info) { - info->exflag |= 1; + info->exflag |= EF_CRIT_INIT; } #endif
@@ -242,6 +257,25 @@ static inline void ioinfo_set_unicode(ioinfo *info, BOOL unicode) } #endif
+#if _MSVCR_VER >= 140 +static ioinfo stdio_ioinfo[3] = +{ + { .handle = INVALID_HANDLE_VALUE, .crit = { &stdio_critsect_debug[0], -1, 0, 0, 0, 0 } }, + { .handle = INVALID_HANDLE_VALUE, .crit = { &stdio_critsect_debug[1], -1, 0, 0, 0, 0 } }, + { .handle = INVALID_HANDLE_VALUE, .crit = { &stdio_critsect_debug[2], -1, 0, 0, 0, 0 } }, +}; +#else +static ioinfo stdio_ioinfo[3] = +{ + { .handle = INVALID_HANDLE_VALUE, .crit = { &stdio_critsect_debug[0], -1, 0, 0, 0, 0 }, + .exflag = EF_CRIT_INIT }, + { .handle = INVALID_HANDLE_VALUE, .crit = { &stdio_critsect_debug[1], -1, 0, 0, 0, 0 }, + .exflag = EF_CRIT_INIT }, + { .handle = INVALID_HANDLE_VALUE, .crit = { &stdio_critsect_debug[2], -1, 0, 0, 0, 0 }, + .exflag = EF_CRIT_INIT }, +}; +#endif + typedef struct { FILE file; CRITICAL_SECTION crit; @@ -394,6 +428,8 @@ static void time_to_filetime( __time64_t time, FILETIME *ft ) static inline ioinfo* get_ioinfo_nolock(int fd) { ioinfo *ret = NULL; + if(fd>=0 && fd<3) + return &stdio_ioinfo[fd]; if(fd>=0 && fd<MSVCRT_MAX_FILES) ret = MSVCRT___pioinfo[fd/MSVCRT_FD_BLOCK_SIZE]; if(!ret)
Judging from the test failures in msvcrt, this probably isn't right.
There shouldn't be any need to free memory on process exit, all we need is to flush pending buffers.
On Wed Jun 11 14:22:09 2025 +0000, Alexandre Julliard wrote:
There shouldn't be any need to free memory on process exit, all we need is to flush pending buffers.
Opened !8288, hope I understood what you meant correctly
This merge request was closed by Yuxuan Shui.