win: use PROC_THREAD_ATTRIBUTE_HANDLE_LIST in uv_spawn

Use STARTUPINFOEXW with PROC_THREAD_ATTRIBUTE_HANDLE_LIST so that only
the stdio handles prepared for each child process are inherited by it.
This closes the race condition where two concurrent uv_spawn calls could
cause handles intended for one child to leak into another child process.

Fixes: https://github.com/libuv/libuv/issues/1490

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sandbox User 2026-03-24 14:55:01 +00:00
parent e3a27e0728
commit 0d4acd733b

View File

@ -902,14 +902,20 @@ int uv_spawn(uv_loop_t* loop,
BOOL result;
WCHAR* application_path = NULL, *application = NULL, *arguments = NULL,
*env = NULL, *cwd = NULL;
STARTUPINFOW startup;
STARTUPINFOEXW startup;
PROCESS_INFORMATION info;
DWORD process_flags, cwd_len;
BYTE* child_stdio_buffer;
HANDLE* handle_list;
LPPROC_THREAD_ATTRIBUTE_LIST attr_list;
int attr_list_initialized;
uv__process_init(loop, process);
process->exit_cb = options->exit_cb;
child_stdio_buffer = NULL;
handle_list = NULL;
attr_list = NULL;
attr_list_initialized = 0;
if (options->flags & (UV_PROCESS_SETGID | UV_PROCESS_SETUID)) {
return UV_ENOTSUP;
@ -1022,20 +1028,67 @@ int uv_spawn(uv_loop_t* loop,
goto done;
}
startup.cb = sizeof(startup);
startup.lpReserved = NULL;
startup.lpDesktop = NULL;
startup.lpTitle = NULL;
startup.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
memset(&startup, 0, sizeof startup);
startup.StartupInfo.cb = sizeof(startup);
startup.StartupInfo.lpReserved = NULL;
startup.StartupInfo.lpDesktop = NULL;
startup.StartupInfo.lpTitle = NULL;
startup.StartupInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
startup.cbReserved2 = uv__stdio_size(child_stdio_buffer);
startup.lpReserved2 = (BYTE*) child_stdio_buffer;
startup.StartupInfo.cbReserved2 = uv__stdio_size(child_stdio_buffer);
startup.StartupInfo.lpReserved2 = (BYTE*) child_stdio_buffer;
startup.hStdInput = uv__stdio_handle(child_stdio_buffer, 0);
startup.hStdOutput = uv__stdio_handle(child_stdio_buffer, 1);
startup.hStdError = uv__stdio_handle(child_stdio_buffer, 2);
startup.StartupInfo.hStdInput = uv__stdio_handle(child_stdio_buffer, 0);
startup.StartupInfo.hStdOutput = uv__stdio_handle(child_stdio_buffer, 1);
startup.StartupInfo.hStdError = uv__stdio_handle(child_stdio_buffer, 2);
process_flags = CREATE_UNICODE_ENVIRONMENT;
/* Build the list of handles to inherit. Using
* PROC_THREAD_ATTRIBUTE_HANDLE_LIST ensures only these specific handles are
* inherited, closing the race condition where concurrent uv_spawn calls
* could cause handles intended for one child to leak into another. */
{
int count = uv__stdio_size(child_stdio_buffer);
int n = 0;
SIZE_T attr_size = 0;
handle_list = (HANDLE*) uv__malloc(count * sizeof(HANDLE));
if (handle_list == NULL) {
err = ERROR_OUTOFMEMORY;
goto done;
}
for (i = 0; i < count; i++) {
HANDLE h = uv__stdio_handle(child_stdio_buffer, i);
if (h != INVALID_HANDLE_VALUE)
handle_list[n++] = h;
}
InitializeProcThreadAttributeList(NULL, 1, 0, &attr_size);
attr_list = (LPPROC_THREAD_ATTRIBUTE_LIST) uv__malloc(attr_size);
if (attr_list == NULL) {
err = ERROR_OUTOFMEMORY;
goto done;
}
if (!InitializeProcThreadAttributeList(attr_list, 1, 0, &attr_size)) {
err = GetLastError();
goto done;
}
attr_list_initialized = 1;
if (!UpdateProcThreadAttribute(attr_list,
0,
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
handle_list,
n * sizeof(HANDLE),
NULL,
NULL)) {
err = GetLastError();
goto done;
}
}
startup.lpAttributeList = attr_list;
process_flags = CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT;
if ((options->flags & UV_PROCESS_WINDOWS_HIDE_CONSOLE) ||
(options->flags & UV_PROCESS_WINDOWS_HIDE)) {
@ -1050,9 +1103,9 @@ int uv_spawn(uv_loop_t* loop,
if ((options->flags & UV_PROCESS_WINDOWS_HIDE_GUI) ||
(options->flags & UV_PROCESS_WINDOWS_HIDE)) {
/* Use SW_HIDE to avoid any potential process window. */
startup.wShowWindow = SW_HIDE;
startup.StartupInfo.wShowWindow = SW_HIDE;
} else {
startup.wShowWindow = SW_SHOWDEFAULT;
startup.StartupInfo.wShowWindow = SW_SHOWDEFAULT;
}
if (options->flags & UV_PROCESS_DETACHED) {
@ -1078,7 +1131,7 @@ int uv_spawn(uv_loop_t* loop,
process_flags,
env,
cwd,
&startup,
&startup.StartupInfo,
&info)) {
/* CreateProcessW failed. */
err = GetLastError();
@ -1161,6 +1214,14 @@ int uv_spawn(uv_loop_t* loop,
uv__free(env);
uv__free(alloc_path);
if (attr_list != NULL) {
if (attr_list_initialized)
DeleteProcThreadAttributeList(attr_list);
uv__free(attr_list);
attr_list = NULL;
}
uv__free(handle_list);
if (child_stdio_buffer != NULL) {
/* Clean up child stdio handles. */
uv__stdio_destroy(child_stdio_buffer);