blob: c213fc293e09307510bf62f8719e8f1f1d3b3164 [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2011-2014 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.
*/
#include "share.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#ifdef WINDOWS
# include "config.h"
# include "elm.h"
# include "events.h" /* for canary */
# include "processes.h" /* for canary */
# include "options.h" /* for option checking */
# include "ntdll_types.h" /* for NT_SUCCESS */
# include <io.h> /* for canary */
# include <Fcntl.h> /* for canary */
# include <aclapi.h>
#else
# include <sys/stat.h>
#endif
#ifndef UNIT_TEST
#ifdef WINDOWS
# ifdef DEBUG
int debuglevel = DL_FATAL;
int abortlevel = DL_FATAL;
void
set_debuglevel(int level)
{
debuglevel = level;
}
void
set_abortlevel(int level)
{
abortlevel = level;
}
#define CONFIG_MAX 8192
#define HEADER_SNIPPET(defsfile) \
"POLICY_VERSION=30000\n" \
"BEGIN_BLOCK\n" \
"GLOBAL\n" \
"DYNAMORIO_OPTIONS=\n" \
"DYNAMORIO_RUNUNDER=1\n" \
"DYNAMORIO_AUTOINJECT=\\lib\\dynamorio.dll\n" \
"DYNAMORIO_HOT_PATCH_POLICIES=" defsfile "\n" \
"DYNAMORIO_UNSUPPORTED=\n" \
"END_BLOCK\n"
DWORD
load_test_config(const char *snippet, BOOL use_hotpatch_defs)
{
char buf[CONFIG_MAX];
_snprintf(buf, CONFIG_MAX, "%s%s",
use_hotpatch_defs ?
HEADER_SNIPPET("\\conf") : HEADER_SNIPPET(""),
snippet);
NULL_TERMINATE_BUFFER(buf);
DO_ASSERT(strlen(buf) < CONFIG_MAX - 2);
DO_DEBUG(DL_VERB,
printf("importing %s\n", buf);
);
CHECKED_OPERATION(policy_import(buf, FALSE, NULL, NULL));
return ERROR_SUCCESS;
}
void
get_testdir(WCHAR *buf, UINT maxchars)
{
WCHAR *filePart;
WCHAR tmp[MAX_PATH];
DWORD len;
len = GetEnvironmentVariable(L"DYNAMORIO_WINDIR",
tmp, maxchars);
DO_ASSERT(len < maxchars);
if (len == 0) {
len = GetEnvironmentVariable(L_DYNAMORIO_VAR_HOME,
tmp, maxchars);
DO_ASSERT(len < maxchars);
/* check for cygwin paths on windows */
if (!file_exists(buf))
len = 0;
DO_DEBUG(DL_INFO,
printf("ignoring invalid-looking DYNAMORIO_HOME=%S\n", tmp);
);
}
if (len == 0) {
wcsncpy(tmp, L"..", MAX_PATH);
}
len = GetFullPathName(tmp, maxchars, buf, &filePart);
DO_DEBUG(DL_INFO,
printf("using drhome: %S\n", buf);
);
DO_ASSERT(len != 0);
return;
}
void
error_cb(unsigned int errcode, WCHAR *message)
{
if (errcode || !errcode || message) {
DO_ASSERT(0);
}
}
extern BOOL do_once;
typedef struct evthelp__ {
DWORD type;
WCHAR *exename;
ULONG pid;
WCHAR *s3;
WCHAR *s4;
UINT maxchars;
BOOL found;
} evthelp;
evthelp *cb_eh = NULL;
int last_record = -1;
void
check_event_cb(EVENTLOGRECORD *record)
{
const WCHAR *strings;
if (cb_eh->found)
return;
last_record = record->RecordNumber;
if (record->EventID == cb_eh->type) {
if (cb_eh->exename != NULL &&
0 != wcscmp(get_event_exename(record), cb_eh->exename))
return;
if (cb_eh->pid != 0 && cb_eh->pid != get_event_pid(record))
return;
strings = get_message_strings(record);
strings = next_message_string(strings);
strings = next_message_string(strings);
if (cb_eh->s3 != NULL) {
cb_eh->s3[0] = L'\0';
if (strings != NULL) {
wcsncpy(cb_eh->s3, strings, cb_eh->maxchars);
cb_eh->s3[cb_eh->maxchars-1] = L'\0';
}
}
if (cb_eh->s4 != NULL) {
cb_eh->s4[0] = L'\0';
strings = next_message_string(strings);
if (strings != NULL) {
wcsncpy(cb_eh->s4, strings, cb_eh->maxchars);
cb_eh->s4[cb_eh->maxchars-1] = L'\0';
}
}
cb_eh->found = TRUE;
}
}
void
reset_last_event()
{
last_record = -1;
}
/* checks for events matching type, exename (if not null), and pid (if
* not 0). fills in s3 and s4 with 3rd and 4th message strings of
* match, if not null.
* next search will start with event after matched event. */
BOOL
check_for_event(DWORD type, WCHAR *exename, ULONG pid,
WCHAR *s3, WCHAR *s4, UINT maxchars)
{
evthelp eh;
eh.type = type;
eh.exename = exename;
eh.pid = pid;
eh.s3 = s3;
eh.s4 = s4;
eh.maxchars = maxchars;
eh.found = FALSE;
cb_eh = &eh;
/* backdoor */
do_once = TRUE;
CHECKED_OPERATION(start_eventlog_monitor(FALSE, NULL, check_event_cb,
error_cb, last_record));
DO_ASSERT(WAIT_OBJECT_0 ==
WaitForSingleObject(get_eventlog_monitor_thread_handle(),
10000));
stop_eventlog_monitor();
return eh.found;
}
FILE *event_list_fp;
void
show_event_cb(unsigned int mID,
unsigned int type,
WCHAR *message,
DWORD timestamp)
{
/* fool the compiler */
DO_ASSERT(type == 0 || type != 0 || timestamp == 0);
fprintf(event_list_fp, " Event %d: %S\n", mID, message);
}
void
show_all_events(FILE *fp)
{
DO_ASSERT (fp != NULL);
event_list_fp = fp;
/* backdoor */
do_once = TRUE;
CHECKED_OPERATION(start_eventlog_monitor(TRUE, show_event_cb, NULL,
error_cb, (DWORD) -1));
DO_ASSERT(WAIT_OBJECT_0 ==
WaitForSingleObject(get_eventlog_monitor_thread_handle(),
10000));
stop_eventlog_monitor();
return;
}
# endif /* _DEBUG */
void
wcstolower(WCHAR *str)
{
UINT i;
for (i=0; i < wcslen(str); i++)
str[i] = towlower(str[i]);
}
WCHAR *
get_exename_from_path(const WCHAR *path)
{
WCHAR *name = wcsrchr(path, L'\\');
if (name == NULL)
name = (WCHAR *) path;
else
name += 1;
return name;
}
DWORD
acquire_shutdown_privilege()
{
HANDLE hToken = NULL;
TOKEN_PRIVILEGES Priv;
// get current thread token
if (!OpenThreadToken(GetCurrentThread(),
TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES,
FALSE,
&hToken)) {
// can't get thread token, try process token instead
if(!OpenProcessToken(GetCurrentProcess(),
TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES,
&hToken)) {
return GetLastError();
}
}
Priv.PrivilegeCount = 1;
Priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &Priv.Privileges[0].Luid);
// try to enable the privilege
if(!AdjustTokenPrivileges(hToken, FALSE, &Priv, sizeof(Priv), NULL, 0))
return GetLastError();
return ERROR_SUCCESS;
}
/*
* FIXME: shutdown reason. we should probably use this, BUT
* InitiateSystemShutdownEx is not included in VS6.0, so we'll have
* to dynamically link it in.
*
* from msdn:
* SHTDN_REASON_FLAG_PLANNED: The shutdown was planned. On Windows .NET
* Server, the system generates a state snapshot. For more information,
* see the help for Shutdown Event Tracker.
*
* various combinations of major/minor flags are "recognized by the
* system"; the APPLICATION|RECONFIG is NOT one of these. However,
* "You can also define your own shutdown reasons and add them to the
* the registry." we should probably do this at installation, once
* we're happy we've got a good reason code.
*/
DWORD
reboot_system()
{
DWORD res;
res = acquire_shutdown_privilege();
if (res != ERROR_SUCCESS)
return res;
/* do we need to harden this at all?
* "If the the system is not ready to handle the request, the last
* error code is ERROR_NOT_READY. The application should wait a
* short while and retry the call."
* also ERROR_MACHINE_LOCKED, ERROR_SHUTDOWN_IN_PROGRESS, etc. */
res = InitiateSystemShutdown(NULL, L"A System Restart was requested.",
30, TRUE, TRUE);
// SHTDN_REASON_MAJOR_APPLICATION |
// SHTDN_REASON_MINOR_RECONFIG);
return res;
}
#define LAST_WCHAR(wstr) wstr[wcslen(wstr) - 1]
#endif /* WINDOWS */
/* this sucks.
* i can't believe this is best way to implement this in Win32...
* but i can't seem to find a better way.
* msdn suggests using CreateFile() with CREATE_NEW or OPEN_EXISTING,
* and then checking error codes; but the problem there is that C:\\
* returns PATH_NOT_FOUND regardless. */
bool
file_exists(const TCHAR *fn)
{
#ifdef WINDOWS
WIN32_FIND_DATA fd;
HANDLE search;
DO_ASSERT(fn != NULL);
search = FindFirstFile(fn, &fd);
if (search == INVALID_HANDLE_VALUE) {
/* special handling for e.g. C:\\ */
if (LAST_WCHAR(fn) == L'\\' || LAST_WCHAR(fn) == L':') {
WCHAR buf[MAX_PATH];
_snwprintf(buf, MAX_PATH, L"%S%S*",
fn, LAST_WCHAR(fn) == L'\\' ? L"" : L"\\");
NULL_TERMINATE_BUFFER(buf);
search = FindFirstFile(buf, &fd);
if (search != INVALID_HANDLE_VALUE) {
FindClose(search);
return TRUE;
} else {
DO_DEBUG(DL_VERB,
printf("%S: even though we tried hard, %d\n",
buf, GetLastError());
);
}
}
DO_DEBUG(DL_VERB,
printf("%S doesn't exist because of: %d\n",
fn, GetLastError());
);
return FALSE;
}
else {
FindClose(search);
return TRUE;
}
#else
struct stat st;
return stat(fn, &st) == 0;
#endif
}
#ifdef WINDOWS
#define MAX_COUNTER 999999
/* grokked from the core.
* FIXME: shareme!
* if NULL is passed for directory, then it is ignored and no directory
* check is done, and filename_base is assumed to be absolute.
* TODO: make this a proactive check: make sure the file can be
* opened, eg, do a create/delete on the filename to be returned.
*/
BOOL
get_unique_filename(const WCHAR* directory,
const WCHAR* filename_base,
const WCHAR* file_type,
WCHAR* filename_buffer,
UINT maxlen)
{
UINT counter = 0;
if (directory != NULL && !file_exists(directory))
return FALSE;
do {
if (directory == NULL)
_snwprintf(filename_buffer, maxlen, L"%s.%.8d%s",
filename_base, counter, file_type);
else
_snwprintf(filename_buffer, maxlen, L"%s\\%s.%.8d%s",
directory, filename_base, counter, file_type);
filename_buffer[maxlen-1] = L'\0';
}
while (file_exists(filename_buffer) && (++counter < MAX_COUNTER));
return (counter < MAX_COUNTER);
}
DWORD
delete_file_on_boot(WCHAR *filename)
{
DWORD res;
BOOL success =
MoveFileEx(filename, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
/* reboot removal adds an entry to
* HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations
* and smss.exe will delete the file on next boot
*/
if (success)
res = ERROR_SUCCESS;
else
res = GetLastError();
return res;
}
DWORD
delete_file_rename_in_use(WCHAR *filename)
{
DWORD res;
BOOL success = DeleteFile(filename);
if (success)
return ERROR_SUCCESS;
/* xref case 4512: if we leave a dll in a process after we're done
* using it, we won't be able to delete it; however, hopefully
* we can rename it so there won't be issues replacing it later. */
res = GetLastError();
if (res != ERROR_SUCCESS) {
WCHAR tempname[MAX_PATH];
if (get_unique_filename(NULL, filename, L".tmp", tempname, MAX_PATH)) {
success = MoveFile(filename, tempname);
if (success) {
res = ERROR_SUCCESS;
/* as best effort, we also schedule cleanup of the
* temporary file on next boot */
delete_file_on_boot(tempname);
} else
res = GetLastError();
}
}
return res;
}
#ifndef PROTECTED_DACL_SECURITY_INFORMATION
# define PROTECTED_DACL_SECURITY_INFORMATION (0x80000000L)
#endif
/*
* quick permissions xfer workaround for updating permissions
* on upgrade.
*/
DWORD
copy_file_permissions(WCHAR *filedst, WCHAR *filesrc)
{
DWORD res = ERROR_SUCCESS;
SECURITY_DESCRIPTOR *sd = NULL;
ACL *dacl = NULL;
res = GetNamedSecurityInfo(filesrc, SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION,
NULL, NULL, &dacl, NULL, &sd);
if (res != ERROR_SUCCESS)
return res;
res = SetNamedSecurityInfo(filedst, SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION |
PROTECTED_DACL_SECURITY_INFORMATION,
NULL, NULL, dacl, NULL);
LocalFree(sd);
return res;
}
/* NOTE: for now we only consider the major/minor versions and
* platform id.
*
* the osinfo.szCSDVersion string contains service pack information,
* which could be used to distinguish e.g. XPSP2, 2K3SP1, if
* necessary.
*/
DWORD
get_platform(DWORD *platform)
{
/* determine the OS version information */
OSVERSIONINFOW osinfo;
/* i#1418: GetVersionEx is just plain broken on win8.1+ so we use the Rtl version */
typedef NTSTATUS (NTAPI *RtlGetVersion_t)(OSVERSIONINFOW *info);
RtlGetVersion_t RtlGetVersion;
NTSTATUS res = -1;
HANDLE ntdll_handle = GetModuleHandle(_T("ntdll.dll"));
if (ntdll_handle == NULL)
return GetLastError();
RtlGetVersion = (RtlGetVersion_t)
GetProcAddress((HMODULE)ntdll_handle, "RtlGetVersion");
if (RtlGetVersion == NULL)
return GetLastError();
osinfo.dwOSVersionInfoSize = sizeof(osinfo);
res = RtlGetVersion(&osinfo);
if (NT_SUCCESS(res)) {
DO_DEBUG(DL_VERB,
WCHAR verbuf[MAX_PATH];
_snwprintf(verbuf, MAX_PATH,
L"Major=%d, Minor=%d, Build=%d, SPinfo=%s",
osinfo.dwMajorVersion, osinfo.dwMinorVersion,
osinfo.dwBuildNumber, osinfo.szCSDVersion);
NULL_TERMINATE_BUFFER(verbuf);
printf("%S\n", verbuf);
);
if (osinfo.dwPlatformId != VER_PLATFORM_WIN32_NT)
return ERROR_UNSUPPORTED_OS;
if (osinfo.dwMajorVersion == 4) {
if (osinfo.dwMinorVersion == 0) {
*platform = PLATFORM_WIN_NT_4;
return ERROR_SUCCESS;
}
}
else if (osinfo.dwMajorVersion == 5) {
if (osinfo.dwMinorVersion == 0) {
*platform = PLATFORM_WIN_2000;
return ERROR_SUCCESS;
}
else if (osinfo.dwMinorVersion == 1) {
*platform = PLATFORM_WIN_XP;
return ERROR_SUCCESS;
}
else if (osinfo.dwMinorVersion == 2) {
*platform = PLATFORM_WIN_2003;
return ERROR_SUCCESS;
}
}
else if (osinfo.dwMajorVersion == 6) {
if (osinfo.dwMinorVersion == 0) {
*platform = PLATFORM_VISTA;
return ERROR_SUCCESS;
}
else if (osinfo.dwMinorVersion == 1) {
*platform = PLATFORM_WIN_7;
return ERROR_SUCCESS;
}
else if (osinfo.dwMinorVersion == 2) {
*platform = PLATFORM_WIN_8;
return ERROR_SUCCESS;
}
else if (osinfo.dwMinorVersion == 3) {
*platform = PLATFORM_WIN_8_1;
return ERROR_SUCCESS;
}
}
return ERROR_UNSUPPORTED_OS;
} else {
return res;
}
}
BOOL
is_wow64(HANDLE hProcess)
{
/* IsWow64Pocess is only available on XP+ */
typedef DWORD (WINAPI *IsWow64Process_Type)(HANDLE hProcess,
PBOOL isWow64Process);
static HANDLE kernel32_handle;
static IsWow64Process_Type IsWow64Process;
if (kernel32_handle == NULL)
kernel32_handle = GetModuleHandle(L"kernel32.dll");
if (IsWow64Process == NULL && kernel32_handle != NULL) {
IsWow64Process = (IsWow64Process_Type)
GetProcAddress(kernel32_handle, "IsWow64Process");
}
if (IsWow64Process == NULL) {
/* should be NT or 2K */
DO_DEBUG(DL_INFO, {
DWORD platform = 0;
get_platform(&platform);
DO_ASSERT(platform == PLATFORM_WIN_NT_4 || platform == PLATFORM_WIN_2000);
});
return FALSE;
} else {
BOOL res;
if (!IsWow64Process(hProcess, &res))
return FALSE;
return res;
}
}
static const TCHAR *
get_dynamorio_home_helper(BOOL reset)
{
static TCHAR dynamorio_home[MAXIMUM_PATH] = { 0 };
int res;
if (reset)
dynamorio_home[0] = L'\0';
if (dynamorio_home[0] != L'\0')
return dynamorio_home;
res = get_config_parameter(L_PRODUCT_NAME, FALSE,
L_DYNAMORIO_VAR_HOME, dynamorio_home, MAXIMUM_PATH);
if (res == ERROR_SUCCESS && dynamorio_home[0] != L'\0')
return dynamorio_home;
else
return NULL;
}
const TCHAR *
get_dynamorio_home()
{
return get_dynamorio_home_helper(FALSE);
}
static const TCHAR *
get_dynamorio_logdir_helper(BOOL reset)
{
static TCHAR dynamorio_logdir[MAXIMUM_PATH] = { 0 };
DWORD res;
if (reset)
dynamorio_logdir[0] = L'\0';
if (dynamorio_logdir[0] != L'\0')
return dynamorio_logdir;
res = get_config_parameter(L_PRODUCT_NAME, FALSE,
L_DYNAMORIO_VAR_LOGDIR, dynamorio_logdir, MAXIMUM_PATH);
if (res == ERROR_SUCCESS && dynamorio_logdir[0] != L'\0')
return dynamorio_logdir;
else
return NULL;
}
const TCHAR *
get_dynamorio_logdir()
{
return get_dynamorio_logdir_helper(FALSE);
}
/* If a path is passed in, it is checked for 8.3 compatibility; else,
* the default path is checked. This routine does not check the
* actual 8.3 reg key.
*/
BOOL
using_system32_for_preinject(const WCHAR *preinject)
{
DWORD platform = 0;
get_platform(&platform);
if (platform == PLATFORM_WIN_NT_4) {
return TRUE;
}
else {
/* case 7586: we need to check if the system has disabled
* 8.3 names; if so, we need to use the system32 for
* preinject (since spaces are not allowed in AppInitDLLs)
*/
WCHAR short_path[MAX_PATH];
WCHAR long_path[MAX_PATH];
if (preinject == NULL) {
/* note: with force_local_path == TRUE, we don't have
* to worry about get_preinject_path() calling this
* method back, and it will always return success.
*/
get_preinject_path(short_path, MAX_PATH, TRUE, TRUE);
wcsncat(short_path, L"\\" L_EXPAND_LEVEL(INJECT_DLL_8_3_NAME),
MAX_PATH - wcslen(short_path));
NULL_TERMINATE_BUFFER(short_path);
get_preinject_path(long_path, MAX_PATH, TRUE, FALSE);
wcsncat(long_path, L"\\" L_EXPAND_LEVEL(INJECT_DLL_8_3_NAME),
MAX_PATH - wcslen(long_path));
NULL_TERMINATE_BUFFER(long_path);
} else {
/* Check the passed-in file */
GetShortPathName(preinject, short_path, BUFFER_SIZE_ELEMENTS(short_path));
NULL_TERMINATE_BUFFER(short_path);
wcsncpy(long_path, preinject, BUFFER_SIZE_ELEMENTS(long_path));
}
/* if 8.3 names are disabled, file_exists will return FALSE on
* the GetShortPathName()'ed path.
*/
return (file_exists(long_path) && !file_exists(short_path));
}
}
/* if force_local_path, then this returns the in-installation
* path regardless of using_system32_for_preinject().
* otherwise, this returns the path to the actuall DLL that
* will be injected, which depends on
* using_system32_for_preinject()
* if short_path, calls GetShortPathName() on the path before returning it.
* for a canonical preinject path, this parameter should be TRUE.
*/
DWORD
get_preinject_path(WCHAR *buf, int nchars, BOOL force_local_path, BOOL short_path)
{
if (!force_local_path && using_system32_for_preinject(NULL)) {
UINT len;
len = GetSystemDirectory(buf, MAX_PATH);
if (len == 0)
return GetLastError();
}
else {
const WCHAR *home = get_dynamorio_home();
/* using_system32_for_preinject() assumes we always succeed */
_snwprintf(buf, nchars, L"%s\\lib", home == NULL ? L"" : home);
}
buf[nchars - 1] = L'\0';
if (short_path)
GetShortPathName(buf, buf, nchars);
return ERROR_SUCCESS;
}
DWORD
get_preinject_name(WCHAR *buf, int nchars)
{
DWORD res;
if (using_system32_for_preinject(NULL)) {
wcsncpy(buf, L_EXPAND_LEVEL(INJECT_DLL_NAME), nchars);
}
else {
res = get_preinject_path(buf, nchars, FALSE, TRUE);
if (res != ERROR_SUCCESS)
return res;
wcsncat(buf, L"\\" L_EXPAND_LEVEL(INJECT_DLL_8_3_NAME),
nchars - wcslen(buf));
}
buf[nchars - 1] = L'\0';
return ERROR_SUCCESS;
}
#endif /* WINDOWS */
static dr_platform_t registry_view = DR_PLATFORM_DEFAULT;
void
set_dr_platform(dr_platform_t platform)
{
registry_view = platform;
}
dr_platform_t
get_dr_platform()
{
if (registry_view == DR_PLATFORM_64BIT
IF_X64(|| registry_view == DR_PLATFORM_DEFAULT))
return DR_PLATFORM_64BIT;
return DR_PLATFORM_32BIT;
}
#ifdef WINDOWS
DWORD
platform_key_flags()
{
/* PR 244206: have control over whether using WOW64 redirection or
* raw 64-bit registry view.
* These flags should be used for all Reg{Create,Open,Delete}KeyEx calls,
* on XP+ (invalid on earlier platforms) on redirected keys
* (most of HKLM\Software).
* The flags don't matter on non-redirected trees like HKLM\System.
* Since too many functions in libutil/ end up calling something
* that reads/writes the registry, we don't pass the dr_platform_t
* around and instead use a global variable.
*/
DWORD platform = 0;
get_platform(&platform);
if (platform == PLATFORM_WIN_NT_4 || platform == PLATFORM_WIN_2000)
return 0;
else {
switch (registry_view) {
case DR_PLATFORM_DEFAULT: return 0;
case DR_PLATFORM_32BIT: return KEY_WOW64_32KEY;
case DR_PLATFORM_64BIT: return KEY_WOW64_64KEY;
default: DO_ASSERT(false); return 0;
}
}
}
/* PR 244206: use this instead of RegDeleteKey for deleting redirected keys
* (most of HKLM\Software)
*/
DWORD
delete_product_key(HKEY hkey, LPCWSTR subkey)
{
/* RegDeleteKeyEx is only available on XP+. We cannot delete
* from 64-bit registry if we're WOW64 using RegDeleteKey, so we
* dynamically look up RegDeleteKeyEx.
* We could instead use NtDeleteKey and first open the subkey HKEY:
* we could link with the core's ntdll.c, and also use is_wow64_process().
*/
typedef DWORD (WINAPI *RegDeleteKeyExW_Type)(HKEY hKey,
LPCWSTR lpSubKey,
REGSAM samDesired,
DWORD Reserved);
static HANDLE advapi32_handle;
static RegDeleteKeyExW_Type RegDeleteKeyExW;
if (advapi32_handle == NULL)
advapi32_handle = GetModuleHandle(L"advapi32.dll");
if (RegDeleteKeyExW == NULL && advapi32_handle != NULL) {
RegDeleteKeyExW = (RegDeleteKeyExW_Type)
GetProcAddress(advapi32_handle, "RegDeleteKeyExW");
}
if (RegDeleteKeyExW == NULL) {
/* should be NT or 2K */
DO_DEBUG(DL_INFO, {
DWORD platform = 0;
get_platform(&platform);
DO_ASSERT(platform == PLATFORM_WIN_NT_4 || platform == PLATFORM_WIN_2000);
});
return RegDeleteKey(hkey, subkey);
} else
return RegDeleteKeyExW(hkey, subkey, platform_key_flags(), 0);
}
DWORD
create_root_key()
{
int res;
HKEY hkroot;
res = RegCreateKeyEx(DYNAMORIO_REGISTRY_HIVE,
L_DYNAMORIO_REGISTRY_KEY,
0,
NULL,
REG_OPTION_NON_VOLATILE,
platform_key_flags()|KEY_WRITE|KEY_ENUMERATE_SUB_KEYS,
NULL,
&hkroot,
NULL);
RegCloseKey(hkroot);
return res;
}
/* Deletes the reg key created by create_root_key/setup_installation and the parent
* company key if it's empty afterwards (might not be if PE or nodemgr has config subkeys
* there. */
DWORD
destroy_root_key()
{
DWORD res;
/* This deletes just the product key. */
res = recursive_delete_key(DYNAMORIO_REGISTRY_HIVE, L_DYNAMORIO_REGISTRY_KEY, NULL);
/* Delete the company key (this will only work if it is empty, so no need to worry
* about clobbering any config settings or doing too much damage if we screw up. */
if (res == ERROR_SUCCESS) {
WCHAR company_key[MAX_PATH];
WCHAR *pop;
wcsncpy(company_key, L_DYNAMORIO_REGISTRY_KEY, MAX_PATH);
NULL_TERMINATE_BUFFER(company_key);
pop = wcsstr(company_key, L_COMPANY_NAME);
if (pop != NULL) {
pop += wcslen(L_COMPANY_NAME);
/* sanity check */
if (pop == wcsrchr(company_key, L'\\')) {
*pop = L'\0';
delete_product_key(DYNAMORIO_REGISTRY_HIVE, company_key);
} else
res = ERROR_BAD_FORMAT;
} else
res = ERROR_BAD_FORMAT;
}
return res;
}
DWORD
setup_installation(const WCHAR *path, BOOL overwrite)
{
WCHAR buf[MAX_PATH];
/* if there's something there, leave it */
if (!overwrite && get_dynamorio_home() != NULL)
return ERROR_SUCCESS;
DO_DEBUG(DL_INFO,
printf("setting up installation at: %S\n", path);
);
mkdir_with_parents(path);
if (!file_exists(path))
return ERROR_PATH_NOT_FOUND;
_snwprintf(buf, MAX_PATH, L"%s\\%s", path, L"conf");
NULL_TERMINATE_BUFFER(buf);
DO_DEBUG(DL_INFO,
printf("making config dir: %S\n", buf);
);
mkdir_with_parents(buf);
if (!file_exists(buf))
return ERROR_PATH_NOT_FOUND;
_snwprintf(buf, MAX_PATH, L"%s\\%s", path, L"logs");
NULL_TERMINATE_BUFFER(buf);
DO_DEBUG(DL_INFO,
printf("making logdir: %S\n", buf);
);
mkdir_with_parents(buf);
if (!file_exists(buf))
return ERROR_PATH_NOT_FOUND;
CHECKED_OPERATION(create_root_key());
CHECKED_OPERATION(set_config_parameter(L_PRODUCT_NAME, FALSE,
L_DYNAMORIO_VAR_HOME, path));
CHECKED_OPERATION(set_config_parameter(L_PRODUCT_NAME, FALSE,
L_DYNAMORIO_VAR_LOGDIR, buf));
/* reset the DR_HOME cache */
get_dynamorio_home_helper(TRUE);
return ERROR_SUCCESS;
}
/* modifies permissions for 4.3 cache/User-SID directories to be
* created by users themselves
*/
DWORD
setup_cache_permissions(WCHAR *cacheRootDirectory)
{
DWORD result = ERROR_UNSUPPORTED_OS;
#define NUM_ACES 2
/* in C const int isn't good enough */
EXPLICIT_ACCESS ea[NUM_ACES];
PSID pSIDEveryone = NULL;
PSID pSIDCreatorOwner = NULL;
PACL pACL = NULL;
PACL pOldDACL = NULL;
SID_IDENTIFIER_AUTHORITY SIDAuthWorld =
SECURITY_WORLD_SID_AUTHORITY;
SID_IDENTIFIER_AUTHORITY SIDAuthCreator =
SECURITY_CREATOR_SID_AUTHORITY;
DWORD dwRes;
SECURITY_DESCRIPTOR *pSD = NULL;
DWORD platform = 0; /* accomodating NT permissions */
get_platform(&platform);
/* Note that we prefer to not create ACLs from scratch, so that we
* can accommodate Administrator groups unknown to us that would
* have been inherited from \Program Files\. We should always
* start with a known ACL and just edit the new ACEs
*/
dwRes = GetNamedSecurityInfo(cacheRootDirectory, SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION,
NULL, NULL, &pOldDACL, NULL, &pSD);
if (dwRes != ERROR_SUCCESS)
return dwRes;
/* Note: Although we are ADDING possibly existing ACE, it seems
* like this is handled well and we don't grow the ACL. For now
* this doesn't matter to us, since we expect to have just copied
* the flags from the lib\ directory so can't really accumulate.
*/
// Create a SID for the Everyone group.
if (!AllocateAndInitializeSid(&SIDAuthWorld, 1,
SECURITY_WORLD_RID,
0,
0, 0, 0, 0, 0, 0,
&pSIDEveryone)) {
DO_DEBUG(DL_VERB,
printf("AllocateAndInitializeSid (Everyone).\n");
);
goto cleanup;
}
// Create a SID for the CREATOR OWNER group
if (!AllocateAndInitializeSid(&SIDAuthCreator, 1,
SECURITY_CREATOR_OWNER_RID,
0,
0, 0, 0, 0, 0, 0,
&pSIDCreatorOwner)) {
DO_DEBUG(DL_VERB,
printf("AllocateAndInitializeSid (CreatorOwner).\n");
);
goto cleanup;
}
ZeroMemory(&ea, NUM_ACES * sizeof(EXPLICIT_ACCESS));
/* Grant create directory access to Everyone, which will be in
* addition to existing Read/Execute permissions we are starting
* with.
*/
ea[0].grfAccessPermissions = FILE_ADD_SUBDIRECTORY;
ea[0].grfAccessMode = GRANT_ACCESS; /* not SET_ACCESS */
ea[0].grfInheritance = NO_INHERITANCE; /* ONLY in cache\ folder! */
ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
ea[0].Trustee.ptstrName = (LPTSTR) pSIDEveryone;
/* Set full control for CREATOR OWNER on any subfolders */
ea[1].grfAccessPermissions = GENERIC_ALL;
ea[1].grfAccessMode = SET_ACCESS; /* we SET ALL */
if (platform == PLATFORM_WIN_NT_4) {
/* case 10502 INHERIT_ONLY_ACE seems to not work */
/* we are mostly interested in any subdirectory, and cache/ is
* already created (and also trusted), so adding it there
* doesn't affect anything.
*/
ea[1].grfInheritance =
OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE;
} else {
/* not using the same as NT, since Creator Owner may already
* have this ACE (and normally does) so we'll clutter with a
* new incomplete one */
ea[1].grfInheritance = INHERIT_ONLY_ACE |
OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE;
}
ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[1].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
ea[1].Trustee.ptstrName = (LPTSTR) pSIDCreatorOwner;
/* FIXME: we may want to disable the default group maybe should
* set CREATOR GROUP to no access otherwise we get the default
* Domain Users group (which usually is the Primary group) added,
* e.g. KRAMMER\None:R(ead)
*/
/* MSDN gave a false alarm that this doesn't exist on NT - It is
* present at least on sp6. FIXME: may want to use GetProcAddress
* if we support earlier versions, but we'll know early enough.
* We don't really need to support anything other than User SYSTEM
* on NT for which we don't need this to work and can return
* ERROR_UNSUPPORTED_OS
*/
if (ERROR_SUCCESS != SetEntriesInAcl(NUM_ACES,
ea,
pOldDACL, /* original DACL */
&pACL))
{
DO_DEBUG(DL_VERB,
printf("SetEntriesInAcl 0x%x\n", GetLastError());
);
goto cleanup;
}
// Try to modify the object's DACL.
result =
SetNamedSecurityInfo(cacheRootDirectory, // name of the object
SE_FILE_OBJECT, // type of object
DACL_SECURITY_INFORMATION |
PROTECTED_DACL_SECURITY_INFORMATION
, // change only the object's DACL
NULL, NULL, // do not change owner or group
pACL, // new DACL specified
NULL); // do not change SACL
if (ERROR_SUCCESS == result)
{
DO_DEBUG(DL_VERB,
printf("Successfully changed DACL\n");
);
}
cleanup:
if (pSIDEveryone)
FreeSid(pSIDEveryone);
if (pSIDCreatorOwner)
FreeSid(pSIDCreatorOwner);
if (pACL)
LocalFree(pACL);
if (pSD)
LocalFree(pSD);
return result;
#undef NUM_ACES
}
/* cache_root should normally be get_dynamorio_home() */
DWORD
setup_cache_shared_directories(const WCHAR *cache_root)
{
DWORD res;
/* support for new-in-4.2 directories, update the permissions
* on the cache/ to be the same as those on lib/, and the
* cache/shared/ folder to be the same as those on logs/
*
* note that the relative paths of the cache and shared cache
* directories here should match the values set in
* setup_cache_shared_registry()
*/
WCHAR libpath[MAX_PATH];
WCHAR cachepath[MAX_PATH];
WCHAR logspath[MAX_PATH];
WCHAR sharedcachepath[MAX_PATH];
_snwprintf(libpath, MAX_PATH, L"%s\\lib", get_dynamorio_home());
NULL_TERMINATE_BUFFER(libpath);
_snwprintf(cachepath, MAX_PATH, L"%s\\cache", cache_root);
NULL_TERMINATE_BUFFER(cachepath);
_snwprintf(logspath, MAX_PATH, L"%s\\logs", get_dynamorio_home());
NULL_TERMINATE_BUFFER(logspath);
_snwprintf(sharedcachepath, MAX_PATH, L"%s\\shared", cachepath);
NULL_TERMINATE_BUFFER(sharedcachepath);
mkdir_with_parents(sharedcachepath);
/* FIXME: no error checking */
res = copy_file_permissions(cachepath, libpath);
if (res != ERROR_SUCCESS) {
return res;
}
res = copy_file_permissions(sharedcachepath, logspath);
if (res != ERROR_SUCCESS) {
return res;
}
/* For in 4.3 ONLY if all users (most importantly services)
* validate their per-user directory (or files) for ownership
*/
res = setup_cache_permissions(cachepath);
if (res != ERROR_SUCCESS) {
return res;
}
return ERROR_SUCCESS;
}
/* cache_root should normally be get_dynamorio_home() */
DWORD
setup_cache_shared_registry(const WCHAR *cache_root,
ConfigGroup *policy)
{
/* note that nodemgr doesn't need to do call this routine,
* since the registry keys are added to the node policies in
* controller/servlets/PolicyUpdateResponseHandler.java in the controller. but
* anyway we expect these to be forever the same, and in any
* case not configurable from the controller.
*/
WCHAR wpathbuf [MAX_PATH];
/* set up cache\ shared\ registry keys */
_snwprintf(wpathbuf, MAX_PATH, L"%s\\cache", cache_root);
NULL_TERMINATE_BUFFER(wpathbuf);
set_config_group_parameter(policy,
L_IF_WIN(DYNAMORIO_VAR_CACHE_ROOT),
wpathbuf);
/* set up cache\ shared\ registry keys */
_snwprintf(wpathbuf, MAX_PATH, L"%s\\cache\\shared", cache_root);
NULL_TERMINATE_BUFFER(wpathbuf);
set_config_group_parameter(policy,
L_IF_WIN(DYNAMORIO_VAR_CACHE_SHARED),
wpathbuf);
return ERROR_SUCCESS;
}
/* note that this checks the opstring against the
* version of core that matches this build, NOT the version
* of the core that's actually installed! */
BOOL
check_opstring(const WCHAR *opstring)
{
char *cbuf;
options_t ops;
int res;
size_t cbuf_size = wcslen(opstring) + 1;
cbuf = (char *)malloc(cbuf_size);
/* FIXME: if malloc fails, do something */
_snprintf(cbuf, cbuf_size, "%S", opstring);
cbuf[cbuf_size-1] = '\0';
res = set_dynamo_options(&ops, cbuf);
free(cbuf);
return !res;
}
HANDLE hToken = NULL;
TOKEN_PRIVILEGES Priv, OldPriv;
DWORD PrivSize = sizeof(OldPriv);
DWORD
acquire_privileges()
{
DWORD error;
/* if the privileges are already acquired, don't bother.
this almost certainly will cause failures if multiple
threads are trying to acquire privileges.
*/
// FIXME - this should have real synchronization!!!
if (hToken != NULL)
return ERROR_ALREADY_INITIALIZED;
// get current thread token
if (!OpenThreadToken(GetCurrentThread(),
TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES,
FALSE,
&hToken)) {
// can't get thread token, try process token instead
if(!OpenProcessToken(GetCurrentProcess(),
TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES,
&hToken)) {
return GetLastError();
}
}
Priv.PrivilegeCount = 1;
Priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &Priv.Privileges[0].Luid);
// try to enable the privilege
if (!AdjustTokenPrivileges(hToken, FALSE, &Priv, sizeof(Priv),
&OldPriv, &PrivSize)) {
return GetLastError();
}
error = GetLastError();
if (error == ERROR_NOT_ALL_ASSIGNED) {
/* acquiring SeDebugPrivilege requires being admin */
return error;
}
return ERROR_SUCCESS;
}
DWORD
release_privileges()
{
if(hToken == NULL)
return ERROR_NO_SUCH_PRIVILEGE;
AdjustTokenPrivileges(hToken,
FALSE,
&OldPriv,
sizeof(OldPriv),
NULL,
NULL);
CloseHandle(hToken);
hToken = NULL;
return ERROR_SUCCESS;
}
void
wstr_replace(WCHAR *str, WCHAR orig, WCHAR new)
{
UINT i;
for (i = 0; i < wcslen(str); i++)
if (str[i] == orig)
str[i] = new;
return;
}
/* FIXME: should return error code if the directory wasn't created and
* doesn't exist already
*/
void
mkdir_with_parents(const WCHAR *dirname)
{
WCHAR buf[MAX_PATH], *temp_subdir;
wcsncpy(buf, dirname, MAX_PATH);
NULL_TERMINATE_BUFFER(buf);
/* ensure proper slashes */
wstr_replace(buf, L'/', L'\\');
temp_subdir = buf;
while (temp_subdir != NULL) {
temp_subdir = wcschr(temp_subdir, L'\\');
if (temp_subdir != NULL)
*temp_subdir = L'\0';
DO_DEBUG(DL_VERB,
printf("trying to make: %S\n", buf);
);
/* ok if this fails, eg the first time it will be C: */
CreateDirectory(buf, NULL);
if (temp_subdir != NULL) {
*temp_subdir = L'\\';
temp_subdir = temp_subdir + 1;
}
}
return;
}
void
ensure_directory_exists_for_file(WCHAR *filename)
{
WCHAR *slashptr, buf[MAX_PATH];
wcsncpy(buf, filename, MAX_PATH);
NULL_TERMINATE_BUFFER(buf);
slashptr = wcsrchr(buf, L'\\');
if (slashptr == NULL)
return;
*slashptr = L'\0';
mkdir_with_parents(buf);
}
/* FIXME: apparently there's a bug in MSVCRT that converts
* \r\n to \r\r\n ? anyway that's what google and the evidence
* seem to indicate. (see policy.c for more)
*
* so we may want to convert this to using Win32 API instead of
* CRT. but then again we may not, just on principle. */
DWORD
write_file_contents(WCHAR *path, char *contents, BOOL overwrite)
{
FILE *fp = NULL;
DWORD res = ERROR_SUCCESS;
ensure_directory_exists_for_file(path);
fp = _wfopen(path, L"r");
if (fp != NULL) {
if (!overwrite)
return ERROR_ALREADY_EXISTS;
fclose(fp);
}
fp = _wfopen(path, L"w");
if(fp == NULL) {
res = delete_file_rename_in_use(path);
if (res != ERROR_SUCCESS || (fp = _wfopen(path, L"w")) == NULL) {
DO_DEBUG(DL_ERROR,
printf("Unable to open file: %S (%d)\n", path, errno);
);
return res;
}
}
if(strlen(contents) !=
fwrite(contents, 1, strlen(contents), fp)) {
DO_DEBUG(DL_ERROR,
printf("Write failed to file: %S (errno=%d)\n",
path, errno);
);
res = ERROR_WRITE_FAULT;
}
DO_DEBUG(DL_INFO,
printf("wrote file %S\n", path);
);
fclose(fp);
return res;
}
DWORD
write_file_contents_if_different(WCHAR *path, char *contents, BOOL *changed)
{
char *existing;
DWORD res;
DO_ASSERT(path != NULL);
DO_ASSERT(contents != NULL);
DO_ASSERT(changed != NULL);
existing = (char *)malloc(strlen(contents) + 1);
res = read_file_contents(path, existing, strlen(contents) + 1, NULL);
if (res == ERROR_SUCCESS && 0 == strcmp(contents, existing)) {
*changed = FALSE;
res = ERROR_SUCCESS;
}
else {
*changed = TRUE;
res = write_file_contents(path, contents, TRUE);
}
free(existing);
return res;
}
#define READ_BUF_SZ 1024
DWORD
read_file_contents(WCHAR *path, char *contents,
SIZE_T maxchars, SIZE_T *needed)
{
FILE *fp = NULL;
DWORD res = ERROR_SUCCESS;
SIZE_T n_read = 0;
SIZE_T n_needed = 0;
char buf[READ_BUF_SZ];
DO_ASSERT(path != NULL);
DO_ASSERT(contents != NULL || needed != NULL);
DO_ASSERT(contents == NULL || maxchars > 0);
fp = _wfopen(path, L"r");
if (fp == NULL) {
DO_DEBUG(DL_INFO,
printf("Not found: %S\n", path);
);
return ERROR_FILE_NOT_FOUND;
}
if (contents != NULL) {
n_read = fread(contents, 1, maxchars, fp);
/* NULL terminate string. */
contents[n_read == maxchars ? n_read - 1 : n_read] = '\0';
DO_DEBUG(DL_FINEST,
printf("*Read %d bytes from %S (max=%d)\n",
n_read, path, maxchars);
);
}
n_needed = n_read;
while (!feof(fp)) {
res = ERROR_MORE_DATA;
n_read = fread(buf, 1, READ_BUF_SZ, fp);
DO_DEBUG(DL_FINEST,
printf(" Read an additional %d bytes\n", n_read);
);
if (n_read == 0 && !feof(fp)) {
res = ERROR_READ_FAULT;
break;
}
n_needed += n_read;
}
/* + 1 for the NULL terminator */
n_needed += 1;
if (needed != NULL)
*needed = n_needed;
fclose(fp);
if (res == ERROR_SUCCESS || res == ERROR_MORE_DATA) {
DO_DEBUG(DL_VERB,
printf("file %S contents: (%d needed)\n\n%s\n",
path, n_needed, contents);
);
}
else {
DO_DEBUG(DL_ERROR,
printf("read failed, error %d\n", res);
);
}
return res;
}
DWORD
delete_tree(const WCHAR *path)
{
WIN32_FIND_DATA data;
HANDLE hFind;
WCHAR pathbuf[MAX_PATH], subdirbuf[MAX_PATH];
if (path == NULL)
return ERROR_INVALID_PARAMETER;
_snwprintf(pathbuf, MAX_PATH, L"%s\\*.*", path);
NULL_TERMINATE_BUFFER(pathbuf);
hFind = FindFirstFile(pathbuf, &data);
if (hFind == INVALID_HANDLE_VALUE)
return GetLastError();
DO_DEBUG(DL_VERB,
printf("dt working on %S\n", path);
);
do {
if (wcscmp(data.cFileName, L".") == 0 ||
wcscmp(data.cFileName, L"..") == 0)
continue;
/* case 7407: FindNextFile on a FAT32 filesystem returns files in
* the order they were written to disk, which could be different
* from NTFS where the order is alphabetical (from MSDN).
* Also on FAT32, FindNextFile sometimes puts us back in the loop
* for the file we just renamed and we try to
* delete_file_rename_in_use the file we just renamed for a very
* long time (>3 hrs).
*
* FIXME: temporary hack: if filename has .tmp in its name
* (first ocurrance), assume we just renamed it and skip.
*
* note we may want to doublecheck that the file is indeed not
* deletable although we now add it to the
* PendingFileRenameOperations so such unused files can't stay
* around for too long.
*/
if (wcsstr(data.cFileName, L".tmp") != NULL)
continue;
DO_DEBUG(DL_VERB,
printf("dt still working on %S, %d\n", data.cFileName,
data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
);
_snwprintf(subdirbuf, MAX_PATH, L"%s\\%s", path, data.cFileName);
NULL_TERMINATE_BUFFER(subdirbuf);
/* case 4512: use rename trick if file is in use, so that
* the uninstall/reinstall case will work */
if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
delete_tree(subdirbuf);
else
delete_file_rename_in_use(subdirbuf);
}
while (FindNextFile(hFind, &data));
if (!FindClose(hFind))
return GetLastError();
if (!RemoveDirectory(path))
return GetLastError();
return ERROR_SUCCESS;
}
/*
* helper function for registry permissions workaround. stopgap until
* we can make a decent permissions api.
*
* based on example code obtained from:
* http://www.codeproject.com/system/secntobj.asp
*/
PSID
getSID(WCHAR *user)
{
DWORD dwSidLen = 0, dwDomainLen = 0;
SID_NAME_USE SidNameUse;
PSID pRet = NULL;
PSID pSid = NULL;
WCHAR *lpDomainName = NULL;
/* The function on the first call retives the length that we need
* to initialize the SID & domain name pointers */
if(!LookupAccountName(NULL, user, NULL, &dwSidLen,
NULL, &dwDomainLen, &SidNameUse)) {
if(ERROR_INSUFFICIENT_BUFFER == GetLastError()) {
pSid = LocalAlloc(LMEM_ZEROINIT, dwSidLen);
lpDomainName = LocalAlloc(LMEM_ZEROINIT,
dwDomainLen*sizeof(WCHAR));
if(pSid && lpDomainName &&
LookupAccountName(NULL, user, pSid, &dwSidLen,
lpDomainName, &dwDomainLen, &SidNameUse)) {
pRet = pSid;
pSid = NULL;
}
}
}
/* if successful, was set to NULL and left in pRet */
LocalFree(pSid);
LocalFree(lpDomainName);
return pRet;
}
BOOL
make_acl(DWORD count,
WCHAR **userArray,
DWORD *maskArray,
ACL **acl)
{
DWORD dwLoop = 0;
DWORD dwAclLen = 0;
PACL pRetAcl = NULL;
PSID *ppStoreSid = NULL;
BOOL bRes = FALSE;
if(acl == NULL)
goto cleanup;
ppStoreSid = LocalAlloc(LMEM_ZEROINIT, count * sizeof(void*));
if (ppStoreSid == NULL)
goto cleanup;
for (dwLoop = 0; dwLoop < count; dwLoop++) {
ppStoreSid[dwLoop] = getSID(userArray[dwLoop]);
if (ppStoreSid[dwLoop] == NULL)
goto cleanup;
dwAclLen += GetLengthSid(ppStoreSid[dwLoop]) +
sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD);
}
dwAclLen += sizeof(ACL);
pRetAcl = LocalAlloc(LMEM_ZEROINIT, dwAclLen);
if(pRetAcl == NULL || !InitializeAcl(pRetAcl, dwAclLen, ACL_REVISION))
goto cleanup;
for (dwLoop = 0; dwLoop < count; dwLoop++) {
/* only adding access allowed ACE's */
if(!AddAccessAllowedAce(pRetAcl, ACL_REVISION,
maskArray[dwLoop], ppStoreSid[dwLoop]))
goto cleanup;
}
*acl = pRetAcl;
pRetAcl = NULL;
bRes = TRUE;
cleanup:
if (ppStoreSid != NULL) {
for (dwLoop = 0; dwLoop < count; dwLoop++)
LocalFree(ppStoreSid[dwLoop]);
LocalFree(ppStoreSid);
}
/* if successful, was set to NULL and left in *acl */
LocalFree(pRetAcl);
return bRes;
}
#define NUM_ACL_ENTRIES 4
DWORD
set_registry_permissions_for_user(WCHAR *hklm_keyname, WCHAR *user)
{
SECURITY_DESCRIPTOR sd;
SID *owner = NULL;
ACL *acl1 = NULL;
DWORD res;
HKEY hkey = NULL;
WCHAR *users[NUM_ACL_ENTRIES] = {
L"Administrators",
L"Everyone",
L"SYSTEM",
NULL,
};
DWORD masks[NUM_ACL_ENTRIES] = {
KEY_ALL_ACCESS | DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER,
KEY_READ,
KEY_ALL_ACCESS | DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER,
KEY_ALL_ACCESS,
};
users[NUM_ACL_ENTRIES - 1] = user;
DO_DEBUG(DL_VERB,
printf("Starting acl..\n");
);
res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, hklm_keyname,
0, platform_key_flags()|KEY_ALL_ACCESS, &hkey);
if (res != ERROR_SUCCESS)
goto error_out;
DO_DEBUG(DL_VERB,
printf("Got key handle.\n");
);
if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) {
res = GetLastError();
goto error_out;
}
owner = getSID(users[0]);
if (NULL == owner) {
res = ERROR_INVALID_DATA;
goto error_out;
}
if (!SetSecurityDescriptorOwner(&sd, owner, FALSE)) {
res = GetLastError();
goto error_out;
}
DO_DEBUG(DL_VERB,
printf("Set owner.\n");
);
if (!make_acl(NUM_ACL_ENTRIES, users, masks, &acl1)) {
res = ERROR_ACCESS_DENIED;
goto error_out;
}
DO_DEBUG(DL_VERB,
printf("Made ACL.\n");
);
if(!SetSecurityDescriptorDacl(&sd, TRUE, acl1, FALSE)) {
res = GetLastError();
goto error_out;
}
if (!IsValidSecurityDescriptor(&sd)) {
res = GetLastError();
goto error_out;
}
res = RegSetKeySecurity(hkey,
DACL_SECURITY_INFORMATION |
OWNER_SECURITY_INFORMATION,
&sd);
DO_DEBUG(DL_VERB,
printf("Set sacl.\n");
);
goto cleanup;
error_out:
/* make sure to return an error */
if (res == ERROR_SUCCESS)
res = ERROR_ACCESS_DENIED;
cleanup:
if (hkey != NULL)
RegCloseKey(hkey);
LocalFree(owner);
LocalFree(acl1);
return res;
}
/* will limit to 1 MB */
#define MAX_INSERT_SIZE (1024 * 1024)
static void
insert_file(FILE *file, wchar_t *file_src_name, BOOL delete)
{
/* 3rd arg not needed but older headers do not declare as optional */
int fd_src = _wopen(file_src_name, _O_RDONLY|_O_BINARY, 0);
long length;
int error;
if (fd_src == -1) {
fprintf(file, "Unable to open file \"%S\" for inserting\n",
file_src_name);
return;
}
length = _filelength(fd_src);
if (length == -1L) {
fprintf(file, "Unable to get file length for file \"%S\"\n",
file_src_name);
return;
}
if (length > MAX_INSERT_SIZE) {
fprintf(file, "File size exceeds max insert length, truncating from %d to %d\n",
length, MAX_INSERT_SIZE);
length = MAX_INSERT_SIZE;
}
fprintf(file, "Inserting file: name=\"%S\" length=%d\n", file_src_name, length);
/* hmm, there's prob. a better way to do this ... */
#define COPY_BUF_SIZE 4096
{
char buf[COPY_BUF_SIZE] = {0};
long i = 0;
while (i + COPY_BUF_SIZE <= length) {
_read(fd_src, buf, COPY_BUF_SIZE);
fwrite(buf, 1, COPY_BUF_SIZE, file);
i += COPY_BUF_SIZE;
}
if (i < length) {
_read(fd_src, buf, length - i);
fwrite(buf, 1, length - i, file);
}
}
fprintf(file, "Finished inserting file\n");
if (_read(fd_src, &error, sizeof(error)) != 0)
fprintf(file, "ERROR : file continues beyond length\n");
_close(fd_src);
if (delete) {
DeleteFile(file_src_name);
}
return;
}
/* see utils.h for description */
DWORD
get_violation_info(EVENTLOGRECORD *pevlr, /* INOUT */ VIOLATION_INFO *info)
{
DO_ASSERT(pevlr != NULL && info != NULL && pevlr->EventID == MSG_SEC_FORENSICS);
info->report = NULL;
if (pevlr->EventID != MSG_SEC_FORENSICS)
return ERROR_INVALID_PARAMETER;
info->report = get_forensics_filename(pevlr);
if (file_exists(info->report))
return ERROR_SUCCESS;
else
return ERROR_FILE_NOT_FOUND;
}
wchar_t *canary_process_names[] = {L"canary.exe", L"services.exe", L"iexplore.exe"};
#define num_canary_processes BUFFER_SIZE_ELEMENTS(canary_process_names)
/* how long to wait for an apparently hung canary process */
#define CANARY_HANG_WAIT 20000
/* interval to wait for the canary process to do something */
#define CANARY_SLEEP_WAIT 100
#define OPTIONS_CANARY_NATIVE L" -list_modules -check_for_hooked_mods_list ntdll.dll"
#define OPTIONS_CANARY_THIN_CLIENT L""
#define OPTIONS_CANARY_CLIENT L""
#define OPTIONS_CANARY_MF L""
#define OPTIONS_CANARY_INJECT L"-wait"
/* FIXME - could even get ldmps ... */
/* FIXME - xref case 10322 on -syslog_mask 0, eventually should remove and verify
* expected eventlog output (and get PE to ignore them). */
#define OPTIONS_THIN_CLIENT L"-thin_client -syslog_mask 0"
#define OPTIONS_CLIENT L"-client -syslog_mask 0"
/* FIXME - temporary hack so virus scan correctly identified by canary. Weird case
* since this is considered a survivable violation by default (and so ignores kill proc).
*/
#define OPTIONS_MF L"-apc_policy 0 -syslog_mask 0"
/* returns the appropriate canary fail code */
static int
run_individual_canary_test(FILE *file, WCHAR *logbase, WCHAR *dr_options, int exe_index,
ConfigGroup *policy, WCHAR *exe, WCHAR *exe_args,
BOOL inject_test, char *type, BOOL early_test)
{
STARTUPINFO sinfo = {sizeof(sinfo), NULL, L"", 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, NULL, NULL, NULL, NULL};
PROCESS_INFORMATION pinfo;
int canary_code = CANARY_SUCCESS;
WCHAR logbuf[MAX_PATH] = {0};
WCHAR outfile[MAX_PATH];
WCHAR cmd_buf[5*MAX_PATH];
/* set up registry */
get_unique_filename(logbase, L"canary_logs", L"",
logbuf, BUFFER_SIZE_ELEMENTS(logbuf));
CreateDirectory(logbuf, NULL);
set_config_group_parameter(get_child(canary_process_names[exe_index], policy),
L_DYNAMORIO_VAR_LOGDIR, logbuf);
set_config_group_parameter(get_child(canary_process_names[exe_index], policy),
L_DYNAMORIO_VAR_OPTIONS, dr_options);
write_config_group(policy);
/* set up cmd_buf */
_snwprintf(outfile, BUFFER_SIZE_ELEMENTS(outfile), L"%s\\out.rep", logbuf);
NULL_TERMINATE_BUFFER(outfile);
if (early_test) {
/* we get the canary_process to re-launch itself to run test with early inject */
_snwprintf(cmd_buf, BUFFER_SIZE_ELEMENTS(cmd_buf),
L"\"%s\" \"%s\" -launch_child %s%d \"\\\"%s\\\" %s\"",
exe, outfile, inject_test ? L"-verify_inject " : L"",
CANARY_HANG_WAIT / 2, outfile, exe_args);
} else {
_snwprintf(cmd_buf, BUFFER_SIZE_ELEMENTS(cmd_buf), L"\"%s\" \"%s\" %s",
exe, outfile, exe_args);
}
NULL_TERMINATE_BUFFER(cmd_buf);
fprintf(file, "Starting Canary Process \"%S\" core_ops=\"%S\" type=%s%s\n",
cmd_buf, dr_options, type, inject_test ? " inject" : "");
if (CreateProcess(NULL, cmd_buf, NULL, NULL, TRUE, 0, NULL, NULL,
&sinfo, &pinfo)) {
if (inject_test && !early_test) {
DWORD sleep_count = 0, under_dr_code, ws, build = 0;
do {
ws = WaitForSingleObject(pinfo.hProcess, CANARY_SLEEP_WAIT);
sleep_count += CANARY_SLEEP_WAIT;
under_dr_code = under_dynamorio_ex(pinfo.dwProcessId, &build);
} while (ws == WAIT_TIMEOUT && sleep_count < CANARY_HANG_WAIT &&
(under_dr_code == DLL_UNKNOWN || under_dr_code == DLL_NONE));
if (under_dr_code == DLL_UNKNOWN || under_dr_code == DLL_NONE) {
canary_code = CANARY_FAIL_APP_INIT_INJECTION;
fprintf(file, "Injection Failed - verify registry settings\n");
} else {
fprintf(file, "Verified Injection, build %d\n", build);
}
if (ws == WAIT_TIMEOUT)
terminate_process(pinfo.dwProcessId);
} else {
DWORD ws = WaitForSingleObject(pinfo.hProcess, CANARY_HANG_WAIT);
if (ws == WAIT_TIMEOUT) {
if (early_test && inject_test) {
canary_code = CANARY_FAIL_EARLY_INJECTION;
fprintf(file, "Early Injection Failed\n");
} else {
canary_code = CANARY_FAIL_HUNG;
fprintf(file, "Canary Hung\n");
}
terminate_process(pinfo.dwProcessId);
} else {
DWORD exit_code = 0;
GetExitCodeProcess(pinfo.hProcess, &exit_code);
/* FIXME - check return value, shouldn't ever fail though */
if (exit_code != CANARY_PROCESS_EXP_EXIT_CODE) {
/* FIXME - the -1 is based on the core value for kill
* proc, should export that and use it, or really just check for
* violations since we'll want the forensics anyways. Doesn't
* disambiguate between dr error and violation. */
if (exit_code == (DWORD)-1) {
canary_code = CANARY_FAIL_VIOLATION;
fprintf(file, "Canary Violation or DR error\n");
} else {
canary_code = CANARY_FAIL_CRASH;
fprintf(file, "Canary Crashed 0x%08x\n", exit_code);
}
} else if (early_test && inject_test) {
fprintf(file, "Verified Early Injection\n");
}
}
}
CloseHandle(pinfo.hProcess);
CloseHandle(pinfo.hThread);
{
HANDLE hFind;
WIN32_FIND_DATA data;
WCHAR file_name[MAX_PATH], pattern[MAX_PATH];
_snwprintf(pattern, BUFFER_SIZE_ELEMENTS(pattern), L"%s\\*.*", logbuf);
NULL_TERMINATE_BUFFER(pattern);
hFind = FindFirstFile(pattern, &data);
if (hFind != INVALID_HANDLE_VALUE) {
do {
if (wcscmp(data.cFileName, L".") == 0 ||
wcscmp(data.cFileName, L"..") == 0)
continue;
_snwprintf(file_name, BUFFER_SIZE_ELEMENTS(file_name), L"%s\\%s",
logbuf, data.cFileName);
NULL_TERMINATE_BUFFER(file_name);
insert_file(file, file_name, FALSE);
} while (FindNextFile(hFind, &data));
FindClose(hFind);
}
}
fprintf(file, "Canary Finished\n");
} else {
fprintf(file, "Canary \"%S\" Failed to Launch\n", cmd_buf);
}
return canary_code;
}
#pragma warning( disable : 4127) //conditional expression is constant i.e while (FALSE)
/* see utils.h for description */
BOOL
run_canary_test_ex(FILE *file, /* INOUT */ CANARY_INFO *info,
const WCHAR *scratch_folder, const WCHAR *canary_process)
{
ConfigGroup *policy, *save_policy;
WCHAR exe_buf[num_canary_processes][MAX_PATH];
WCHAR log_folder[MAX_PATH];
DWORD i;
BOOL autoinject_set = is_autoinjection_set();
info->canary_code = ERROR_SUCCESS;
info->url = L"CFail";
info->msg = L"Canary Failed";
read_config_group(&save_policy, L_PRODUCT_NAME, TRUE);
save_policy->should_clear = TRUE;
read_config_group(&policy, L_PRODUCT_NAME, TRUE);
policy->should_clear = TRUE;
remove_children(policy);
_snwprintf(log_folder, BUFFER_SIZE_ELEMENTS(log_folder),
L"%s\\canary_logs", scratch_folder);
NULL_TERMINATE_BUFFER(log_folder);
CreateDirectory(log_folder, NULL);
for (i = 0; i < num_canary_processes; i++) {
_snwprintf(exe_buf[i], BUFFER_SIZE_ELEMENTS(exe_buf[i]), L"%s\\%s",
scratch_folder, canary_process_names[i]);
NULL_TERMINATE_BUFFER(exe_buf[i]);
if (CopyFile(canary_process, exe_buf[i], FALSE) == 0) {
fprintf(file, "Failed to copy canary file %S to %S\n",
canary_process, exe_buf[i]);
/* FIXME- continue if file exists from a previous run that didn't clean up */
info->canary_code = CANARY_UNABLE_TO_TEST;
goto canary_exit;
}
add_config_group(policy, new_config_group(canary_process_names[i]));
set_config_group_parameter(get_child(canary_process_names[i], policy),
L_DYNAMORIO_VAR_RUNUNDER, L"1");
}
write_config_group(policy);
/* FIXME - monitor eventlog though we should still detect via forensics and/or
* exit code (crash/violation). Xref 10322, for now we suppress eventlogs. */
/* FIXME - the verify injection tests need work, should just talk to canary proc. */
/* FIXME - verify canary output - necessary? not clear what action would be */
/* Files are copied, begin runs */
#define DO_RUN(run_flag, core_ops, canary_options, inject, run_name, test_type) do { \
if (TEST(run_flag, info->run_flags)) { \
WCHAR *canary_ops = TEST(run_flag, info->fault_run) ? \
info->canary_fault_args : canary_options; \
for (i = 0; i < num_canary_processes; i++) { \
int code = \
run_individual_canary_test(file, log_folder, core_ops, i, policy, \
exe_buf[i], canary_ops, inject, \
run_name, FALSE /* not early */); \
if (code >= 0 && test_type != CANARY_TEST_TYPE_NATIVE) { \
code = \
run_individual_canary_test(file, log_folder, core_ops, i, policy, \
exe_buf[i], canary_ops, inject, \
run_name, TRUE /* early inject*/); \
} \
if (code < 0) { \
if (CANARY_RUN_REQUIRES_PASS(run_flag, info->run_flags)) { \
info->canary_code = GET_CANARY_CODE(test_type, code); \
goto canary_exit; \
} \
break; /* skip remaining tests in run once first failure found */ \
} \
} \
} \
} while (FALSE)
/* First the native runs. */
unset_autoinjection();
/* native info gathering run. */
DO_RUN(CANARY_RUN_NATIVE, L"", OPTIONS_CANARY_NATIVE,
FALSE, "native", CANARY_TEST_TYPE_NATIVE);
set_autoinjection(); /* Going to do the non-native runs now */
/* Now the -thin_client inject run */
DO_RUN(CANARY_RUN_THIN_CLIENT_INJECT, OPTIONS_THIN_CLIENT, OPTIONS_CANARY_INJECT,
TRUE, "-thin_client", CANARY_TEST_TYPE_THIN_CLIENT);
/* now the full -thin_client run */
DO_RUN(CANARY_RUN_THIN_CLIENT, OPTIONS_THIN_CLIENT, OPTIONS_CANARY_THIN_CLIENT,
FALSE, "-thin_client", CANARY_TEST_TYPE_THIN_CLIENT);
/* Now the -client run */
DO_RUN(CANARY_RUN_CLIENT, OPTIONS_CLIENT, OPTIONS_CANARY_CLIENT,
FALSE, "-client", CANARY_TEST_TYPE_CLIENT);
/* Now the MF run */
DO_RUN(CANARY_RUN_MF, OPTIONS_MF, OPTIONS_CANARY_MF,
FALSE, "MF", CANARY_TEST_TYPE_MF);
#undef DO_RUN
canary_exit:
if (autoinject_set)
set_autoinjection();
else
unset_autoinjection();
free_config_group(policy);
write_config_group(save_policy);
free_config_group(save_policy);
fprintf(file, "Canary code 0x%08x\n", info->canary_code);
if (info->canary_code >= 0) {
info->url = L"ctest";
info->msg = L"Canary success";
}
return (info->canary_code >= 0);
}
/* see utils.h for description */
BOOL
run_canary_test(/* INOUT */ CANARY_INFO *info, WCHAR *version_msg)
{
BOOL result;
DWORD res;
FILE *report_file;
WCHAR scratch_folder[MAX_PATH], canary_process[MAX_PATH];
const WCHAR *dynamorio_home = get_dynamorio_home();
const WCHAR *dynamorio_logdir = get_dynamorio_logdir();
_snwprintf(canary_process, BUFFER_SIZE_ELEMENTS(canary_process),
L"%s\\bin\\canary.exe", dynamorio_home);
NULL_TERMINATE_BUFFER(canary_process);
_snwprintf(scratch_folder, BUFFER_SIZE_ELEMENTS(scratch_folder),
L"%s\\canary_test", dynamorio_logdir);
NULL_TERMINATE_BUFFER(scratch_folder);
/* xref case 10157, let's try to make sure this stays clean */
delete_tree(scratch_folder);
CreateDirectory(scratch_folder, NULL);
/* FIXME - verify directory created */
/* Using get unique file name since we plan to run this more then once,
* though only an issue if the caller doesn't cleanup the report file and
* leaves it locked. */
get_unique_filename(dynamorio_logdir, L"canary_report", L".crep",
info->buf_report, BUFFER_SIZE_ELEMENTS(info->buf_report));
info->report = info->buf_report;
report_file = _wfopen(info->report, L"wb");
/* FIXME - verify file creation */
fprintf(report_file, "%S\n", version_msg == NULL ? L"unknown version" : version_msg);
result = run_canary_test_ex(report_file, info, scratch_folder, canary_process);
res = delete_tree(scratch_folder);
fprintf(report_file, "Deleted scratch folder \"%S\", code %d\n",
scratch_folder, res);
fclose(report_file);
return result;
}
#endif /* WINDOWS */
#else //ifdef UNIT_TEST
int
main()
{
set_debuglevel(DL_INFO);
set_abortlevel(DL_WARN);
/* read/write file */
{
char *test1, *test2;
char buffy[1024];
WCHAR *fn = L"utils.tst";
SIZE_T needed;
BOOL changed;
test1 = "This is a stupid file.\r\n\r\nDon't you think?\r\n";
test2 = "foo\r\n";
CHECKED_OPERATION(write_file_contents(fn, test1, TRUE));
DO_ASSERT(ERROR_MORE_DATA == read_file_contents(fn, NULL, 0, &needed));
DO_ASSERT(strlen(test1) + 1 == needed);
CHECKED_OPERATION(read_file_contents(fn, buffy, needed, NULL));
DO_ASSERT(0 == strcmp(test1, buffy));
CHECKED_OPERATION(write_file_contents_if_different(fn, test1,
&changed));
DO_ASSERT(!changed);
CHECKED_OPERATION(write_file_contents_if_different(fn, test2,
&changed));
DO_ASSERT(changed);
CHECKED_OPERATION(read_file_contents(fn, buffy, 1024, NULL));
DO_ASSERT(0 == strcmp(test2, buffy));
}
/* file existence */
{
WCHAR *fn = L"tester-file";
DeleteFile(fn);
DO_ASSERT(!file_exists(fn));
DO_ASSERT(!file_exists(fn));
CHECKED_OPERATION(write_file_contents(fn, "testing", TRUE));
DO_ASSERT(file_exists(fn));
DeleteFile(fn);
DO_ASSERT(file_exists(L"C:\\"));
DO_ASSERT(!file_exists(L"%%RY:\\\\zZsduf"));
}
/* mkdir_with_parents / delete_tree */
{
delete_tree(L"__foo_test");
mkdir_with_parents(L"__foo_test");
DO_ASSERT(file_exists(L"__foo_test"));
mkdir_with_parents(L"__foo_test\\foo\\bar\\goo");
DO_ASSERT(file_exists(L"__foo_test\\foo\\bar\\goo"));
mkdir_with_parents(L"__foo_test/lib/bar/goo/dood");
DO_ASSERT(file_exists(L"__foo_test\\lib\\bar\\goo\\dood"));
CHECKED_OPERATION(delete_tree(L"__foo_test"));
DO_ASSERT(!file_exists(L"__foo_test"));
DO_ASSERT(!file_exists(L"__foo_test\\foo\\bar\\goo"));
DO_ASSERT(!file_exists(L"__foo_test\\lib\\bar\\goo\\dood"));
}
/* setup_installation */
{
CHECKED_OPERATION(setup_installation(L"C:\\", TRUE));
CHECKED_OPERATION(setup_installation(L"C:\\foobarra", FALSE));
DO_ASSERT_WSTR_EQ(L"C:\\", get_dynamorio_home());
CHECKED_OPERATION(setup_installation(L"C:\\foobarra", TRUE));
DO_ASSERT_WSTR_EQ(L"C:\\foobarra", get_dynamorio_home());
}
{
WCHAR piname[MAX_PATH];
BOOL bres = using_system32_for_preinject(NULL);
printf("Using SYSTEM32 for preinject: %s\n", bres ? "TRUE" : "FALSE");
CHECKED_OPERATION(get_preinject_name(piname, MAX_PATH));
printf("Preinject name: %S\n", piname);
}
printf("All Test Passed\n");
return 0;
}
#endif