Created
December 7, 2016 16:50
-
-
Save cwshu/721007e2bff7361f4cf046f014eed1e0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
APR_DECLARE(apr_status_t) apr_proc_create(apr_proc_t *new, | |
const char *progname, | |
const char * const *args, | |
const char * const *env, | |
apr_procattr_t *attr, | |
apr_pool_t *pool) | |
{ | |
apr_status_t rv; | |
apr_size_t i; | |
const char *argv0; | |
char *cmdline; | |
char *pEnvBlock; | |
PROCESS_INFORMATION pi; | |
DWORD dwCreationFlags = 0; | |
new->in = attr->parent_in; | |
new->out = attr->parent_out; | |
new->err = attr->parent_err; | |
if (attr->detached) { | |
/* If we are creating ourselves detached, then we should hide the | |
* window we are starting in. And we had better redefine our | |
* handles for STDIN, STDOUT, and STDERR. Do not set the | |
* detached attribute for Win9x. We have found that Win9x does | |
* not manage the stdio handles properly when running old 16 | |
* bit executables if the detached attribute is set. | |
*/ | |
if (apr_os_level >= APR_WIN_NT) { | |
/* | |
* XXX DETACHED_PROCESS won't on Win9x at all; on NT/W2K | |
* 16 bit executables fail (MS KB: Q150956) | |
*/ | |
dwCreationFlags |= DETACHED_PROCESS; | |
} | |
} | |
/* progname must be unquoted, in native format, as there are all sorts | |
* of bugs in the NT library loader code that fault when parsing '/'. | |
* XXX progname must be NULL if this is a 16 bit app running in WOW | |
*/ | |
if (progname[0] == '\"') { | |
progname = apr_pstrmemdup(pool, progname + 1, strlen(progname) - 2); | |
} | |
if (attr->cmdtype == APR_PROGRAM || attr->cmdtype == APR_PROGRAM_ENV) { | |
char *fullpath = NULL; | |
if ((rv = apr_filepath_merge(&fullpath, attr->currdir, progname, | |
APR_FILEPATH_NATIVE, pool)) != APR_SUCCESS) { | |
if (attr->errfn) { | |
attr->errfn(pool, rv, | |
apr_pstrcat(pool, "filepath_merge failed.", | |
" currdir: ", attr->currdir, | |
" progname: ", progname, NULL)); | |
} | |
return rv; | |
} | |
progname = fullpath; | |
} | |
else { | |
/* Do not fail if the path isn't parseable for APR_PROGRAM_PATH | |
* or APR_SHELLCMD. We only invoke apr_filepath_merge (with no | |
* left hand side expression) in order to correct the path slash | |
* delimiters. But the filename doesn't need to be in the CWD, | |
* nor does it need to be a filename at all (it could be a | |
* built-in shell command.) | |
*/ | |
char *fullpath = NULL; | |
if ((rv = apr_filepath_merge(&fullpath, "", progname, | |
APR_FILEPATH_NATIVE, pool)) == APR_SUCCESS) { | |
progname = fullpath; | |
} | |
} | |
if (has_space(progname)) { | |
argv0 = apr_pstrcat(pool, "\"", progname, "\"", NULL); | |
} | |
else { | |
argv0 = progname; | |
} | |
/* Handle the args, seperate from argv0 */ | |
cmdline = ""; | |
for (i = 1; args && args[i]; ++i) { | |
if (has_space(args[i]) || !args[i][0]) { | |
cmdline = apr_pstrcat(pool, cmdline, " \"", args[i], "\"", NULL); | |
} | |
else { | |
cmdline = apr_pstrcat(pool, cmdline, " ", args[i], NULL); | |
} | |
} | |
#ifndef _WIN32_WCE | |
if (attr->cmdtype == APR_SHELLCMD || attr->cmdtype == APR_SHELLCMD_ENV) { | |
char *shellcmd = getenv("COMSPEC"); | |
if (!shellcmd) { | |
if (attr->errfn) { | |
attr->errfn(pool, APR_EINVAL, "COMSPEC envar is not set"); | |
} | |
return APR_EINVAL; | |
} | |
if (shellcmd[0] == '"') { | |
progname = apr_pstrmemdup(pool, shellcmd + 1, strlen(shellcmd) - 2); | |
} | |
else { | |
progname = shellcmd; | |
if (has_space(shellcmd)) { | |
shellcmd = apr_pstrcat(pool, "\"", shellcmd, "\"", NULL); | |
} | |
} | |
/* Command.com does not support a quoted command, while cmd.exe demands one. | |
*/ | |
i = strlen(progname); | |
if (i >= 11 && strcasecmp(progname + i - 11, "command.com") == 0) { | |
cmdline = apr_pstrcat(pool, shellcmd, " /C ", argv0, cmdline, NULL); | |
} | |
else { | |
cmdline = apr_pstrcat(pool, shellcmd, " /C \"", argv0, cmdline, "\"", NULL); | |
} | |
} | |
else | |
#endif | |
{ | |
#if defined(_WIN32_WCE) | |
{ | |
#else | |
/* Win32 is _different_ than unix. While unix will find the given | |
* program since it's already chdir'ed, Win32 cannot since the parent | |
* attempts to open the program with it's own path. | |
* ###: This solution isn't much better - it may defeat path searching | |
* when the path search was desired. Open to further discussion. | |
*/ | |
i = strlen(progname); | |
if (i >= 4 && (strcasecmp(progname + i - 4, ".bat") == 0 | |
|| strcasecmp(progname + i - 4, ".cmd") == 0)) | |
{ | |
char *shellcmd = getenv("COMSPEC"); | |
if (!shellcmd) { | |
if (attr->errfn) { | |
attr->errfn(pool, APR_EINVAL, "COMSPEC envar is not set"); | |
} | |
return APR_EINVAL; | |
} | |
if (shellcmd[0] == '"') { | |
progname = apr_pstrmemdup(pool, shellcmd + 1, strlen(shellcmd) - 2); | |
} | |
else { | |
progname = shellcmd; | |
if (has_space(shellcmd)) { | |
shellcmd = apr_pstrcat(pool, "\"", shellcmd, "\"", NULL); | |
} | |
} | |
i = strlen(progname); | |
if (i >= 11 && strcasecmp(progname + i - 11, "command.com") == 0) { | |
/* XXX: Still insecure - need doubled-quotes on each individual | |
* arg of cmdline. Suspect we need to postpone cmdline parsing | |
* until this moment in all four code paths, with some flags | |
* to toggle 'which flavor' is needed. | |
*/ | |
cmdline = apr_pstrcat(pool, shellcmd, " /C ", argv0, cmdline, NULL); | |
} | |
else { | |
/* We must protect the cmdline args from any interpolation - this | |
* is not a shellcmd, and the source of argv[] is untrusted. | |
* Notice we escape ALL the cmdline args, including the quotes | |
* around the individual args themselves. No sense in allowing | |
* the shift-state to be toggled, and the application will | |
* not see the caret escapes. | |
*/ | |
cmdline = apr_caret_escape_args(pool, cmdline); | |
/* | |
* Our app name must always be quoted so the quotes surrounding | |
* the entire /c "command args" are unambigious. | |
*/ | |
if (*argv0 != '"') { | |
cmdline = apr_pstrcat(pool, shellcmd, " /C \"\"", argv0, "\"", cmdline, "\"", NULL); | |
} | |
else { | |
cmdline = apr_pstrcat(pool, shellcmd, " /C \"", argv0, cmdline, "\"", NULL); | |
} | |
} | |
} | |
else { | |
#endif | |
/* A simple command we are directly invoking. Do not pass | |
* the first arg to CreateProc() for APR_PROGRAM_PATH | |
* invocation, since it would need to be a literal and | |
* complete file path. That is; "c:\bin\aprtest.exe" | |
* would succeed, but "c:\bin\aprtest" or "aprtest.exe" | |
* can fail. | |
*/ | |
cmdline = apr_pstrcat(pool, argv0, cmdline, NULL); | |
if (attr->cmdtype == APR_PROGRAM_PATH) { | |
progname = NULL; | |
} | |
} | |
} | |
if (!env || attr->cmdtype == APR_PROGRAM_ENV || | |
attr->cmdtype == APR_SHELLCMD_ENV) { | |
pEnvBlock = NULL; | |
} | |
else { | |
apr_size_t iEnvBlockLen; | |
/* | |
* Win32's CreateProcess call requires that the environment | |
* be passed in an environment block, a null terminated block of | |
* null terminated strings. | |
*/ | |
i = 0; | |
iEnvBlockLen = 1; | |
while (env[i]) { | |
iEnvBlockLen += strlen(env[i]) + 1; | |
i++; | |
} | |
if (!i) | |
++iEnvBlockLen; | |
#if APR_HAS_ANSI_FS | |
ELSE_WIN_OS_IS_ANSI | |
{ | |
char *pNext; | |
pEnvBlock = (char *)apr_palloc(pool, iEnvBlockLen); | |
i = 0; | |
pNext = pEnvBlock; | |
while (env[i]) { | |
strcpy(pNext, env[i]); | |
pNext = strchr(pNext, '\0') + 1; | |
i++; | |
} | |
if (!i) | |
*(pNext++) = '\0'; | |
*pNext = '\0'; | |
} | |
#endif /* APR_HAS_ANSI_FS */ | |
} | |
new->invoked = cmdline; | |
#if APR_HAS_ANSI_FS | |
ELSE_WIN_OS_IS_ANSI | |
{ | |
STARTUPINFOA si; | |
memset(&si, 0, sizeof(si)); | |
si.cb = sizeof(si); | |
if (attr->detached) { | |
si.dwFlags |= STARTF_USESHOWWINDOW; | |
si.wShowWindow = SW_HIDE; | |
} | |
if ((attr->child_in && attr->child_in->filehand) | |
|| (attr->child_out && attr->child_out->filehand) | |
|| (attr->child_err && attr->child_err->filehand)) | |
{ | |
si.dwFlags |= STARTF_USESTDHANDLES; | |
si.hStdInput = (attr->child_in) | |
? attr->child_in->filehand | |
: GetStdHandle(STD_INPUT_HANDLE); | |
si.hStdOutput = (attr->child_out) | |
? attr->child_out->filehand | |
: GetStdHandle(STD_OUTPUT_HANDLE); | |
si.hStdError = (attr->child_err) | |
? attr->child_err->filehand | |
: GetStdHandle(STD_ERROR_HANDLE); | |
} | |
rv = CreateProcessA(progname, cmdline, /* Command line */ | |
NULL, NULL, /* Proc & thread security attributes */ | |
TRUE, /* Inherit handles */ | |
dwCreationFlags, /* Creation flags */ | |
pEnvBlock, /* Environment block */ | |
attr->currdir, /* Current directory name */ | |
&si, &pi); | |
} | |
#endif /* APR_HAS_ANSI_FS */ | |
/* Check CreateProcess result | |
*/ | |
if (!rv) | |
return apr_get_os_error(); | |
/* XXX Orphaned handle warning - no fix due to broken apr_proc_t api. | |
*/ | |
new->hproc = pi.hProcess; | |
new->pid = pi.dwProcessId; | |
if ((attr->child_in) && (attr->child_in != &no_file)) { | |
apr_file_close(attr->child_in); | |
} | |
if ((attr->child_out) && (attr->child_out != &no_file)) { | |
apr_file_close(attr->child_out); | |
} | |
if ((attr->child_err) && (attr->child_err != &no_file)) { | |
apr_file_close(attr->child_err); | |
} | |
CloseHandle(pi.hThread); | |
return APR_SUCCESS; | |
} |
win32 spawn process without UNICODE support
we can compare with posix version: https://github.com/apache/apr/blob/1.5.2/threadproc/unix/proc.c#L344
- CreateProcessA(): fork()+exec()
- char *pEnvBlock: environment variable
- STARTUPINFOA si: IO redirection
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Code Path from apache:
https://github.com/apache/httpd/blob/bf1a49c6657f521d811ff2320b7fd8bf91c79817/modules/generators/mod_cgi.c#L458
https://github.com/apache/httpd/blob/fbc5e20ead005fd3a2bec05924f9e90dfd195406/os/win32/util_win32.c#L76