Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add a flag to the uv_spawn to trigger UAC on Windows #4296

Open
wants to merge 23 commits into
base: v1.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 14 additions & 1 deletion docs/src/process.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,20 @@ Data types
* search for the exact file name before trying variants with
* extensions like '.exe' or '.cmd'.
*/
UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME = (1 << 7)
UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME = (1 << 7),
/*
* Run the subprocess as administrator. This option is only meaningful on
* Windows systems. On Unix it is silently ignored.
* This flag is mutually exclusive with UV_PROCESS_DETACHED. UV_EINVAL is
* returned when both are specified.
* This flag is mutually exclusive with the stdio, cwd, env and stdio_count options in
* the uv_process_options_t struct. UV_EINVAL is returned when any of them
* are specified.
*
* See https://github.com/libuv/libuv/issues/4295
*/
UV_PROCESS_WINDOWS_RUNAS_ADMIN = (1 << 8)

};

.. c:type:: uv_stdio_container_t
Expand Down
9 changes: 8 additions & 1 deletion include/uv.h
Original file line number Diff line number Diff line change
Expand Up @@ -1113,7 +1113,14 @@ enum uv_process_flags {
* search for the exact file name before trying variants with
* extensions like '.exe' or '.cmd'.
*/
UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME = (1 << 7)
UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME = (1 << 7),
/*
* Run the subprocess as administrator. This option is only meaningful on
* Windows systems. On Unix it is silently ignored.
*
* See https://github.com/libuv/libuv/issues/4295
*/
UV_PROCESS_WINDOWS_RUNAS_ADMIN = (1 << 8)
};

/*
Expand Down
125 changes: 88 additions & 37 deletions src/win/process.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include <dbghelp.h>
#include <shlobj.h>
#include <psapi.h> /* GetModuleBaseNameW */
#include <shellapi.h> /* ShellExecuteExW */


#define SIGKILL 9
Expand Down Expand Up @@ -917,6 +918,13 @@ int uv_spawn(uv_loop_t* loop,
*env = NULL, *cwd = NULL;
STARTUPINFOW startup;
PROCESS_INFORMATION info;
SHELLEXECUTEINFOW shell_execute_info_w = {0};

HANDLE proc_handle;
DWORD proc_id;
HANDLE thread_handle = NULL;
ULONG flag_mask = 0; /* for keep the ProcHandle */

DWORD process_flags;
BYTE* child_stdio_buffer;

Expand All @@ -928,11 +936,24 @@ int uv_spawn(uv_loop_t* loop,
return UV_ENOTSUP;
}

if (options->file == NULL ||
options->args == NULL) {
if (options->file == NULL || options->args == NULL) {
return UV_EINVAL;
}

if (options->flags & UV_PROCESS_WINDOWS_RUNAS_ADMIN) {
/* UV_PROCESS_WINDOWS_RUNAS_ADMIN and UV_PROCESS_DETACHED are mutually
* exclusive. */
if (options->flags & UV_PROCESS_DETACHED)
return UV_EINVAL;
/* These options are not supported when UV_PROCESS_WINDOWS_RUNAS_ADMIN is
* set. */
if (options->stdio != NULL || options->cwd != NULL ||
options->env != NULL)
return UV_EINVAL;
if (options->stdio_count > 0)
return UV_EINVAL;
}

assert(options->file != NULL);
assert(!(options->flags & ~(UV_PROCESS_DETACHED |
UV_PROCESS_SETGID |
Expand All @@ -941,7 +962,8 @@ int uv_spawn(uv_loop_t* loop,
UV_PROCESS_WINDOWS_HIDE |
UV_PROCESS_WINDOWS_HIDE_CONSOLE |
UV_PROCESS_WINDOWS_HIDE_GUI |
UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS)));
UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS |
UV_PROCESS_WINDOWS_RUNAS_ADMIN)));

err = uv__utf8_to_utf16_alloc(options->file, &application);
if (err)
Expand Down Expand Up @@ -1025,19 +1047,6 @@ 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;

startup.cbReserved2 = uv__stdio_size(child_stdio_buffer);
startup.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);

process_flags = CREATE_UNICODE_ENVIRONMENT;

