blob: 7f3c8fea368a6e73e34a64cdc9d320374f440089 [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2014-2022 Google, Inc. All rights reserved.
* Copyright (c) 2005-2010 VMware, Inc. All rights reserved.
* **********************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of VMware, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
/*
*
* config.c: general configuration interface
*
*/
#include "share.h"
#include "config.h"
#include "processes.h"
#include "string.h"
#include "parser.h"
#include <stdio.h>
#ifndef UNIT_TEST
void
configpath_to_registry_path(WCHAR *path)
{
UINT i = 0;
if (NULL == path)
return;
for (i = 0; i < wcslen(path); i++)
if (CONFIG_PATH_SEPARATOR == path[i])
path[i] = L'\\';
}
DWORD
get_key_handle(HKEY *key, HKEY parent, const WCHAR *path, BOOL absolute, DWORD flags)
{
WCHAR keyname[MAX_PATH];
DO_ASSERT(key != NULL);
if (path == NULL) {
wcsncpy(keyname, CONFIGURATION_ROOT_REGISTRY_KEY, MAX_PATH);
} else if (parent == NULL && !absolute) {
_snwprintf(keyname, MAX_PATH, L"%s\\%s", CONFIGURATION_ROOT_REGISTRY_KEY, path);
} else {
wcsncpy(keyname, path, MAX_PATH);
}
DO_DEBUG(DL_VERB, printf("get_key_handle using %S as keyname\n", keyname););
configpath_to_registry_path(keyname);
return RegCreateKeyEx(parent == NULL ? DYNAMORIO_REGISTRY_HIVE : parent, keyname, 0,
NULL, REG_OPTION_NON_VOLATILE, platform_key_flags() | flags,
NULL, key, NULL);
}
ConfigGroup *
get_child(const WCHAR *name, ConfigGroup *c)
{
ConfigGroup *cur = NULL;
if (c == NULL)
return NULL;
for (cur = c->children; NULL != cur; cur = cur->next) {
if (0 == wcsicmp(name, cur->name))
return cur;
}
return NULL;
}
void
remove_child(const WCHAR *child, ConfigGroup *config)
{
ConfigGroup *cur = NULL, *prev = NULL;
if (NULL == config)
return;
for (cur = config->children; NULL != cur; cur = cur->next) {
if (0 == wcsicmp(child, cur->name)) {
if (NULL == prev)
config->children = cur->next;
else
prev->next = cur->next;
if (config->lastchild == cur)
config->lastchild = prev;
free_config_group(cur);
return;
}
prev = cur;
}
}
BOOL
count_params(ConfigGroup *c)
{
int i = 0;
NameValuePairNode *cur;
for (cur = c->params; NULL != cur; cur = cur->next)
i++;
return i;
}
BOOL
is_param(const WCHAR *name, ConfigGroup *c)
{
NameValuePairNode *cur;
if (c == NULL)
return FALSE;
for (cur = c->params; NULL != cur; cur = cur->next) {
if (0 == wcsicmp(name, cur->name))
return TRUE;
}
return FALSE;
}
ConfigGroup *
new_config_group(const WCHAR *name)
{
ConfigGroup *c = (ConfigGroup *)malloc(sizeof(ConfigGroup));
c->next = NULL;
c->children = NULL;
c->lastchild = NULL;
c->params = NULL;
c->should_clear = FALSE;
c->name = (NULL == name) ? NULL : wcsdup(name);
return c;
}
ConfigGroup *
copy_config_group(ConfigGroup *config, BOOL deep)
{
NameValuePairNode *tmpnvp;
ConfigGroup *tmpcfg;
ConfigGroup *c = new_config_group(config->name);
c->should_clear = config->should_clear;
tmpnvp = config->params;
while (tmpnvp != NULL) {
set_config_group_parameter(c, tmpnvp->name, tmpnvp->value);
tmpnvp = tmpnvp->next;
}
if (deep) {
tmpcfg = config->children;
while (tmpcfg != NULL) {
add_config_group(c, copy_config_group(tmpcfg, deep));
tmpcfg = tmpcfg->next;
}
}
return c;
}
void
remove_children(ConfigGroup *config)
{
ConfigGroup *cur = config->children, *prev;
while (NULL != cur) {
prev = cur;
cur = cur->next;
free_config_group(prev);
}
config->children = NULL;
config->lastchild = NULL;
}
NameValuePairNode *
new_nvp_node(const WCHAR *name)
{
NameValuePairNode *nvp = (NameValuePairNode *)malloc(sizeof(NameValuePairNode));
nvp->next = NULL;
nvp->name = wcsdup(name);
nvp->value = NULL;
return nvp;
}
void
free_nvp(NameValuePairNode *nvpn)
{
if (nvpn->name != NULL)
free(nvpn->name);
if (nvpn->value != NULL)
free(nvpn->value);
free(nvpn);
}
void
free_config_group(ConfigGroup *config)
{
NameValuePairNode *nvpn = config->params, *tmp;
remove_children(config);
while (nvpn != NULL) {
tmp = nvpn->next;
free_nvp(nvpn);
nvpn = tmp;
}
if (config->name != NULL)
free(config->name);
free(config);
}
NameValuePairNode *
get_nvp_node(ConfigGroup *config, const WCHAR *name)
{
NameValuePairNode *nvpn = config->params;
while (NULL != nvpn && 0 != wcsicmp(nvpn->name, name))
nvpn = nvpn->next;
return nvpn;
}
WCHAR *
get_config_group_parameter(ConfigGroup *config, const WCHAR *name)
{
NameValuePairNode *nvpn = get_nvp_node(config, name);
return (NULL == nvpn) ? NULL : nvpn->value;
}
NameValuePairNode *
add_nvp_node(ConfigGroup *config, const WCHAR *name)
{
NameValuePairNode *nvpn = new_nvp_node(name);
nvpn->next = config->params;
config->params = nvpn;
return nvpn;
}
void
set_config_group_parameter(ConfigGroup *config, const WCHAR *name, const WCHAR *value)
{
NameValuePairNode *nvpn = get_nvp_node(config, name);
if (NULL == nvpn) {
nvpn = add_nvp_node(config, name);
}
if (NULL != nvpn->value) {
free(nvpn->value);
nvpn->value = NULL;
}
nvpn->value = (NULL == value) ? NULL : wcsdup(value);
}
void
remove_config_group_parameter(ConfigGroup *config, const WCHAR *name)
{
NameValuePairNode *cur = NULL, *prev = NULL;
if (NULL == config || NULL == name)
return;
for (cur = config->params; NULL != cur; cur = cur->next) {
if (0 == wcsicmp(cur->name, name)) {
if (NULL == prev)
config->params = cur->next;
else
prev->next = cur->next;
free_nvp(cur);
return;
}
prev = cur;
}
}
BOOL
get_config_group_parameter_bool(ConfigGroup *config, const WCHAR *name)
{
WCHAR *val = get_config_group_parameter(config, name);
return (val != NULL && 0 == wcscmp(val, L"TRUE"));
}
int
get_config_group_parameter_int(ConfigGroup *config, const WCHAR *name)
{
WCHAR *val = get_config_group_parameter(config, name);
return val == NULL ? 0 : _wtoi(val);
}
void
set_config_group_parameter_bool(ConfigGroup *config, const WCHAR *name, BOOL value)
{
set_config_group_parameter(config, name, value ? L"TRUE" : L"FALSE");
}
void
set_config_group_parameter_int(ConfigGroup *config, const WCHAR *name, int value)
{
WCHAR buf[MAX_PATH];
_snwprintf(buf, MAX_PATH, L"%d", value);
set_config_group_parameter(config, name, buf);
}
void
set_config_group_parameter_ascii(ConfigGroup *config, const WCHAR *name, char *value)
{
WCHAR buf[MAX_PATH];
_snwprintf(buf, MAX_PATH, L"%S", value);
buf[MAX_PATH - 1] = L'\0';
set_config_group_parameter(config, name, buf);
}
void
set_config_group_parameter_scrambled(ConfigGroup *config, const WCHAR *name,
const WCHAR *value)
{
/* this swaps high and low bytes of WCHAR. obviously not strong,
* just meant to be something other than plaintext. */
WCHAR buf[MAX_PATH];
UINT i = 0;
while (value[i] != 0 && i < MAX_PATH - 1) {
buf[i] = (WCHAR)(((value[i] & 0x00ff) << 8) + ((value[i] & 0xff00) >> 8));
i++;
}
buf[i] = L'\0';
set_config_group_parameter(config, name, buf);
}
void
get_config_group_parameter_scrambled(ConfigGroup *config, const WCHAR *name,
WCHAR *buffer, UINT maxchars)
{
WCHAR *value = get_config_group_parameter(config, name);
UINT i = 0;
if (value == NULL) {
buffer[0] = L'\0';
return;
}
while (value[i] != 0 && i < maxchars - 1) {
buffer[i] = (WCHAR)(((value[i] & 0x00ff) << 8) + ((value[i] & 0xff00) >> 8));
i++;
}
buffer[i] = L'\0';
}
void
add_config_group(ConfigGroup *parent, ConfigGroup *new_child)
{
if (NULL != get_child(new_child->name, parent)) {
DO_DEBUG(DL_WARN, printf("adding multiple child: %S\n", new_child->name););
}
if (parent->children == NULL)
parent->children = new_child;
if (parent->lastchild != NULL)
parent->lastchild->next = new_child;
parent->lastchild = new_child;
new_child->next = NULL;
}
typedef DWORD (*custom_config_read_handler)(ConfigGroup *config, const WCHAR *name,
const WCHAR *value);
# define CURRENT_MODES_VERSION 42000
# define MAX_MODES_FILE_SIZE (64 * 1024)
DWORD
custom_hotp_modes_read_handler(ConfigGroup *config, const WCHAR *name, const WCHAR *value)
{
char modes_file[MAX_MODES_FILE_SIZE];
WCHAR modes_path[MAX_PATH];
WCHAR parambuf[MAX_PATH];
WCHAR valbuf[MAX_PATH];
DWORD res;
SIZE_T needed = 0;
BOOL done = FALSE;
char *modes_line;
ConfigGroup *hotp_config;
if (name == NULL || value == NULL)
return ERROR_SUCCESS;
/* read in modes file */
_snwprintf(modes_path, MAX_PATH, L"%s\\%d\\%S", value, CURRENT_MODES_VERSION,
HOTP_MODES_FILENAME);
modes_path[MAX_PATH - 1] = L'\0';
res = read_file_contents(modes_path, modes_file, MAX_MODES_FILE_SIZE, &needed);
/* if the modes file isn't there, no worries; assume no modes */
if (ERROR_FILE_NOT_FOUND == res)
return ERROR_SUCCESS;
if (ERROR_SUCCESS != res)
return res;
if (needed > MAX_MODES_FILE_SIZE - 2)
return ERROR_INSUFFICIENT_BUFFER;
/* create child config group */
hotp_config = new_config_group(L_DYNAMORIO_VAR_HOT_PATCH_MODES);
/* modes_file has contents, now sscanf and set all nvp. */
modes_line = parse_line_sep(modes_file, ':', &done, parambuf, valbuf, MAX_PATH);
DO_DEBUG(DL_VERB, printf("hotp modes first line %S:%S\n", parambuf, valbuf););
/* expect num lines first */
if (valbuf[0] != '\0')
return ERROR_PARSE_ERROR;
while (!done) {
modes_line = parse_line_sep(modes_line, ':', &done, parambuf, valbuf, MAX_PATH);
if (done)
break;
set_config_group_parameter(hotp_config, parambuf, valbuf);
}
add_config_group(config, hotp_config);
return ERROR_SUCCESS;
}
custom_config_read_handler
get_custom_config_read_handler(const WCHAR *name)
{
if (name == NULL)
return NULL;
if (0 == wcscmp(name, L_DYNAMORIO_VAR_HOT_PATCH_MODES)) {
return (&custom_hotp_modes_read_handler);
}
return NULL;
}
static DWORD
read_config_group_from_registry(HKEY parent, ConfigGroup **configptr, const WCHAR *name,
BOOL recursive)
{
HKEY config_key = NULL;
ConfigGroup *config = NULL;
WCHAR keyname[MAX_PATH];
WCHAR keyvalue[MAX_PARAM_LEN];
DWORD idx, keySz, valSz, res = ERROR_SUCCESS;
FILETIME writetime;
custom_config_read_handler handler;
if (name == NULL) {
config_key = parent;
} else {
WCHAR translated_name[MAX_PATH];
wcsncpy(translated_name, name, MAX_PATH);
configpath_to_registry_path(translated_name);
res = RegOpenKeyEx(parent, translated_name, 0, platform_key_flags() | KEY_READ,
&config_key);
if (res != ERROR_SUCCESS)
goto read_config_out;
}
config = new_config_group(name);
/* first read in all values */
for (idx = 0; /* TRUE */; idx++) {
keySz = MAX_PATH;
valSz = sizeof(keyvalue);
res = RegEnumValue(config_key, idx, keyname, &keySz, 0, NULL, (LPBYTE)keyvalue,
&valSz);
if (res == ERROR_NO_MORE_ITEMS) {
res = ERROR_SUCCESS;
break;
} else if (res != ERROR_SUCCESS) {
goto read_config_out;
}
if (NULL != (handler = get_custom_config_read_handler(keyname))) {
res = handler(config, keyname, keyvalue);
if (res != ERROR_SUCCESS)
goto read_config_out;
} else {
set_config_group_parameter(config, keyname, keyvalue);
}
}
/* and read in all children (if desired) */
for (idx = 0; recursive; idx++) {
ConfigGroup *child_config;
keySz = MAX_PATH;
res = RegEnumKeyEx(config_key, idx, keyname, &keySz, 0, NULL, NULL, &writetime);
if (res == ERROR_NO_MORE_ITEMS) {
res = ERROR_SUCCESS;
break;
} else if (res != ERROR_SUCCESS) {
goto read_config_out;
}
res = read_config_group_from_registry(config_key, &child_config, keyname,
recursive);
if (res != ERROR_SUCCESS)
return res;
add_config_group(config, child_config);
}
*configptr = config;
read_config_out:
if (NULL != config_key && NULL != name)
RegCloseKey(config_key);
return res;
}
DWORD
read_config_group(ConfigGroup **configptr, const WCHAR *name,
BOOL read_children_recursively)
{
HKEY rootkey;
DWORD res = ERROR_SUCCESS;
DO_ASSERT(configptr != NULL);
DO_ASSERT(name != NULL);
*configptr = NULL;
res = get_key_handle(&rootkey, NULL, NULL, FALSE, KEY_READ);
if (res != ERROR_SUCCESS)
return res;
res = read_config_group_from_registry(rootkey, configptr, name,
read_children_recursively);
RegCloseKey(rootkey);
return res;
}
typedef DWORD (*custom_config_write_handler)(HKEY parent, ConfigGroup *config,
ConfigGroup *config_parent);
DWORD
custom_hotp_modes_write_handler(HKEY parent, ConfigGroup *config,
ConfigGroup *config_parent)
{
/* need to write modes file path to key, and the modes file
* contents. */
DWORD res;
WCHAR modes_file[MAX_PATH];
WCHAR modes_key[MAX_PATH];
char modes_file_contents[MAX_MODES_FILE_SIZE];
char modes_file_line[MAX_PATH];
BOOL changed = FALSE;
NameValuePairNode *nvpn;
/* the modes file name is based on the parent ConfigGroup; if the
* parent config is the root, then the modes file goes in the
* top-level config dir, otherwise it goes in an app-specific
* config dir. */
if (0 == wcscmp(L_PRODUCT_NAME, config_parent->name)) {
_snwprintf(modes_key, MAX_PATH, L"%s\\config", get_dynamorio_home());
} else {
_snwprintf(modes_key, MAX_PATH, L"%s\\config\\%s", get_dynamorio_home(),
config_parent->name);
}
modes_key[MAX_PATH - 1] = L'\0';
/* now write modes file content; name=patch ID, value=mode */
_snprintf(modes_file_contents, MAX_MODES_FILE_SIZE, "%d\n", count_params(config));
modes_file_contents[MAX_MODES_FILE_SIZE - 1] = '\0';
for (nvpn = config->params; NULL != nvpn; nvpn = nvpn->next) {
_snprintf(modes_file_line, MAX_PATH, "%S:%S\n", nvpn->name, nvpn->value);
modes_file_line[MAX_PATH - 1] = '\0';
strncat(modes_file_contents, modes_file_line,
MAX_MODES_FILE_SIZE - strlen(modes_file_contents));
modes_file_contents[MAX_MODES_FILE_SIZE - 1] = '\0';
}
if (strlen(modes_file_contents) > MAX_MODES_FILE_SIZE - 2) {
return ERROR_INSUFFICIENT_BUFFER;
}
/* first, mkdir -p */
_snwprintf(modes_file, MAX_PATH, L"%s\\%d\\%S", modes_key, CURRENT_MODES_VERSION,
HOTP_MODES_FILENAME);
modes_file[MAX_PATH - 1] = L'\0';
ensure_directory_exists_for_file(modes_file);
/* and then write file */
res = write_file_contents_if_different(modes_file, modes_file_contents, &changed);
if (res != ERROR_SUCCESS)
return res;
/* finally, write the filename to the modes key */
return write_reg_string(parent, L_DYNAMORIO_VAR_HOT_PATCH_MODES, modes_key);
}
custom_config_write_handler
get_custom_config_write_handler(ConfigGroup *config)
{
if (config == NULL || config->name == NULL)
return NULL;
if (0 == wcscmp(config->name, L_DYNAMORIO_VAR_HOT_PATCH_MODES)) {
return (&custom_hotp_modes_write_handler);
}
return NULL;
}
DWORD
write_config_group_to_registry(HKEY parent, ConfigGroup *config,
ConfigGroup *parent_config)
{
HKEY config_key = NULL;
NameValuePairNode *nvpn;
DWORD res = ERROR_SUCCESS;
custom_config_write_handler handler = NULL;
if (NULL == config->name) {
config_key = parent;
} else if (NULL != (handler = get_custom_config_write_handler(config))) {
res = handler(parent, config, parent_config);
if (res != ERROR_SUCCESS)
goto write_config_out;
/* only continue down the chain if custom-handled */
if (NULL != config->next) {
res = write_config_group_to_registry(parent, config->next, parent_config);
}
goto write_config_out;
} else {
/* open or create */
res = get_key_handle(&config_key, parent, config->name, FALSE,
KEY_WRITE | KEY_ENUMERATE_SUB_KEYS);
if (res != ERROR_SUCCESS)
goto write_config_out;
}
/* write the NVP's */
for (nvpn = config->params; NULL != nvpn; nvpn = nvpn->next) {
/* hook for forced deletion of a key */
if (0 == wcscmp(nvpn->value, L_DELETE_PARAMETER_KEY)) {
RegDeleteValue(config_key, nvpn->name);
res = ERROR_SUCCESS;
} else {
res = write_reg_string(config_key, nvpn->name, nvpn->value);
}
if (res != ERROR_SUCCESS)
goto write_config_out;
}
/* write the children */
if (NULL != config->children) {
res = write_config_group_to_registry(config_key, config->children, config);
if (res != ERROR_SUCCESS)
goto write_config_out;
}
/* continue down the chain */
if (NULL != config->next) {
res = write_config_group_to_registry(parent, config->next, parent_config);
if (res != ERROR_SUCCESS)
goto write_config_out;
}
write_config_out:
if (NULL != config_key && NULL != config->name)
RegCloseKey(config_key);
return res;
}
/*
* this enforces that the key tree matches the config group exactly,
* e.g., all keys/values are deleted that do not appear in 'filter'
*
* this complexity is necessary in order to allow
* write_config_group_to_registry to be atomic, in the sense that
* config information is updated on a key-by-key atomic basis.
*/
DWORD
recursive_delete_key(HKEY parent, const WCHAR *keyname, ConfigGroup *filter)
{
HKEY subkey = NULL;
WCHAR subkeyname[MAX_PATH];
FILETIME writetime;
DWORD res, keySz, idx;
ConfigGroup *child;
res = get_key_handle(&subkey, parent, keyname, TRUE, KEY_WRITE | KEY_READ);
if (res != ERROR_SUCCESS)
goto recursive_delete_out;
/* and read in all children (if desired) */
for (idx = 0;;) {
/* note that since deleting, we always pass index 0 */
keySz = MAX_PATH;
res = RegEnumKeyEx(subkey, idx, subkeyname, &keySz, 0, NULL, NULL, &writetime);
if (res == ERROR_NO_MORE_ITEMS) {
res = ERROR_SUCCESS;
break;
} else if (res != ERROR_SUCCESS) {
goto recursive_delete_out;
}
/* note that recursive_delete_key will only delete the entire
* subkey if it's not a child (eg, if the 'filter' parameter
* is NULL) */
child = get_child(subkeyname, filter);
res = recursive_delete_key(subkey, subkeyname, child);
if (res != ERROR_SUCCESS)
goto recursive_delete_out;
/* increment the index if we're not deleting the key */
if (NULL != child)
idx++;
}
if (NULL != filter) {
/* prune values too */
for (idx = 0;;) {
keySz = MAX_PATH;
res = RegEnumValue(subkey, idx, subkeyname, &keySz, 0, NULL, NULL, NULL);
if (res == ERROR_NO_MORE_ITEMS) {
res = ERROR_SUCCESS;
break;
} else if (res != ERROR_SUCCESS) {
goto recursive_delete_out;
}
/* don't delete if the param exists, OR if this is a custom
* handled name and the child exists. */
if (is_param(subkeyname, filter) ||
(get_child(subkeyname, filter) &&
get_custom_config_read_handler(subkeyname))) {
/* if we're not deleting, increment the index */
idx++;
} else {
res = RegDeleteValue(subkey, subkeyname);
if (res != ERROR_SUCCESS)
goto recursive_delete_out;
}
}
}
RegCloseKey(subkey);
subkey = NULL;
/* only delete the key itself if it's not being filtered out. */
if (filter == NULL) {
/* PR 244206: we assume we're only going to recursively delete keys in
* HKLM\Software\Company
*/
res = delete_product_key(parent, keyname);
}
recursive_delete_out:
if (subkey != NULL)
RegCloseKey(subkey);
return res;
}
DWORD
write_config_group(ConfigGroup *config)
{
HKEY rootkey;
DWORD res;
res = get_key_handle(&rootkey, NULL, NULL, FALSE, KEY_WRITE | KEY_ENUMERATE_SUB_KEYS);
if (res != ERROR_SUCCESS)
return res;
res = write_config_group_to_registry(rootkey, config, NULL);
/* prune if necessary */
if (ERROR_SUCCESS == res && config->should_clear) {
res = recursive_delete_key(rootkey, config->name, config);
}
RegCloseKey(rootkey);
return res;
}
/* single parameter config functions */
DWORD
set_config_parameter(const WCHAR *path, BOOL absolute, const WCHAR *name,
const WCHAR *value)
{
HKEY rootkey;
DWORD res;
res = get_key_handle(&rootkey, NULL, path, absolute, KEY_WRITE);
if (res != ERROR_SUCCESS)
return res;
res = write_reg_string(rootkey, name, value);
RegCloseKey(rootkey);
return res;
}
DWORD
get_config_parameter(const WCHAR *path, BOOL absolute, const WCHAR *name, WCHAR *value,
int maxlen)
{
HKEY rootkey;
DWORD res;
res = get_key_handle(&rootkey, NULL, path, absolute, KEY_READ);
if (res != ERROR_SUCCESS)
return res;
res = read_reg_string(rootkey, name, value, maxlen);
RegCloseKey(rootkey);
return res;
}
DWORD
read_reg_string(HKEY subkey, const WCHAR *keyname, WCHAR *value, int valchars)
{
DWORD len = valchars * sizeof(WCHAR);
return RegQueryValueEx(subkey, keyname, 0, NULL, (LPBYTE)value, &len);
}
/* if value is NULL, the key will be deleted */
DWORD
write_reg_string(HKEY subkey, const WCHAR *keyname, const WCHAR *value)
{
if (value)
return RegSetValueEx(subkey, keyname, 0, REG_SZ, (LPBYTE)value,
(DWORD)(wcslen(value) + 1) * sizeof(WCHAR));
else
return RegDeleteValue(subkey, keyname);
}
/* process identification routines */
/* tries both with and without no_strip */
ConfigGroup *
get_qualified_config_group(ConfigGroup *config, const WCHAR *exename,
const WCHAR *cmdline)
{
WCHAR qname[MAX_PATH];
ConfigGroup *c;
/* need to try both with and without NO_STRIP! */
_snwprintf(qname, MAX_PATH, L"%s-", exename);
if (get_commandline_qualifier(cmdline, qname + wcslen(qname),
(UINT)(MAX_PATH - wcslen(qname)), FALSE)) {
c = get_child(qname, config);
if (NULL != c)
return c;
}
_snwprintf(qname, MAX_PATH, L"%s-", exename);
if (get_commandline_qualifier(cmdline, qname + wcslen(qname),
(UINT)(MAX_PATH - wcslen(qname)), TRUE)) {
c = get_child(qname, config);
if (NULL != c)
return c;
}
return NULL;
}
BOOL
is_parent_of_qualified_config_group(ConfigGroup *config)
{
WCHAR *run_under = NULL;
if (config == NULL)
return FALSE;
run_under = get_config_group_parameter(config, L_DYNAMORIO_VAR_RUNUNDER);
if (run_under == NULL)
return FALSE;
return TEST(RUNUNDER_COMMANDLINE_DISPATCH, _wtoi(run_under));
}
ConfigGroup *
get_process_config_group(ConfigGroup *config, process_id_t pid)
{
WCHAR buf[MAX_PATH];
DWORD res;
ConfigGroup *c;
res = get_process_name(pid, buf, MAX_PATH);
if (res != ERROR_SUCCESS)
return NULL;
wcstolower(buf);
c = get_child(buf, config);
if (c == NULL || is_parent_of_qualified_config_group(c)) {
WCHAR cmdlbuf[MAX_PATH];
ConfigGroup *qualified;
get_process_cmdline(pid, cmdlbuf, MAX_PATH);
qualified = get_qualified_config_group(config, buf, cmdlbuf);
if (NULL != qualified)
return qualified;
}
return c;
}
/******************
* list functions *
******************/
/*
* given a ;-separated list and a filename, return a pointer to
* the filename in the list, if it appears. comparisons are
* case insensitive and independent of path; eg,
* get_entry_location("c:\\foo\\bar.dll;blah;...", "D:\\Bar.DLL")
* would return a pointer to the beginning of the list.
*/
WCHAR *
get_entry_location(const WCHAR *list, const WCHAR *filename, WCHAR separator)
{
WCHAR *lowerlist, *lowername, *entry;
wcstolower(lowerlist = wcsdup(list));
wcstolower(lowername = wcsdup(get_exename_from_path(filename)));
entry = wcsstr(lowerlist, lowername);
while (NULL != entry) {
WCHAR last = *(entry + wcslen(lowername));
/* make sure it's not just a substring */
if ((last == separator || last == L'\0') &&
(entry == lowerlist || *(entry - 1) == separator || *(entry - 1) == L'\\' ||
*(entry - 1) == L'/')) {
/* everything's cool; now put the path back in and translate */
WCHAR *cur = lowerlist;
while (NULL != wcschr(cur, separator) && wcschr(cur, separator) < entry)
cur = wcschr(cur, separator) + 1;
entry = ((WCHAR *)list) + (cur - lowerlist);
break;
} else {
entry = wcsstr(entry + 1, lowername);
}
}
free(lowername);
free(lowerlist);
return entry;
}
BOOL
is_in_file_list(const WCHAR *list, const WCHAR *filename, WCHAR separator)
{
return NULL != get_entry_location(list, filename, separator);
}
/* returns a new list which needs to be freed, and frees old list */
WCHAR *
add_to_file_list(WCHAR *list, const WCHAR *filename, BOOL check_for_duplicates,
BOOL add_to_front, BOOL overwrite_existing, WCHAR separator)
{
WCHAR *new_list;
SIZE_T new_list_size;
if (NULL == list || 0 == wcslen(list))
return wcsdup(filename);
if (overwrite_existing)
remove_from_file_list(list, filename, separator);
if (check_for_duplicates && is_in_file_list(list, filename, separator))
return list;
new_list_size = wcslen(list) + wcslen(filename) + 2;
new_list = (WCHAR *)malloc(sizeof(WCHAR) * new_list_size);
if (wcslen(list) == 0) {
wcsncpy(new_list, filename, new_list_size);
} else {
_snwprintf(new_list, new_list_size, L"%s%c%s", add_to_front ? filename : list,
separator, add_to_front ? list : filename);
}
free(list);
return new_list;
}
/* provide these helpers to avoid multiple libc problems */
WCHAR *
new_file_list(SIZE_T initial_chars)
{
WCHAR *list = (WCHAR *)malloc(sizeof(WCHAR) * (1 + initial_chars));
*list = L'\0';
return list;
}
void
free_file_list(WCHAR *list)
{
free(list);
}
/* removes all occurrences */
void
remove_from_file_list(WCHAR *list, const WCHAR *filename, WCHAR separator)
{
WCHAR *entry, *end_of_entry;
entry = get_entry_location(list, filename, separator);
if (NULL == entry)
return;
end_of_entry = wcschr(entry, separator);
if (NULL == end_of_entry) {
if (entry == list)
*entry = L'\0';
else
*(entry - 1) = L'\0';
} else {
/* note the whole wcslen(end_of_entry) to get the null
* terminator (since we're doing end_of_entry + 1)
*
* note that memmove handles appropriately the case
* where the src and dest regions overlap. */
memmove(entry, end_of_entry + 1, (wcslen(end_of_entry)) * sizeof(WCHAR));
}
/* recurse for multiple occurrences */
remove_from_file_list(list, filename, separator);
}
# define BLACK 0
# define WHITE 1
BOOL
filter(WCHAR *list, const WCHAR *filter, DWORD black_or_white, BOOL check_only,
WCHAR separator)
{
WCHAR *working_list, *cur, *next;
BOOL satisfied = TRUE, remove_entry;
if (list == NULL || wcslen(list) == 0)
return TRUE;
next = cur = working_list = wcsdup(list);
do {
if (NULL != next)
next = wcschr(cur, separator);
if (NULL != next)
*next = L'\0';
cur = get_exename_from_path(cur);
remove_entry =
!(is_in_file_list(filter, cur, separator) ^ (BLACK == black_or_white));
if (remove_entry) {
if (check_only)
return FALSE;
else
remove_from_file_list(list, cur, separator);
satisfied = FALSE;
}
cur = next + 1;
} while (NULL != next);
free(working_list);
return satisfied;
}
BOOL
blocklist_filter(WCHAR *list, const WCHAR *blocklist, BOOL check_only, WCHAR separator)
{
return filter(list, blocklist, BLACK, check_only, separator);
}
BOOL
allowlist_filter(WCHAR *list, const WCHAR *allowlist, BOOL check_only, WCHAR separator)
{
return filter(list, allowlist, WHITE, check_only, separator);
}
/* appinit key */
DWORD
set_autoinjection_ex(BOOL inject, DWORD flags, const WCHAR *blocklist,
const WCHAR *allowlist,
/* OUT */ DWORD *list_error, const WCHAR *custom_preinject_name,
/* OUT */ WCHAR *current_list, SIZE_T maxchars)
{
WCHAR curlist[MAX_PARAM_LEN];
WCHAR preinject_name[MAX_PATH];
WCHAR *list;
DWORD res;
BOOL list_ok_ = TRUE;
BOOL using_system32 = using_system32_for_preinject(custom_preinject_name);
res = get_config_parameter(INJECT_ALL_KEY_L, TRUE, INJECT_ALL_SUBKEY_L, curlist,
MAX_PARAM_LEN);
if (res != ERROR_SUCCESS) {
/* if it's not there, then we create it */
curlist[0] = L'\0';
}
list = new_file_list(wcslen(curlist) + 1);
wcsncpy(list, curlist, wcslen(curlist) + 1);
if (NULL != current_list)
wcsncpy(current_list, curlist, maxchars);
if (using_system32 && TEST(APPINIT_SYS32_CLEAR_OTHERS, flags)) {
/* if we're using system32, and the clear flag is set, clear it */
curlist[0] = L'\0';
}
if (NULL == custom_preinject_name) {
res = get_preinject_name(preinject_name, MAX_PATH);
if (res != ERROR_SUCCESS)
return res;
} else {
wcsncpy(preinject_name, custom_preinject_name, MAX_PATH);
}
/* if using system32, make sure to copy the DLL there from
* the standard place (or remove it if we're turning off) */
if (using_system32) {
WCHAR src_path[MAX_PATH], dst_path[MAX_PATH];
if (NULL == custom_preinject_name) {
res = get_preinject_path(src_path, MAX_PATH, TRUE, TRUE);
if (res != ERROR_SUCCESS)
return res;
wcsncat(src_path, L"\\" L_EXPAND_LEVEL(INJECT_DLL_NAME),
MAX_PATH - wcslen(src_path));
} else {
wcsncpy(src_path, custom_preinject_name, MAX_PATH);
}
get_preinject_path(dst_path, MAX_PATH, FALSE, TRUE);
if (res != ERROR_SUCCESS)
return res;
wcsncat(dst_path, L"\\" L_EXPAND_LEVEL(INJECT_DLL_NAME),
MAX_PATH - wcslen(dst_path));
if (inject) {
/* We used to only copy if (!file_exists(dst_path))
* but it seems that we should clobber to avoid
* upgrade issues, at risk of messing up another product
* w/ a dll of the same name.
*/
CopyFile(src_path, dst_path, FALSE /*don't fail if exists*/);
} else {
/* FIXME: do we want to use delete_file_rename_in_use?
* this should only be called at uninstall or by
* tools users, so there shouldn't be any problem
* leaving one .tmp file around...
* alternatively, we could try renaming to a path in
* our installation directory, which would be
* wiped out on installation. however, it gets
* complicated if our installation folder is on
* a different volume.
* another option is to rename to %SYSTEM32%\..\TEMP,
* which should always exist i think...
*/
DeleteFile(dst_path);
}
/* now we want just the name, since in system32 */
if (get_exename_from_path(preinject_name) != NULL) {
wcsncpy(preinject_name, get_exename_from_path(preinject_name),
BUFFER_SIZE_ELEMENTS(preinject_name));
}
}
if (inject) {
BOOL force_overwrite;
WCHAR *old = get_entry_location(list, preinject_name, APPINIT_SEPARATOR_CHAR);
/* first, if there's something there, make sure it exists. if
* not, remove it before proceeding (to ensure overwrite)
* cf case 4053 */
if (NULL != old) {
if (using_system32) {
/* PR 232765: we want to replace any full path w/ filename */
force_overwrite = true;
} else {
WCHAR *firstsep, tmpbuf[MAX_PATH];
wcsncpy(tmpbuf, old, MAX_PATH);
firstsep = wcschr(tmpbuf, APPINIT_SEPARATOR_CHAR);
if (NULL != firstsep)
*firstsep = L'\0';
force_overwrite = !file_exists(tmpbuf);
}
} else {
/* force overwrite if someone cared enough to set one of these */
force_overwrite =
TEST((APPINIT_FORCE_TO_FRONT | APPINIT_FORCE_TO_BACK), flags);
}
/* always overwrite if asked to */
force_overwrite = force_overwrite | TEST(APPINIT_OVERWRITE, flags);
/* add !TEST(APPINIT_FORCE_TO_BACK, flags) so that we favor adding
* to the front if neither are set. */
list = add_to_file_list(list, preinject_name, TRUE,
TEST(APPINIT_FORCE_TO_FRONT, flags) ||
!TEST(APPINIT_FORCE_TO_BACK, flags),
force_overwrite, APPINIT_SEPARATOR_CHAR);
} else {
remove_from_file_list(list, preinject_name, APPINIT_SEPARATOR_CHAR);
}
if (TEST(APPINIT_USE_ALLOWLIST, flags)) {
if (NULL == allowlist)
res = ERROR_INVALID_PARAMETER;
else
list_ok_ =
allowlist_filter(list, allowlist, TEST(APPINIT_CHECK_LISTS_ONLY, flags),
APPINIT_SEPARATOR_CHAR);
} else if (TEST(APPINIT_USE_BLOCKLIST, flags)) {
/* else if since allowlist subsumes blocklist */
if (NULL == blocklist)
res = ERROR_INVALID_PARAMETER;
else
list_ok_ =
blocklist_filter(list, blocklist, TEST(APPINIT_CHECK_LISTS_ONLY, flags),
APPINIT_SEPARATOR_CHAR);
}
if (list_error != NULL && !list_ok_)
*list_error = ERROR_LIST_VIOLATION;
if (res == ERROR_INVALID_PARAMETER)
return res;
if (!list_ok_ && TEST(APPINIT_BAIL_ON_LIST_VIOLATION, flags))
remove_from_file_list(list, preinject_name, APPINIT_SEPARATOR_CHAR);
/* now, system32 flag checks */
if (using_system32) {
/* not yet supported */
if (TEST(APPINIT_SYS32_USE_LENGTH_WORKAROUND, flags))
return ERROR_INVALID_PARAMETER;
if (wcslen(list) > APPINIT_SYSTEM32_LENGTH_LIMIT) {
if (list_error != NULL)
*list_error = ERROR_LENGTH_VIOLATION;
if (TEST(APPINIT_SYS32_FAIL_ON_LENGTH_ERROR, flags)) {
remove_from_file_list(list, preinject_name, APPINIT_SEPARATOR_CHAR);
} else {
/* truncate, if the flags specify it. */
if (TEST(APPINIT_SYS32_TRUNCATE, flags)) {
list[APPINIT_SYSTEM32_LENGTH_LIMIT] = L'\0';
}
}
}
}
/* only write if it's changed */
if (0 != wcscmp(list, curlist)) {
res = set_config_parameter(INJECT_ALL_KEY_L, TRUE, INJECT_ALL_SUBKEY_L, list);
if (res != ERROR_SUCCESS)
return res;
}
free_file_list(list);
return ERROR_SUCCESS;
}
DWORD
set_custom_autoinjection(const WCHAR *preinject, DWORD flags)
{
return set_autoinjection_ex(TRUE, flags, NULL, NULL, NULL, preinject, NULL, 0);
}
DWORD
set_autoinjection()
{
return set_autoinjection_ex(TRUE, 0, NULL, NULL, NULL, NULL, NULL, 0);
}
DWORD
unset_custom_autoinjection(const WCHAR *preinject, DWORD flags)
{
return set_autoinjection_ex(FALSE, flags, NULL, NULL, NULL, preinject, NULL, 0);
}
DWORD
unset_autoinjection()
{
return set_autoinjection_ex(FALSE, 0, NULL, NULL, NULL, NULL, NULL, 0);
}
/* NOTE: that this returns the current status -- which on NT
* is not necessarily the actual status, which is cached by the
* os at boot time.
* FIXME: add a helper method for determining the status of
* appinit that is being used for the current boot session. */
BOOL
is_autoinjection_set()
{
WCHAR list[MAX_PARAM_LEN], preinject[MAX_PATH];
DWORD res;
res = get_config_parameter(INJECT_ALL_KEY_L, TRUE, INJECT_ALL_SUBKEY_L, list,
MAX_PARAM_LEN);
if (res != ERROR_SUCCESS)
return FALSE;
res = get_preinject_name(preinject, MAX_PATH);
if (res != ERROR_SUCCESS)
return FALSE;
return is_in_file_list(list, preinject, APPINIT_SEPARATOR_CHAR);
}
BOOL
is_custom_autoinjection_set(const WCHAR *preinject)
{
WCHAR list[MAX_PARAM_LEN];
DWORD res;
res = get_config_parameter(INJECT_ALL_KEY_L, TRUE, INJECT_ALL_SUBKEY_L, list,
MAX_PARAM_LEN);
if (res != ERROR_SUCCESS)
return FALSE;
return is_in_file_list(list, preinject, APPINIT_SEPARATOR_CHAR);
}
/* Returns true for Vista or later, including Windows 7 */
BOOL
is_vista()
{
DWORD platform = 0;
get_platform(&platform);
return (platform >= PLATFORM_VISTA);
}
BOOL
is_win7()
{
DWORD platform = 0;
get_platform(&platform);
return (platform >= PLATFORM_WIN_7);
}
/* Also disables reqt for signature on lib for win7+ */
DWORD
set_loadappinit_value(DWORD value)
{
HKEY rootkey;
DWORD res;
if (!is_vista())
return ERROR_UNSUPPORTED_OS;
res = get_key_handle(&rootkey, INJECT_ALL_HIVE, INJECT_ALL_KEY_L, TRUE, KEY_WRITE);
if (res != ERROR_SUCCESS)
return res;
res = RegSetValueEx(rootkey, INJECT_ALL_LOAD_SUBKEY_L, 0, REG_DWORD, (LPBYTE)&value,
sizeof(value));
if (res != ERROR_SUCCESS)
return res;
if (is_win7()) {
/* Disable the reqt for a signature.
* FIXME i#323: better to sign drpreinject so we don't have to
* relax security!
*/
DWORD disable = 0;
res = RegSetValueEx(rootkey, INJECT_ALL_SIGN_SUBKEY_L, 0, REG_DWORD,
(LPBYTE)&disable, sizeof(disable));
}
return res;
}
DWORD
set_loadappinit()
{
return set_loadappinit_value(1);
}
DWORD
unset_loadappinit()
{
return set_loadappinit_value(0);
}
BOOL
is_loadappinit_set()
{
HKEY rootkey;
DWORD value;
DWORD size = sizeof(value);
DWORD res;
res = get_key_handle(&rootkey, INJECT_ALL_HIVE, INJECT_ALL_KEY_L, TRUE, KEY_READ);
if (res != ERROR_SUCCESS)
return res;
res = RegQueryValueEx(rootkey, INJECT_ALL_LOAD_SUBKEY_L, NULL, NULL, (LPBYTE)&value,
&size);
return (res == ERROR_SUCCESS && value != 0);
}
/* Deletes the product eventlog. If no one else (nodemgr/pe) is using the eventlog also
* deletes the base key and the eventlog file. */
DWORD
destroy_eventlog()
{
DWORD res, res2;
/* PR 244206: we don't need the wow64 flag since not HKLM\Software
* (if we do want it, need to add on create/open as well, including
* elm.c, which requires linking w/ utils.c)
*/
res = RegDeleteKey(EVENTLOG_HIVE, L_EVENT_SOURCE_SUBKEY);
res2 = RegDeleteKey(EVENTLOG_HIVE, L_EVENT_LOG_SUBKEY);
if (res2 == ERROR_SUCCESS) {
/* we deleted the top level key (which means it had no subkeys left) which means
* no one else is using our eventlog, we go ahead and free the file. */
WCHAR *file = is_vista() ? L_EVENT_FILE_NAME_VISTA : L_EVENT_FILE_NAME_PRE_VISTA;
WCHAR *truncate_start;
WCHAR file_exp[MAX_PATH];
ExpandEnvironmentStrings(file, file_exp, BUFFER_SIZE_ELEMENTS(file_exp));
NULL_TERMINATE_BUFFER(file_exp);
if (file_exists(file_exp)) {
/* If in use by the eventlog can't delete or re-name */
if (!DeleteFile(file_exp))
delete_file_on_boot(file_exp);
}
/* It appears the generated file is usually truncated to 8.3 by the eventlog
* program (though apparently not always since some of my machines also have
* the full name). Will try to delete the truncated to 8.3 versions. */
truncate_start = wcsrchr(file_exp, L'\\');
if (truncate_start != NULL) {
WCHAR *type = wcsrchr(file_exp, L'.');
truncate_start += 8;
if (type != NULL && truncate_start < type) {
WCHAR tmp_buf[MAX_PATH];
wcsncpy(tmp_buf, type, BUFFER_SIZE_ELEMENTS(tmp_buf));
NULL_TERMINATE_BUFFER(tmp_buf);
wcsncpy(truncate_start, tmp_buf,
BUFFER_SIZE_ELEMENTS(file_exp) - (truncate_start - file_exp));
NULL_TERMINATE_BUFFER(file_exp);
/* If in use by the eventlog can't delete or re-name */
if (!DeleteFile(file_exp))
delete_file_on_boot(file_exp);
}
}
}
return res;
}
DWORD
create_eventlog(const WCHAR *dll_path)
{
HKEY eventlog_key;
HKEY eventsrc_key;
WCHAR *wvalue;
DWORD dvalue;
DWORD res;
res =
get_key_handle(&eventlog_key, EVENTLOG_HIVE, L_EVENT_LOG_SUBKEY, TRUE, KEY_WRITE);
if (res != ERROR_SUCCESS)
return res;
if (is_vista()) {
wvalue = L_EVENT_FILE_NAME_VISTA;
} else {
wvalue = L_EVENT_FILE_NAME_PRE_VISTA;
}
/* REG_EXPAND_SZ since we use %systemroot% in the path which needs
* to be expanded */
res = RegSetValueEx(eventlog_key, L_EVENT_FILE_VALUE_NAME, 0, REG_EXPAND_SZ,
(LPBYTE)wvalue, (DWORD)(wcslen(wvalue) + 1) * sizeof(WCHAR));
if (res != ERROR_SUCCESS)
return res;
dvalue = EVENT_MAX_SIZE;
res = RegSetValueEx(eventlog_key, L_EVENT_MAX_SIZE_NAME, 0, REG_DWORD,
(LPBYTE)&dvalue, sizeof(dvalue));
if (res != ERROR_SUCCESS)
return res;
dvalue = EVENT_RETENTION;
res = RegSetValueEx(eventlog_key, L_EVENT_RETENTION_NAME, 0, REG_DWORD,
(LPBYTE)&dvalue, sizeof(dvalue));
if (res != ERROR_SUCCESS)
return res;
res = get_key_handle(&eventsrc_key, EVENTLOG_HIVE, L_EVENT_SOURCE_SUBKEY, TRUE,
KEY_WRITE);
if (res != ERROR_SUCCESS)
return res;
dvalue = EVENT_TYPES_SUPPORTED;
res = RegSetValueEx(eventsrc_key, L_EVENT_TYPES_SUPPORTED_NAME, 0, REG_DWORD,
(LPBYTE)&dvalue, sizeof(dvalue));
if (res != ERROR_SUCCESS)
return res;
dvalue = EVENT_CATEGORY_COUNT;
res = RegSetValueEx(eventsrc_key, L_EVENT_CATEGORY_COUNT_NAME, 0, REG_DWORD,
(LPBYTE)&dvalue, sizeof(dvalue));
if (res != ERROR_SUCCESS)
return res;
res = RegSetValueEx(eventsrc_key, L_EVENT_CATEGORY_FILE_NAME, 0, REG_SZ,
(LPBYTE)dll_path, (DWORD)(wcslen(dll_path) + 1) * sizeof(WCHAR));
if (res != ERROR_SUCCESS)
return res;
res = RegSetValueEx(eventsrc_key, L_EVENT_MESSAGE_FILE, 0, REG_SZ, (LPBYTE)dll_path,
(DWORD)(wcslen(dll_path) + 1) * sizeof(WCHAR));
if (res != ERROR_SUCCESS)
return res;
return ERROR_SUCCESS;
}
/* Copies the two drearlyhelper?.dlls located in "dir" to system32.
* They are only needed on win2k, but it's up to the caller to check
* for that.
*/
DWORD
copy_earlyhelper_dlls(const WCHAR *dir)
{
WCHAR src_path[MAX_PATH], dst_path[MAX_PATH];
DWORD len;
/* We copy drearlyhelp2.dll first, to be on the safe side.
* drearlyhelp1.dll has a dependency on drearlyhelp2.dll, so if #1 exists
* and number 2 doesn't the loader will raise an error (and we're pre-image
* entry point so it becomes a "process failed to initialize" error).
*/
_snwprintf(src_path, BUFFER_SIZE_ELEMENTS(src_path), L"%s\\%s", dir,
L_EXPAND_LEVEL(INJECT_HELPER_DLL2_NAME));
NULL_TERMINATE_BUFFER(src_path);
/* Get system32 path */
len = GetSystemDirectory(dst_path, MAX_PATH);
if (len == 0)
return GetLastError();
wcsncat(dst_path, L"\\" L_EXPAND_LEVEL(INJECT_HELPER_DLL2_NAME),
BUFFER_SIZE_ELEMENTS(dst_path) - wcslen(dst_path));
/* We could check file_exists(dst_path) but better to just
* clobber so we can upgrade nicely (at risk of clobbering
* some other product's same-name dll)
*/
if (!CopyFile(src_path, dst_path, FALSE /*don't fail if exists*/))
return GetLastError();
_snwprintf(src_path, BUFFER_SIZE_ELEMENTS(src_path), L"%s\\%s", dir,
L_EXPAND_LEVEL(INJECT_HELPER_DLL1_NAME));
NULL_TERMINATE_BUFFER(src_path);
/* Get system32 path, again: we could cache it */
len = GetSystemDirectory(dst_path, MAX_PATH);
if (len == 0)
return GetLastError();
wcsncat(dst_path, L"\\" L_EXPAND_LEVEL(INJECT_HELPER_DLL1_NAME),
BUFFER_SIZE_ELEMENTS(dst_path) - wcslen(dst_path));
if (!CopyFile(src_path, dst_path, FALSE /*don't fail if exists*/))
return GetLastError();
/* FIXME PR 232738: add a param for removing the files */
return ERROR_SUCCESS;
}
void
dump_nvp(NameValuePairNode *nvpn)
{
printf("%S=%S", nvpn->name, nvpn->value == NULL ? L"<null>" : nvpn->value);
}
void
dump_config_group(char *prefix, char *incr, ConfigGroup *c, BOOL traverse)
{
NameValuePairNode *nvpn = NULL;
printf("%sConfig Group: %S\n", prefix, c->name == NULL ? L"<null>" : c->name);
printf("%sshould_clear: %d\n", prefix, c->should_clear);
printf("%sparams:\n", prefix);
for (nvpn = c->params; NULL != nvpn; nvpn = nvpn->next) {
printf("%s%s%s", prefix, incr, incr);
dump_nvp(nvpn);
printf("\n");
}
if (c->children != NULL) {
char p2[MAX_PATH];
_snprintf(p2, MAX_PATH, "%s%s", prefix, incr);
dump_config_group(p2, incr, c->children, TRUE);
}
if (traverse && c->next != NULL) {
dump_config_group(prefix, incr, c->next, TRUE);
}
}
#else // ifdef UNIT_TEST
int
main()
{
ConfigGroup *c, *c2, *c3, *c4, *cp;
DWORD res;
WCHAR sep = LIST_SEPARATOR_CHAR;
WCHAR *list1, *list2, *tmplist;
WCHAR buf[MAX_PARAM_LEN];
set_debuglevel(DL_INFO);
set_abortlevel(DL_WARN);
list1 = new_file_list(16);
/* basic list operations */
{
DO_ASSERT_WSTR_EQ(L"", list1);
list1 = add_to_file_list(list1, L"c:\\foo.dll", TRUE, TRUE, FALSE, sep);
DO_ASSERT_WSTR_EQ(L"c:\\foo.dll", list1);
list1 = add_to_file_list(list1, L"C:\\bar.dll", TRUE, TRUE, FALSE, sep);
DO_ASSERT_WSTR_EQ(L"C:\\bar.dll;c:\\foo.dll", list1);
list1 = add_to_file_list(list1, L"foo.dll", TRUE, TRUE, FALSE, sep);
DO_ASSERT_WSTR_EQ(L"C:\\bar.dll;c:\\foo.dll", list1);
list1 = add_to_file_list(list1, L"C:\\shr\\Foo.dll", TRUE, TRUE, FALSE, sep);
DO_ASSERT_WSTR_EQ(L"C:\\bar.dll;c:\\foo.dll", list1);
list1 = add_to_file_list(list1, L"C:\\shr\\Foo.dll", FALSE, TRUE, FALSE, sep);
DO_ASSERT_WSTR_EQ(L"C:\\shr\\Foo.dll;C:\\bar.dll;c:\\foo.dll", list1);
list1 = add_to_file_list(list1, L"d:\\gee.dll", TRUE, FALSE, FALSE, sep);
DO_ASSERT_WSTR_EQ(L"C:\\shr\\Foo.dll;C:\\bar.dll;c:\\foo.dll;d:\\gee.dll", list1);
list1 = add_to_file_list(list1, L"ar.dll", TRUE, FALSE, FALSE, sep);
DO_ASSERT_WSTR_EQ(L"C:\\shr\\Foo.dll;C:\\bar.dll;c:\\foo.dll;d:\\gee.dll;ar.dll",
list1);
}
/* used in tests below */
DO_ASSERT_WSTR_EQ(L"C:\\shr\\Foo.dll;C:\\bar.dll;c:\\foo.dll;d:\\gee.dll;ar.dll",
list1);
/* remove */
{
tmplist = wcsdup(list1);
remove_from_file_list(tmplist, L"ar.dll", sep);
DO_ASSERT_WSTR_EQ(L"C:\\shr\\Foo.dll;C:\\bar.dll;c:\\foo.dll;d:\\gee.dll",
tmplist);
remove_from_file_list(tmplist, L"foo.dll", sep);
DO_ASSERT_WSTR_EQ(L"C:\\bar.dll;d:\\gee.dll", tmplist);
free_file_list(tmplist);
tmplist = wcsdup(list1);
remove_from_file_list(tmplist, L"Ar.dll", sep);
DO_ASSERT_WSTR_EQ(L"C:\\shr\\Foo.dll;C:\\bar.dll;c:\\foo.dll;d:\\gee.dll",
tmplist);
remove_from_file_list(tmplist, L"Foo.DLL", sep);
DO_ASSERT_WSTR_EQ(L"C:\\bar.dll;d:\\gee.dll", tmplist);
free_file_list(tmplist);
}
/* path checking */
{
tmplist = wcsdup(list1);
tmplist = add_to_file_list(tmplist, L"C:\\Program Files\\blah\\blah\\foo.dll",
TRUE, TRUE, TRUE, sep);
DO_ASSERT_WSTR_EQ(
L"C:\\Program Files\\blah\\blah\\foo.dll;C:\\bar.dll;d:\\gee.dll;ar.dll",
tmplist);
free_file_list(tmplist);
}
/* list filters */
{
list2 = new_file_list(1);
list2 = add_to_file_list(list2, L"foo.dll", TRUE, TRUE, FALSE, sep);
list2 = add_to_file_list(list2, L"gee.dll", TRUE, TRUE, FALSE, sep);
tmplist = wcsdup(list1);
DO_ASSERT(!blocklist_filter(tmplist, list2, TRUE, sep));
DO_ASSERT_WSTR_EQ(L"C:\\shr\\Foo.dll;C:\\bar.dll;c:\\foo.dll;d:\\gee.dll;ar.dll",
tmplist);
DO_ASSERT(!blocklist_filter(tmplist, list2, FALSE, sep));
DO_ASSERT_WSTR_EQ(L"C:\\bar.dll;ar.dll", tmplist);
DO_ASSERT(blocklist_filter(tmplist, list2, TRUE, sep));
free_file_list(tmplist);
tmplist = wcsdup(list1);
DO_ASSERT(!allowlist_filter(tmplist, list2, FALSE, sep));
DO_ASSERT_WSTR_EQ(L"C:\\shr\\Foo.dll;c:\\foo.dll;d:\\gee.dll", tmplist);
DO_ASSERT(allowlist_filter(tmplist, list2, TRUE, sep));
free_file_list(tmplist);
free_file_list(list2);
}
free_file_list(list1);
/* force to front */
{
list1 = L"home.dll;C:\\PROGRA~1\\DETERM~1\\SECURE~1\\lib\\DRPREI~1.DLL;foo.dll;"
L"bar.dll";
list2 = new_file_list(wcslen(list1) + 1);
wcsncpy(list2, list1, wcslen(list1) + 1);
list2 = add_to_file_list(list2,
L"C:\\PROGRA~1\\DETERM~1\\SECURE~1\\lib\\DRPREI~1.DLL",
TRUE, TRUE, TRUE, sep);
DO_ASSERT_WSTR_EQ(L"C:\\PROGRA~1\\DETERM~1\\SECURE~1\\lib\\DRPREI~1.DLL;home.dll;"
L"foo.dll;bar.dll",
list2);
DO_ASSERT(wcslen(list1) == wcslen(list2));
}
/* autoinject (FIXME: needs more..) */
{
res = get_config_parameter(INJECT_ALL_KEY_L, TRUE, INJECT_ALL_SUBKEY_L, buf,
MAX_PARAM_LEN);
DO_ASSERT(res == ERROR_SUCCESS);
res = set_config_parameter(INJECT_ALL_KEY_L, TRUE, INJECT_ALL_SUBKEY_L,
L"foo.dll;bar.dll;home.dll");
DO_ASSERT(res == ERROR_SUCCESS);
/* FIXME: should all full automated tests of all
* APPINIT_FLAGS etc. */
// res = set_autoinjection_ex(TRUE, );
// DO_ASSERT(res == ERROR_SUCCESS);
res = set_config_parameter(INJECT_ALL_KEY_L, TRUE, INJECT_ALL_SUBKEY_L, buf);
DO_ASSERT(res == ERROR_SUCCESS);
}
/* config group */
{
c = new_config_group(L"foo");
DO_ASSERT_WSTR_EQ(c->name, L"foo");
set_config_group_parameter(c, L"bar", L"wise");
c2 = new_config_group(L"foo2");
set_config_group_parameter(c2, L"bar2", L"dumb");
set_config_group_parameter(c2, L"bar3", L"dumber");
add_config_group(c, c2);
c3 = new_config_group(L"foo4");
set_config_group_parameter(c3, L"bar4", L"eloquent");
add_config_group(c, c3);
c4 = new_config_group(L"foo5");
set_config_group_parameter(c4, L"bar5", L"dull");
add_config_group(c3, c4);
c->should_clear = TRUE;
DO_DEBUG(DL_VERB, dump_config_group("", " ", c, FALSE););
res = write_config_group(c);
DO_ASSERT(res == ERROR_SUCCESS);
DO_ASSERT_WSTR_EQ(c->name, L"foo");
}
/* copy config group */
{
ConfigGroup *cc, *copy = copy_config_group(c, TRUE);
DO_ASSERT_WSTR_EQ(copy->name, L"foo");
cc = get_child(L"foo4", copy);
DO_ASSERT(NULL != cc);
DO_ASSERT_WSTR_EQ(L"eloquent", get_config_group_parameter(cc, L"bar4"));
cc = get_child(L"foo5", cc);
DO_ASSERT(NULL != cc);
DO_ASSERT_WSTR_EQ(L"dull", get_config_group_parameter(cc, L"bar5"));
free_config_group(copy);
copy = copy_config_group(c, FALSE);
DO_ASSERT_WSTR_EQ(copy->name, L"foo");
DO_ASSERT_WSTR_EQ(L"wise", get_config_group_parameter(copy, L"bar"));
cc = get_child(L"foo4", copy);
DO_ASSERT(NULL == cc);
free_config_group(copy);
}
/* remove child */
{
DO_ASSERT(c2 == get_child(L"foo2", c));
remove_child(L"foo2", c);
DO_ASSERT(NULL == get_child(L"foo2", c));
DO_ASSERT(c3 == get_child(L"foo4", c));
DO_ASSERT(c4 == get_child(L"foo5", c3));
DO_ASSERT(c3 == get_child(L"Foo4", c));
DO_ASSERT(c3 == get_child(L"FOO4", c));
/* make sure we don't die */
remove_child(L"foo2", c);
}
/* remove parameter */
{
set_config_group_parameter(c, L"testremove", L"sucks");
DO_ASSERT_WSTR_EQ(get_config_group_parameter(c, L"testremove"), L"sucks");
DO_ASSERT_WSTR_EQ(get_config_group_parameter(c, L"bar"), L"wise");
remove_config_group_parameter(c, L"testremove");
DO_ASSERT(NULL == get_config_group_parameter(c, L"testremove"));
DO_ASSERT_WSTR_EQ(get_config_group_parameter(c, L"bar"), L"wise");
free_config_group(c);
}
/* read config */
{
res = read_config_group(&c, L"foo", TRUE);
DO_ASSERT_HANDLE(res == ERROR_SUCCESS, printf("res=%d\n", res); return -1;);
DO_ASSERT_WSTR_EQ(c->name, L"foo");
DO_ASSERT(NULL != get_child(L"foo2", c));
DO_ASSERT(NULL != get_child(L"foo4", c));
DO_ASSERT(NULL != get_child(L"foo5", get_child(L"foo4", c)));
free_config_group(c);
}
/* read deep config */
{
res = read_config_group(&c, L"foo:foo2", TRUE);
DO_ASSERT(res == ERROR_SUCCESS);
DO_DEBUG(DL_VERB, printf("c->name: >%S<\n", c->name););
DO_ASSERT_WSTR_EQ(c->name, L"foo:foo2");
free_config_group(c);
}
/* read deeper config */
{
res = read_config_group(&c, L"foo:foo4:foo5", TRUE);
DO_ASSERT(res == ERROR_SUCCESS);
DO_ASSERT_WSTR_EQ(c->name, L"foo:foo4:foo5");
DO_ASSERT_WSTR_EQ(get_config_group_parameter(c, L"bar5"), L"dull");
free_config_group(c);
}
/* test write */
{
res = read_config_group(&c, L"foo:foo4:foo5", TRUE);
DO_ASSERT(res == ERROR_SUCCESS);
set_config_group_parameter(c, L"testwrite", L"rocks");
DO_ASSERT(get_config_group_parameter(c, L"testwrite") != NULL);
DO_ASSERT_WSTR_EQ(get_config_group_parameter(c, L"testwrite"), L"rocks");
res = write_config_group(c);
DO_ASSERT(res == ERROR_SUCCESS);
free_config_group(c);
res = read_config_group(&c, L"foo:foo4:foo5", TRUE);
DO_ASSERT(res == ERROR_SUCCESS);
DO_ASSERT_WSTR_EQ(get_config_group_parameter(c, L"testwrite"), L"rocks");
DO_ASSERT_WSTR_EQ(get_config_group_parameter(c, L"bar5"), L"dull");
free_config_group(c);
}
/* test write patches */
{
ConfigGroup *hp = new_config_group(L_DYNAMORIO_VAR_HOT_PATCH_MODES);
c = new_config_group(L"testapp.exe");
DO_ASSERT(c != NULL);
set_config_group_parameter(hp, L"TEST.0001", L"0");
set_config_group_parameter(hp, L"TEST.0002", L"1");
set_config_group_parameter(hp, L"MS06.019A", L"2");
add_config_group(c, hp);
hp = get_child(L_DYNAMORIO_VAR_HOT_PATCH_MODES, c);
DO_ASSERT(hp != NULL);
DO_ASSERT(get_config_group_parameter(hp, L"TEST.0001") != NULL);
DO_ASSERT_WSTR_EQ(get_config_group_parameter(hp, L"TEST.0001"), L"0");
res = write_config_group(c);
DO_ASSERT(res == ERROR_SUCCESS);
free_config_group(c);
res = read_config_group(&c, L"testapp.exe", TRUE);
DO_ASSERT(res == ERROR_SUCCESS);
hp = get_child(L_DYNAMORIO_VAR_HOT_PATCH_MODES, c);
DO_ASSERT(hp != NULL);
DO_ASSERT(get_config_group_parameter(hp, L"TEST.0001") != NULL);
DO_ASSERT_WSTR_EQ(get_config_group_parameter(hp, L"TEST.0001"), L"0");
DO_DEBUG(DL_VERB, dump_config_group("", " ", c, FALSE););
free_config_group(c);
}
/* case insensitive read */
{
res = read_config_group(&c, L"FOO:FOO4:FOO5", TRUE);
DO_ASSERT(res == ERROR_SUCCESS);
DO_ASSERT_WSTR_EQ(get_config_group_parameter(c, L"bar5"), L"dull");
free_config_group(c);
}
/* write back with foo2 removed */
{
res = read_config_group(&c, L"foo", TRUE);
DO_ASSERT(res == ERROR_SUCCESS);
DO_ASSERT_WSTR_EQ(c->name, L"foo");
c->should_clear = TRUE;
DO_ASSERT(NULL != get_child(L"foo2", c));
remove_child(L"foo2", c);
DO_ASSERT(NULL == get_child(L"foo2", c));
res = write_config_group(c);
DO_ASSERT(res == ERROR_SUCCESS);
DO_ASSERT_WSTR_EQ(c->name, L"foo");
free_config_group(c);
/* read back and make sure foo2 is still gone */
res = read_config_group(&c, L"foo", TRUE);
DO_ASSERT(res == ERROR_SUCCESS);
DO_ASSERT_WSTR_EQ(c->name, L"foo");
DO_ASSERT(NULL == get_child(L"foo2", c));
DO_ASSERT(NULL != get_child(L"foo4", c));
DO_ASSERT(NULL != get_child(L"foo5", get_child(L"foo4", c)));
free_config_group(c);
}
/* absolute config */
{
WCHAR buf[MAX_PATH];
res = set_config_parameter(L"Software\\Microsoft", TRUE, L"foo", L"bar");
DO_ASSERT(res == ERROR_SUCCESS);
res = get_config_parameter(L"Software\\Microsoft", TRUE, L"foo", buf, MAX_PATH);
DO_ASSERT(res == ERROR_SUCCESS);
DO_ASSERT_WSTR_EQ(buf, L"bar");
res = set_config_parameter(L"Software\\Microsoft", TRUE, L"foo", L"rebar");
DO_ASSERT(res == ERROR_SUCCESS);
res = get_config_parameter(L"Software\\Microsoft", TRUE, L"foo", buf, MAX_PATH);
DO_ASSERT(res == ERROR_SUCCESS);
DO_ASSERT_WSTR_EQ(buf, L"rebar");
res = set_config_parameter(L"Software\\Microsoft", TRUE, L"foo", NULL);
DO_ASSERT(res == ERROR_SUCCESS);
res = get_config_parameter(L"Software\\Microsoft", TRUE, L"foo", buf, MAX_PATH);
DO_ASSERT(res != ERROR_SUCCESS);
}
/* qnames */
{
c = new_config_group(L"apps");
c2 = new_config_group(L"bar.exe");
c3 = new_config_group(L"bar.exe-bar");
c4 = new_config_group(L"foo.exe");
add_config_group(c, c2);
add_config_group(c, c3);
add_config_group(c, c4);
set_config_group_parameter(c2, L"DYNAMORIO_RUNUNDER", L"48");
set_config_group_parameter(c3, L"DYNAMORIO_RUNUNDER", L"49");
cp = get_qualified_config_group(c, L"bar.exe", L"bar.exe /bar");
DO_ASSERT(cp != NULL);
cp = get_qualified_config_group(c, L"bar.exe", L"bar.exe /b /a /r");
DO_ASSERT(cp != NULL);
cp = get_qualified_config_group(c, L"bar.exe", L"bar.exe /c bar");
DO_ASSERT(cp != NULL);
cp = get_qualified_config_group(c, L"bar.exe", L"bar.exe /c Bar");
DO_ASSERT(cp != NULL);
cp = get_qualified_config_group(c, L"bar.exe", L"Bar.exe /c bar");
DO_ASSERT(cp != NULL);
DO_ASSERT(is_parent_of_qualified_config_group(c2));
DO_ASSERT(is_parent_of_qualified_config_group(c3));
DO_ASSERT(!is_parent_of_qualified_config_group(c4));
set_config_group_parameter(c4, L"DYNAMORIO_RUNUNDER", L"5");
DO_ASSERT(!is_parent_of_qualified_config_group(c4));
}
/* autoinject tests */
{
res = unset_autoinjection();
DO_ASSERT(res == ERROR_SUCCESS);
res = set_autoinjection();
DO_ASSERT(res == ERROR_SUCCESS);
res = unset_autoinjection();
DO_ASSERT(res == ERROR_SUCCESS);
}
printf("All Test Passed\n");
return 0;
}
#endif