blob: 236bcfdab856fca5cfadec1533fc26ec7d5f1f11 [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2004-2008 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.
*/
#include "share.h"
#include "policy.h"
#include "parser.h"
#include "config.h"
#include "processes.h"
#ifndef UNIT_TEST
WCHAR *msg_id_keys[] = {
# define MSG_FIELD(x) L# x,
POLICY_DEF_KEYS
# undef MSG_FIELD
L"<invalid message field>"
};
msg_id
get_msgkey_id(WCHAR *msgk)
{
msg_id id = -1;
DO_DEBUG(DL_FINEST, printf("trying to ID %S\n", msgk););
while (++id != MSGKEY_BAD_FIELD)
if (0 == wcscmp(msg_id_keys[id], msgk))
break;
return id;
}
char *
parse_policy_line(char *start, BOOL *done, msg_id *mfield, WCHAR *param, WCHAR *value,
SIZE_T maxchars)
{
char *next = parse_line(start, done, param, value, maxchars);
if (mfield != NULL)
*mfield = get_msgkey_id(param);
return next;
}
# define MAX_SUPPORTED_ENGINES 16
void
add_to_engines(int *engines, const WCHAR *neweng)
{
int i;
for (i = 0; engines[i] != 0; i++)
;
engines[i] = _wtoi(neweng);
}
DWORD
parse_policy(char *policy_definition,
/* OUT */ ConfigGroup **config,
/* OUT */ ConfigGroup **options, BOOL validating)
{
WCHAR namebuf[MAX_PARAM_LEN], valuebuf[MAX_PARAM_LEN];
WCHAR app_name[MAX_PATH];
msg_id mfield;
DWORD res;
char *polstr = policy_definition;
ConfigGroup *policy, *app;
char *modes_file;
SIZE_T modes_file_size;
BOOL parsing_done;
int engines[MAX_SUPPORTED_ENGINES] = { 0 };
DO_ASSERT(config != NULL);
DO_ASSERT(options != NULL);
*config = NULL;
*options = NULL;
DO_DEBUG(DL_VERB, printf("policy string received: %s\n ", polstr););
res = read_config_group(config, L_PRODUCT_NAME, FALSE);
if (res != ERROR_SUCCESS)
return res;
*options = new_config_group(L"options");
/* alias the pointer for clarity */
policy = *config;
policy->should_clear = TRUE;
/* starting parsing the polstr */
polstr = parse_policy_line(polstr, &parsing_done, &mfield, namebuf, valuebuf,
MAX_PARAM_LEN);
/* first, load the options at the beginning
* (GLOBAL_PROTECT, VERSION, etc) */
while (!parsing_done && mfield != MSGKEY_BEGIN_BLOCK) {
if (0 == wcscmp(namebuf, L"ENGINE")) {
add_to_engines(engines, valuebuf);
} else {
set_config_group_parameter(*options, namebuf, valuebuf);
}
polstr = parse_policy_line(polstr, &parsing_done, &mfield, namebuf, valuebuf,
MAX_PARAM_LEN);
}
/* now do all of the blocks */
while (!parsing_done) {
if (mfield != MSGKEY_BEGIN_BLOCK) {
DO_DEBUG(DL_ERROR, printf("BEGIN_BLOCK not found, instead %S\n", namebuf););
return ERROR_PARSE_ERROR;
}
polstr = parse_policy_line(polstr, &parsing_done, &mfield, namebuf, valuebuf,
MAX_PARAM_LEN);
if (mfield == MSGKEY_GLOBAL) {
app = policy;
} else if (mfield == MSGKEY_APP_NAME) {
wcsncpy(app_name, valuebuf, MAX_PATH);
NULL_TERMINATE_BUFFER(app_name);
app = new_config_group(app_name);
add_config_group(policy, app);
} else {
DO_DEBUG(DL_ERROR, printf("bad appname token: %S\n", namebuf););
return ERROR_PARSE_ERROR;
}
DO_DEBUG(DL_FINEST, printf("'%S' is the app being parsed\n", app->name););
modes_file = NULL;
modes_file_size = 0xffffffff;
for (;;) {
polstr = parse_policy_line(polstr, &parsing_done, &mfield, namebuf, valuebuf,
MAX_PARAM_LEN);
if (parsing_done || mfield == MSGKEY_END_BLOCK)
break;
if (mfield == MSGKEY_BEGIN_MP_MODES) {
polstr = next_token(polstr, &modes_file_size);
modes_file = polstr;
DO_DEBUG(DL_VERB, printf("mf=%s\n", modes_file););
polstr = get_message_block_size(polstr, msg_id_keys[MSGKEY_END_MP_MODES],
&modes_file_size);
if (modes_file_size == 0xffffffff)
return ERROR_PARSE_ERROR;
continue;
}
DO_DEBUG(DL_VERB,
printf("option setting: %S, %S=%S\n", app_name, namebuf, valuebuf););
set_config_group_parameter(app, namebuf, valuebuf);
} // for(;;)
/* FIXME:
* strictly speaking, this isn't parsing, and so it's
* kind of messy to be doing this here. (eg: notice that
* validate_policy at this point will write all of the
* modes files!) but it's ok for now..sort of... */
if (!validating && modes_file != NULL) {
char backup;
WCHAR *modes_path;
BOOL changed;
WCHAR modes_filename[MAX_PATH];
int j;
modes_path = get_config_group_parameter(app, L_DYNAMORIO_VAR_HOT_PATCH_MODES);
if (modes_path == NULL) {
DO_DEBUG(DL_ERROR, printf("missing modes file name!\n"););
return ERROR_PARSE_ERROR;
}
backup = modes_file[modes_file_size];
modes_file[modes_file_size] = L'\0';
/* need to write modes file for all supported engines */
for (j = 0; engines[j] != 0; j++) {
_snwprintf(modes_filename, MAX_PATH, L"%s\\%d\\%S", modes_path,
engines[j], HOTP_MODES_FILENAME);
res = write_file_contents_if_different(modes_filename, modes_file,
&changed);
}
modes_file[modes_file_size] = backup;
if (res != ERROR_SUCCESS)
return res;
/* FIXME: we have the 'changed' info, so we may want to nudge
* selectively based on that...but it gets kind of dangerous
* so just nudge_all for now. */
} else {
/* FIXME: should we delete old modes files? */
}
/* and prep the next line */
polstr = parse_policy_line(polstr, &parsing_done, &mfield, namebuf, valuebuf,
MAX_PARAM_LEN);
} // while(!parsing_done)
return ERROR_SUCCESS;
}
/* the amount of time to wait after startup before doing
* a nudge reset on all processes. */
# define DEFAULT_RESET_INTERVAL_MS 2 * 60 * 1000
/* timeout for nudge reset operation */
# define DEFAULT_RESET_TIMEOUT_MS 30 * 1000
/* to mitigate the possibility of bringing the system to a halt, wait
* 2 seconds between process resets. */
# define DEFAULT_RESET_DELAY_MS 2 * 1000
DWORD
policy_import(char *policy_definition, BOOL synchronize_system, BOOL *inject_flag,
DWORD *warning)
{
ConfigGroup *policy;
ConfigGroup *options;
DWORD res;
WCHAR *global_protect;
DO_ASSERT(policy_definition != NULL);
if (warning != NULL)
*warning = ERROR_SUCCESS;
res = parse_policy(policy_definition, &policy, &options, FALSE);
if (res != ERROR_SUCCESS)
return res;
res = write_config_group(policy);
if (res != ERROR_SUCCESS)
return res;
/* note global protect is optional */
global_protect =
get_config_group_parameter(options, msg_id_keys[MSGKEY_GLOBAL_PROTECT]);
if (NULL != global_protect) {
if (_wtoi(global_protect)) {
if (inject_flag != NULL)
*inject_flag = TRUE;
else
set_autoinjection();
} else {
if (inject_flag != NULL) {
*inject_flag = FALSE;
} else {
if (!using_system32_for_preinject(NULL)) {
/* NOTE: on NT the appinit value is cached per-boot;
* so instead of clearing it, we just leave our
* preinject in permanently, and only remove it at
* uninstall. this is ok for global protect = OFF
* since that message will come with an empty list of
* apps, so nothing will be run under dr.
* but for slightly saner platforms we prefer to take
* the safe route of clearing out the appinit value. */
unset_autoinjection();
}
}
res = detach_all(DETACH_RECOMMENDED_TIMEOUT);
if (res != ERROR_SUCCESS && warning != NULL)
*warning = res;
}
}
if (res != ERROR_SUCCESS)
return res;
if (synchronize_system) {
/* FIXME: This is a ugly hack to fix case 9436; reasonable because 4.2
* release is close and this is the minimal change. The better fix
* would be to make the core flag thin_client via drmarker and use it in
* system_info_cb. Though that is better, it still isn't great because
* node manager will still be issuing detach to all that aren't in the
* config set. The best would be either to make EV send an incremental
* policy update rather than a full one or have node manager identify
* which ones to nudge by maintaining the prior policy update. */
wchar_t *global_options;
global_options = get_config_group_parameter(policy, L_DYNAMORIO_VAR_OPTIONS);
if (global_options == NULL || wcsstr(global_options, L"-thin_client") == NULL) {
res = detach_all_not_in_config_group(policy, DETACH_RECOMMENDED_TIMEOUT);
}
if (res != ERROR_SUCCESS) {
DO_DEBUG(DL_WARN, printf("error %d doing consistency detach!\n", res););
if (warning && *warning != ERROR_SUCCESS)
*warning = res;
}
/* FIXME: very inefficient to issue a process control nudge each time
* there is an generic update; ev should split update messages into
* regular, process control, hotpatch mode, hotpatch defs, etc and use
* them only as needed - will prevent needless performance bottlenecks,
* esp. with hotpatch nudges. 02-Nov-06: Bharath. */
/* FIXME: reset_threadproc() just seems to do a generic nudge with
* sleep - parameterize the sleep and check its uses to see if it can
* be made generic. 02-Nov-06: Bharath. */
/* FIXME: Also, there are two layers of delaying/sleeping one at
* reset_threadproc() level and one at the process_walk level for
* nudges. Why wasn't one enough? 02-Nov-06e: Bharath */
/* FIXME: return value from generic_nudge_all and
* hotp_notify_all_modes_update (and hotp_notify_all_defs_update else
* where) haven't been checked. Don't even know what that means for a
* nudge all. Is it supposed to indicate that the whole thing failed
* or just one nudge? If so what is the intended action. 02-Nov-06:
* Bharath. */
/* FIXME: make both nodemanager and core use the generic nudge
* interface to do hotpatch and detach nudges and do away with the
* nudge-specific code. 02-Nov-06: Bharath. */
generic_nudge_all(NUDGE_GENERIC(process_control), NULL, DEFAULT_RESET_TIMEOUT_MS,
0);
/* FIXME: for now we do this at every policy update, but
* maybe should be more efficient? */
res = hotp_notify_all_modes_update(DETACH_RECOMMENDED_TIMEOUT);
if (res != ERROR_SUCCESS) {
DO_DEBUG(DL_WARN, printf("Hotpatch nudge failed! %d\n", res););
if (warning && *warning != ERROR_SUCCESS)
*warning = res;
}
}
free_config_group(options);
free_config_group(policy);
DO_DEBUG(DL_INFO, printf("Processed policy update.\n"););
return ERROR_SUCCESS;
}
DWORD
clear_policy()
{
ConfigGroup *config;
DWORD res;
/* wipe out the registry by writing an empty policy. */
res = read_config_group(&config, L_PRODUCT_NAME, FALSE);
if (res != ERROR_SUCCESS)
return res;
config->should_clear = TRUE;
res = write_config_group(config);
free_config_group(config);
return res;
}
/* returns ERROR_SUCCESS unless the policy_buffer is invalid. */
DWORD
validate_policy(char *policy_definition)
{
ConfigGroup *policy;
ConfigGroup *options;
DWORD res;
DO_ASSERT(policy_definition != NULL);
res = parse_policy(policy_definition, &policy, &options, TRUE);
if (res != ERROR_SUCCESS)
return res;
free_config_group(policy);
free_config_group(options);
return ERROR_SUCCESS;
}
void
append_policy_block(char *policy_buffer, SIZE_T maxchars, SIZE_T *accumlen,
ConfigGroup *cfg)
{
NameValuePairNode *nvpn = NULL;
msg_append(policy_buffer, maxchars, msg_id_keys[MSGKEY_BEGIN_BLOCK], accumlen);
msg_append(policy_buffer, maxchars, L_NEWLINE, accumlen);
if (0 == wcscmp(cfg->name, L_PRODUCT_NAME)) {
msg_append(policy_buffer, maxchars, msg_id_keys[MSGKEY_GLOBAL], accumlen);
msg_append(policy_buffer, maxchars, L_NEWLINE, accumlen);
} else {
msg_append_nvp(policy_buffer, maxchars, accumlen, msg_id_keys[MSGKEY_APP_NAME],
cfg->name);
}
for (nvpn = cfg->params; NULL != nvpn; nvpn = nvpn->next) {
msg_append_nvp(policy_buffer, maxchars, accumlen, nvpn->name, nvpn->value);
}
msg_append(policy_buffer, maxchars, msg_id_keys[MSGKEY_END_BLOCK], accumlen);
msg_append(policy_buffer, maxchars, L_NEWLINE, accumlen);
}
/* FIXME: does not export modes! */
DWORD
policy_export(char *policy_buffer, SIZE_T maxchars, SIZE_T *needed)
{
ConfigGroup *config, *chld;
DWORD res;
SIZE_T accumlen = 0;
res = read_config_group(&config, L_PRODUCT_NAME, TRUE);
if (res != ERROR_SUCCESS)
return res;
/* NOTE: we don't specify global protect when exporting */
/* FIXME: hardcoded ID and version */
msg_append_nvp(policy_buffer, maxchars, &accumlen, L"POLICY_VERSION", L"30000");
append_policy_block(policy_buffer, maxchars, &accumlen, config);
for (chld = config->children; chld != NULL; chld = chld->next) {
append_policy_block(policy_buffer, maxchars, &accumlen, chld);
}
/* for the null terminator */
accumlen++;
if (needed != NULL)
*needed = accumlen;
if (policy_buffer == NULL || maxchars < accumlen)
return ERROR_MORE_DATA;
else
return ERROR_SUCCESS;
}
DWORD
load_policy(WCHAR *filename, BOOL synchronize_system, DWORD *warning)
{
DWORD res;
SIZE_T len;
char *policy;
DO_ASSERT(filename != NULL);
res = read_file_contents(filename, NULL, 0, &len);
DO_ASSERT(res == ERROR_MORE_DATA);
policy = (char *)malloc(len);
res = read_file_contents(filename, policy, len, NULL);
if (res != ERROR_SUCCESS)
return res;
res = policy_import(policy, synchronize_system, NULL, warning);
free(policy);
return res;
}
DWORD
save_policy(WCHAR *filename)
{
DWORD res;
SIZE_T len;
char *policy;
res = policy_export(NULL, 0, &len);
if (res != ERROR_MORE_DATA && res != ERROR_SUCCESS)
return res;
policy = (char *)malloc(len);
policy[0] = '\0';
res = policy_export(policy, len, NULL);
if (res == ERROR_SUCCESS) {
res = write_file_contents(filename, policy, TRUE);
}
free(policy);
return res;
}
#else // ifdef UNIT_TEST
void
test_sample_mfp(ConfigGroup *globals, ConfigGroup *config)
{
ConfigGroup *chld;
if (globals != NULL) {
DO_ASSERT_WSTR_EQ(L"77777",
get_config_group_parameter(globals, L"POLICY_VERSION"));
}
DO_ASSERT_WSTR_EQ(L"1", get_config_group_parameter(config, L"DYNAMORIO_RUNUNDER"));
DO_ASSERT_WSTR_EQ(L"", get_config_group_parameter(config, L"DYNAMORIO_OPTIONS"));
DO_ASSERT(NULL !=
wcsstr(get_config_group_parameter(config, L"DYNAMORIO_AUTOINJECT"),
L"\\lib\\77777\\dynamorio.dll"));
chld = get_child(L"svchost.exe-bitsgroup", config);
DO_ASSERT(chld != NULL);
DO_ASSERT_WSTR_EQ(L"17", get_config_group_parameter(chld, L"DYNAMORIO_RUNUNDER"));
DO_ASSERT_WSTR_EQ(L"-report_max 0 -kill_thread -kill_thread_max 1000",
get_config_group_parameter(chld, L"DYNAMORIO_OPTIONS"));
DO_ASSERT(NULL !=
wcsstr(get_config_group_parameter(chld, L"DYNAMORIO_AUTOINJECT"),
L"\\lib\\77777\\dynamorio.dll"));
}
int
main()
{
char *testline = "GLOBAL_PROTECT=1\r\nBEGIN_BLOCK\r\nAPP_NAME=inetinfo."
"exe\r\nDYNAMORIO_OPTIONS=\r\nFOO=\\bar.dll\r\n";
WCHAR *sample = L"sample.mfp";
SIZE_T len;
DWORD res;
char *policy;
set_debuglevel(DL_INFO);
set_abortlevel(DL_WARN);
/* load the sample policy file for testing */
res = read_file_contents(sample, NULL, 0, &len);
DO_ASSERT(res == ERROR_MORE_DATA);
DO_ASSERT(len > 1000);
policy = (char *)malloc(len);
res = read_file_contents(sample, policy, len, NULL);
DO_ASSERT(res == ERROR_SUCCESS);
/* parse_policy_line tests */
{
WCHAR param[MAX_PATH], value[MAX_PATH];
BOOL done;
msg_id mfield;
char *ptr, *line = testline;
ptr = parse_policy_line(line, &done, &mfield, param, value, MAX_PATH);
DO_ASSERT(!done);
DO_ASSERT(mfield == MSGKEY_GLOBAL_PROTECT);
DO_ASSERT_WSTR_EQ(param, L"GLOBAL_PROTECT");
DO_ASSERT_WSTR_EQ(value, L"1");
ptr = parse_policy_line(ptr, &done, &mfield, param, value, MAX_PATH);
DO_ASSERT(!done);
DO_ASSERT(mfield == MSGKEY_BEGIN_BLOCK);
DO_ASSERT_WSTR_EQ(param, L"BEGIN_BLOCK");
DO_ASSERT_WSTR_EQ(value, L"");
ptr = parse_policy_line(ptr, &done, &mfield, param, value, MAX_PATH);
DO_ASSERT(!done);
DO_ASSERT(mfield == MSGKEY_APP_NAME);
DO_ASSERT_WSTR_EQ(param, L"APP_NAME");
DO_ASSERT_WSTR_EQ(value, L"inetinfo.exe");
ptr = parse_policy_line(ptr, &done, &mfield, param, value, MAX_PATH);
DO_ASSERT(!done);
DO_ASSERT(mfield == MSGKEY_BAD_FIELD);
DO_ASSERT_WSTR_EQ(param, L"DYNAMORIO_OPTIONS");
DO_ASSERT_WSTR_EQ(value, L"");
ptr = parse_policy_line(ptr, &done, &mfield, param, value, MAX_PATH);
DO_ASSERT(!done);
DO_ASSERT(mfield == MSGKEY_BAD_FIELD);
DO_ASSERT_WSTR_EQ(param, L"FOO");
DO_ASSERT(NULL != wcsstr(value, L"bar"));
DO_ASSERT(NULL != wcsstr(value, get_dynamorio_home()));
}
/* parse policy tests */
{
ConfigGroup *config, *globals;
res = parse_policy(policy, &config, &globals, FALSE);
DO_ASSERT(res == ERROR_SUCCESS);
DO_ASSERT(globals != NULL);
DO_ASSERT(config != NULL);
test_sample_mfp(globals, config);
free_config_group(globals);
free_config_group(config);
}
/* validate policy tests */
{}
/* import policy tests */
{
DWORD warning;
ConfigGroup *config;
warning = ERROR_SUCCESS;
res = policy_import(policy, FALSE, NULL, &warning);
DO_ASSERT(res == ERROR_SUCCESS);
res = read_config_group(&config, L_PRODUCT_NAME, TRUE);
DO_ASSERT(res == ERROR_SUCCESS);
test_sample_mfp(NULL, config);
free_config_group(config);
}
/* export policy tests */
{
WCHAR *outfn = L"test.mfp";
char *outpol;
DWORD warning;
warning = ERROR_SUCCESS;
res = policy_import(policy, FALSE, NULL, &warning);
DO_ASSERT(res == ERROR_SUCCESS);
/* load the sample policy file for testing */
res = policy_export(NULL, 0, &len);
DO_ASSERT(res == ERROR_MORE_DATA);
DO_ASSERT(len > 1000);
outpol = (char *)malloc(len);
outpol[0] = '\0';
res = policy_export(outpol, len, NULL);
DO_ASSERT(res == ERROR_SUCCESS);
DO_ASSERT(len == 1 + strlen(outpol));
res = write_file_contents(outfn, outpol, TRUE);
DO_ASSERT(res == ERROR_SUCCESS);
// DO_ASSERT(0 == strcmp(policy, outpol));
}
/* load/save */
{
DWORD warning = ERROR_SUCCESS;
ConfigGroup *c;
/* TODO: better */
CHECKED_OPERATION(clear_policy());
CHECKED_OPERATION(load_policy(sample, FALSE, &warning));
DO_ASSERT(warning == ERROR_SUCCESS);
CHECKED_OPERATION(read_config_group(&c, L_PRODUCT_NAME, TRUE));
test_sample_mfp(NULL, c);
free_config_group(c);
delete_file_rename_in_use(L"test2.mfp");
CHECKED_OPERATION(save_policy(L"test2.mfp"));
}
/* cleanup */
CHECKED_OPERATION(clear_policy());
printf("All Test Passed\n");
return 0;
}
#endif