windows: sort environment variables before calling CreateProcess

This commit is contained in:
Jameson Nash 2014-07-17 23:24:04 -04:00 committed by Saúl Ibarra Corretgé
parent 2ce14cfab4
commit 8db42383ad
2 changed files with 265 additions and 103 deletions

View File

@ -25,6 +25,8 @@
#include <stdlib.h>
#include <signal.h>
#include <limits.h>
#include <malloc.h>
#include <wchar.h>
#include "uv.h"
#include "internal.h"
@ -36,14 +38,27 @@
typedef struct env_var {
const char* narrow;
const WCHAR* wide;
size_t len; /* including null or '=' */
DWORD value_len;
int supplied;
const WCHAR* const wide;
const WCHAR* const wide_eq;
const size_t len; /* including null or '=' */
} env_var_t;
#define E_V(str) { str "=", L##str, sizeof(str), 0, 0 }
#define E_V(str) { L##str, L##str L"=", sizeof(str) }
static const env_var_t required_vars[] = { /* keep me sorted */
E_V("HOMEDRIVE"),
E_V("HOMEPATH"),
E_V("LOGONSERVER"),
E_V("PATH"),
E_V("SYSTEMDRIVE"),
E_V("SYSTEMROOT"),
E_V("TEMP"),
E_V("USERDOMAIN"),
E_V("USERNAME"),
E_V("USERPROFILE"),
E_V("WINDIR"),
};
static size_t n_required_vars = ARRAY_SIZE(required_vars);
static HANDLE uv_global_job_handle_;
@ -587,25 +602,56 @@ error:
}
/*
* If we learn that people are passing in huge environment blocks
* then we should probably qsort() the array and then bsearch()
* to see if it contains this variable. But there are ownership
* issues associated with that solution; this is the caller's
* char**, and modifying it is rude.
*/
static void check_required_vars_contains_var(env_var_t* required, int count,
const char* var) {
int i;
for (i = 0; i < count; ++i) {
if (_strnicmp(required[i].narrow, var, required[i].len) == 0) {
required[i].supplied = 1;
return;
int env_strncmp(const wchar_t* a, int na, const wchar_t* b) {
wchar_t* a_eq;
wchar_t* b_eq;
wchar_t* A;
wchar_t* B;
int nb;
int r;
if (na < 0) {
a_eq = wcschr(a, L'=');
assert(a_eq);
na = (int)(long)(a_eq - a);
} else {
na--;
}
b_eq = wcschr(b, L'=');
assert(b_eq);
nb = b_eq - b;
A = alloca((na+1) * sizeof(wchar_t));
B = alloca((nb+1) * sizeof(wchar_t));
r = LCMapStringW(LOCALE_INVARIANT, LCMAP_UPPERCASE, a, na, A, na);
assert(r==na);
A[na] = L'\0';
r = LCMapStringW(LOCALE_INVARIANT, LCMAP_UPPERCASE, b, nb, B, nb);
assert(r==nb);
B[nb] = L'\0';
while (1) {
wchar_t AA = *A++;
wchar_t BB = *B++;
if (AA < BB) {
return -1;
} else if (AA > BB) {
return 1;
} else if (!AA && !BB) {
return 0;
}
}
}
static int qsort_wcscmp(const void *a, const void *b) {
wchar_t* astr = *(wchar_t* const*)a;
wchar_t* bstr = *(wchar_t* const*)b;
return env_strncmp(astr, -1, bstr);
}
/*
* The way windows takes environment variables is different than what C does;
* Windows wants a contiguous block of null-terminated strings, terminated
@ -626,93 +672,143 @@ int make_program_env(char* env_block[], WCHAR** dst_ptr) {
WCHAR* dst;
WCHAR* ptr;
char** env;
size_t env_len = 1; /* room for closing null */
size_t env_len = 0;
int len;
size_t i;
DWORD var_size;
size_t env_block_count = 1; /* 1 for null-terminator */
WCHAR* dst_copy;
WCHAR** ptr_copy;
WCHAR** env_copy;
DWORD* required_vars_value_len = alloca(n_required_vars * sizeof(DWORD*));
env_var_t required_vars[] = {
E_V("SYSTEMROOT"),
E_V("SYSTEMDRIVE"),
E_V("TEMP"),
E_V("HOMEDRIVE"),
E_V("HOMEPATH"),
E_V("USERDOMAIN"),
E_V("USERNAME"),
E_V("USERPROFILE"),
E_V("WINDIR"),
E_V("PATH"),
E_V("LOGONSERVER"),
};
/* first pass: determine size in UTF-16 */
for (env = env_block; *env; env++) {
int len;
check_required_vars_contains_var(required_vars,
ARRAY_SIZE(required_vars),
*env);
len = MultiByteToWideChar(CP_UTF8,
0,
*env,
-1,
NULL,
0);
if (len <= 0) {
return GetLastError();
if (strchr(*env, '=')) {
len = MultiByteToWideChar(CP_UTF8,
0,
*env,
-1,
NULL,
0);
if (len <= 0) {
return GetLastError();
}
env_len += len;
env_block_count++;
}
env_len += len;
}
for (i = 0; i < ARRAY_SIZE(required_vars); ++i) {
if (!required_vars[i].supplied) {
/* second pass: copy to UTF-16 environment block */
dst_copy = _malloca(env_len * sizeof(WCHAR));
if (!dst_copy) {
return ERROR_OUTOFMEMORY;
}
env_copy = alloca(env_block_count * sizeof(WCHAR*));
ptr = dst_copy;
ptr_copy = env_copy;
for (env = env_block; *env; env++) {
if (strchr(*env, '=')) {
len = MultiByteToWideChar(CP_UTF8,
0,
*env,
-1,
ptr,
(int) (env_len - (ptr - dst_copy)));
if (len <= 0) {
DWORD err = GetLastError();
_freea(dst_copy);
return err;
}
*ptr_copy++ = ptr;
ptr += len;
}
}
*ptr_copy = NULL;
assert(env_len == ptr - dst_copy);
/* sort our (UTF-16) copy */
qsort(env_copy, env_block_count-1, sizeof(wchar_t*), qsort_wcscmp);
/* third pass: check for required variables */
for (ptr_copy = env_copy, i = 0; i < n_required_vars; ) {
int cmp;
if (!*ptr_copy) {
cmp = -1;
} else {
cmp = env_strncmp(required_vars[i].wide_eq,
required_vars[i].len,
*ptr_copy);
}
if (cmp < 0) {
/* missing required var */
var_size = GetEnvironmentVariableW(required_vars[i].wide, NULL, 0);
required_vars[i].value_len = var_size;
required_vars_value_len[i] = var_size;
if (var_size != 0) {
env_len += required_vars[i].len;
env_len += var_size;
}
i++;
} else {
ptr_copy++;
if (cmp == 0)
i++;
}
}
dst = malloc(env_len * sizeof(WCHAR));
/* final pass: copy, in sort order, and inserting required variables */
dst = malloc((1+env_len) * sizeof(WCHAR));
if (!dst) {
_freea(dst_copy);
return ERROR_OUTOFMEMORY;
}
ptr = dst;
for (env = env_block; *env; env++, ptr += len) {
len = MultiByteToWideChar(CP_UTF8,
0,
*env,
-1,
ptr,
(int) (env_len - (ptr - dst)));
if (len <= 0) {
free(dst);
return GetLastError();
for (ptr = dst, ptr_copy = env_copy, i = 0;
*ptr_copy || i < n_required_vars;
ptr += len) {
int cmp;
if (i >= n_required_vars) {
cmp = 1;
} else if (!*ptr_copy) {
cmp = -1;
} else {
cmp = env_strncmp(required_vars[i].wide_eq,
required_vars[i].len,
*ptr_copy);
}
}
for (i = 0; i < ARRAY_SIZE(required_vars); ++i) {
if (!required_vars[i].supplied && required_vars[i].value_len!=0) {
wcscpy(ptr, required_vars[i].wide);
ptr += required_vars[i].len - 1;
*ptr++ = L'=';
var_size = GetEnvironmentVariableW(required_vars[i].wide,
ptr,
required_vars[i].value_len);
if (var_size == 0) {
uv_fatal_error(GetLastError(), "GetEnvironmentVariableW");
if (cmp < 0) {
/* missing required var */
len = required_vars_value_len[i];
if (len) {
wcscpy(ptr, required_vars[i].wide_eq);
ptr += required_vars[i].len;
var_size = GetEnvironmentVariableW(required_vars[i].wide,
ptr,
(int) (env_len - (ptr - dst)));
if (var_size != len-1) { /* race condition? */
uv_fatal_error(GetLastError(), "GetEnvironmentVariableW");
}
}
ptr += required_vars[i].value_len;
i++;
} else {
/* copy var from env_block */
DWORD r;
len = wcslen(*ptr_copy) + 1;
r = wmemcpy_s(ptr, (env_len - (ptr - dst)), *ptr_copy, len);
assert(!r);
ptr_copy++;
if (cmp == 0)
i++;
}
}
/* Terminate with an extra NULL. */
assert(env_len == (ptr - dst));
*ptr = L'\0';
_freea(dst_copy);
*dst_ptr = dst;
return 0;
}

View File

@ -31,6 +31,7 @@
# include <basetyps.h>
# endif
# include <shellapi.h>
# include <wchar.h>
#else
# include <unistd.h>
#endif
@ -897,45 +898,110 @@ TEST_IMPL(environment_creation) {
"SYSTEM=ROOT", /* substring of a supplied var name */
"SYSTEMROOTED=OMG", /* supplied var name is a substring */
"TEMP=C:\\Temp",
"INVALID",
"BAZ=QUX",
"B_Z=QUX",
"B\xe2\x82\xacZ=QUX",
"B\xf0\x90\x80\x82Z=QUX",
"B\xef\xbd\xa1Z=QUX",
"B\xf0\xa3\x91\x96Z=QUX",
"BAZ", /* repeat, invalid variable */
NULL
};
WCHAR expected[512];
WCHAR* ptr = expected;
WCHAR* wenvironment[] = {
L"BAZ=QUX",
L"B_Z=QUX",
L"B\x20acZ=QUX",
L"B\xd800\xdc02Z=QUX",
L"B\xd84d\xdc56Z=QUX",
L"B\xff61Z=QUX",
L"FOO=BAR",
L"SYSTEM=ROOT", /* substring of a supplied var name */
L"SYSTEMROOTED=OMG", /* supplied var name is a substring */
L"TEMP=C:\\Temp",
};
WCHAR* from_env[] = {
/* list should be kept in sync with list
* in process.c, minus variables in wenvironment */
L"HOMEDRIVE",
L"HOMEPATH",
L"LOGONSERVER",
L"PATH",
L"USERDOMAIN",
L"USERNAME",
L"USERPROFILE",
L"SYSTEMDRIVE",
L"SYSTEMROOT",
L"WINDIR",
/* test for behavior in the absence of a
* required-environment variable: */
L"ZTHIS_ENV_VARIABLE_DOES_NOT_EXIST",
};
int found_in_loc_env[ARRAY_SIZE(wenvironment)] = {0};
int found_in_usr_env[ARRAY_SIZE(from_env)] = {0};
WCHAR *expected[ARRAY_SIZE(from_env)];
int result;
WCHAR* str;
WCHAR* prev;
WCHAR* env;
for (i = 0; i < sizeof(environment) / sizeof(environment[0]) - 1; i++) {
ptr += uv_utf8_to_utf16(environment[i],
ptr,
expected + sizeof(expected) - ptr);
for (i = 0; i < ARRAY_SIZE(from_env); i++) {
/* copy expected additions to environment locally */
size_t len = GetEnvironmentVariableW(from_env[i], NULL, 0);
if (len == 0) {
found_in_usr_env[i] = 1;
str = malloc(1 * sizeof(WCHAR));
*str = 0;
expected[i] = str;
} else {
size_t name_len = wcslen(from_env[i]);
str = malloc((name_len+1+len) * sizeof(WCHAR));
wmemcpy(str, from_env[i], name_len);
expected[i] = str;
str += name_len;
*str++ = L'=';
GetEnvironmentVariableW(from_env[i], str, len);
}
}
memcpy(ptr, L"SYSTEMROOT=", sizeof(L"SYSTEMROOT="));
ptr += sizeof(L"SYSTEMROOT=")/sizeof(WCHAR) - 1;
ptr += GetEnvironmentVariableW(L"SYSTEMROOT",
ptr,
expected + sizeof(expected) - ptr);
++ptr;
memcpy(ptr, L"SYSTEMDRIVE=", sizeof(L"SYSTEMDRIVE="));
ptr += sizeof(L"SYSTEMDRIVE=")/sizeof(WCHAR) - 1;
ptr += GetEnvironmentVariableW(L"SYSTEMDRIVE",
ptr,
expected + sizeof(expected) - ptr);
++ptr;
*ptr = '\0';
result = make_program_env(environment, &env);
ASSERT(result == 0);
for (str = env; *str; str += wcslen(str) + 1) {
wprintf(L"%s\n", str);
for (str = env, prev = NULL; *str; prev = str, str += wcslen(str) + 1) {
int found = 0;
#if 0
_cputws(str);
putchar('\n');
#endif
for (i = 0; i < ARRAY_SIZE(wenvironment) && !found; i++) {
if (!wcscmp(str, wenvironment[i])) {
ASSERT(!found_in_loc_env[i]);
found_in_loc_env[i] = 1;
found = 1;
}
}
for (i = 0; i < ARRAY_SIZE(expected) && !found; i++) {
if (!wcscmp(str, expected[i])) {
ASSERT(!found_in_usr_env[i]);
found_in_usr_env[i] = 1;
found = 1;
}
}
if (prev) { /* verify sort order -- requires Vista */
#if _WIN32_WINNT >= 0x0600
ASSERT(CompareStringOrdinal(prev, -1, str, -1, TRUE) == 1);
#endif
}
ASSERT(found); /* verify that we expected this variable */
}
ASSERT(wcscmp(expected, env) == 0);
/* verify that we found all expected variables */
for (i = 0; i < ARRAY_SIZE(wenvironment); i++) {
ASSERT(found_in_loc_env[i]);
}
for (i = 0; i < ARRAY_SIZE(expected); i++) {
ASSERT(found_in_usr_env[i]);
}
return 0;
}