if ((options->flags & UV_PROCESS_WINDOWS_HIDE_CONSOLE) ||
Expand All @@ -1046,8 +1055,10 @@ int uv_spawn(uv_loop_t* loop,
for (i = 0; i < options->stdio_count; i++) {
if (options->stdio[i].flags & UV_INHERIT_FD)
break;
if (i == options->stdio_count - 1)
if (i == options->stdio_count - 1) {
process_flags |= CREATE_NO_WINDOW;
flag_mask |= SEE_MASK_NO_CONSOLE;
}
}
}
if ((options->flags & UV_PROCESS_WINDOWS_HIDE_GUI) ||
Expand All @@ -1073,27 +1084,65 @@ int uv_spawn(uv_loop_t* loop,
process_flags |= CREATE_SUSPENDED;
}

if (!CreateProcessW(application_path,
arguments,
NULL,
NULL,
1,
process_flags,
env,
cwd,
&startup,
&info)) {
/* CreateProcessW failed. */
err = GetLastError();
goto done;
if (options->flags & UV_PROCESS_WINDOWS_RUNAS_ADMIN) {
shell_execute_info_w.cbSize = sizeof(shell_execute_info_w);
shell_execute_info_w.fMask = flag_mask | SEE_MASK_NOCLOSEPROCESS; /* for shell_execute_info_w.hProcess */
shell_execute_info_w.lpVerb = L"runas";
shell_execute_info_w.lpFile = application_path;
shell_execute_info_w.lpParameters = arguments;
shell_execute_info_w.nShow = startup.wShowWindow;
if (!ShellExecuteExW(&shell_execute_info_w)) {
err = GetLastError();
goto done;
}
proc_handle = shell_execute_info_w.hProcess;
if (proc_handle == NULL) {
err = UV_ENOENT;
goto done_uv;
}
proc_id = GetProcessId(proc_handle);
if (proc_id == 0) {
err = GetLastError();
goto done;
}
} else {
startup.cb = sizeof(startup);
startup.lpReserved = NULL;
startup.lpDesktop = NULL;
startup.lpTitle = NULL;
startup.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;

startup.cbReserved2 = uv__stdio_size(child_stdio_buffer);
startup.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);

if (!CreateProcessW(application_path,
arguments,
NULL,
NULL,
1,
process_flags,
env,
cwd,
&startup,
&info)) {
err = GetLastError();
goto done;
}
proc_handle = info.hProcess;
proc_id = info.dwProcessId;
thread_handle = info.hThread;
}

/* If the process isn't spawned as detached, assign to the global job object
* so windows will kill it when the parent process dies. */
if (!(options->flags & UV_PROCESS_DETACHED)) {
uv_once(&uv_global_job_handle_init_guard_, uv__init_global_job_handle);

if (!AssignProcessToJobObject(uv_global_job_handle_, info.hProcess)) {
if (!AssignProcessToJobObject(uv_global_job_handle_, proc_handle)) {
/* AssignProcessToJobObject might fail if this process is under job
* control and the job doesn't have the
* JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK flag set, on a Windows version
Expand All @@ -1110,18 +1159,18 @@ int uv_spawn(uv_loop_t* loop,
}
}

if (process_flags & CREATE_SUSPENDED) {
if (ResumeThread(info.hThread) == ((DWORD)-1)) {
if ((process_flags & CREATE_SUSPENDED) && thread_handle != NULL) {
if (ResumeThread(thread_handle) == ((DWORD)-1)) {
err = GetLastError();
TerminateProcess(info.hProcess, 1);
TerminateProcess(proc_handle, 1);
goto done;
}
}

/* Spawn succeeded. Beyond this point, failure is reported asynchronously. */

process->process_handle = info.hProcess;
process->pid = info.dwProcessId;
process->process_handle = proc_handle;
process->pid = proc_id;

/* Set IPC pid to all IPC pipes. */
for (i = 0; i < options->stdio_count; i++) {
Expand All @@ -1142,7 +1191,9 @@ int uv_spawn(uv_loop_t* loop,
uv_fatal_error(GetLastError(), "RegisterWaitForSingleObject");
}

CloseHandle(info.hThread);
if (thread_handle != NULL) {
CloseHandle(thread_handle);
}

assert(!err);

Expand Down