/* ********************************************************** * Copyright (c) 2011-2026 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 "drlibc.h" #include #include #include #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 /* for canary */ # include /* for canary */ # include #else # include #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; } /* * XXX: 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 stat64 st; /* Use the raw syscall to avoid glibc 2.33 deps (i#5474). */ return dr_stat_syscall(fn, &st) == 0; # endif } # ifdef WINDOWS # define MAX_COUNTER 999999 /* grokked from the core. * XXX: 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")); /* i#1598: on any error or on unknown ver, best to assume it's a new ver * and will look most like the most recent known ver. We'll still return error * return val below, but many callers don't check that (!). */ *platform = PLATFORM_WIN_10; 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; } } else if (osinfo.dwMajorVersion == 10) { if (osinfo.dwMinorVersion == 0) { if (GetProcAddress((HMODULE)ntdll_handle, "NtAllocateVirtualMemoryEx") != NULL) *platform = PLATFORM_WIN_10_1803; else if (GetProcAddress((HMODULE)ntdll_handle, "NtCallEnclave") != NULL) *platform = PLATFORM_WIN_10_1709; else if (GetProcAddress((HMODULE)ntdll_handle, "NtLoadHotPatch") != NULL) *platform = PLATFORM_WIN_10_1703; else if (GetProcAddress((HMODULE)ntdll_handle, "NtCreateRegistryTransaction") != NULL) *platform = PLATFORM_WIN_10_1607; else if (GetProcAddress((HMODULE)ntdll_handle, "NtCreateEnclave") != NULL) *platform = PLATFORM_WIN_10_1511; else *platform = PLATFORM_WIN_10; 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(void) { 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; /* XXX: 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. XXX: 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); /* XXX: 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); /* XXX: 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. */ // XXX - 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; } /* XXX: 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); } /* XXX: 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 %zu bytes from %S (max=%zu)\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 %zu 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: (%zu 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). * * XXX: 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" /* XXX - could even get ldmps ... */ /* XXX - 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" /* XXX - 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); /* XXX - check return value, shouldn't ever fail though */ if (exit_code != CANARY_PROCESS_EXP_EXIT_CODE) { /* XXX - 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]); /* XXX- 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); /* XXX - monitor eventlog though we should still detect via forensics and/or * exit code (crash/violation). Xref 10322, for now we suppress eventlogs. */ /* XXX - the verify injection tests need work, should just talk to canary proc. */ /* XXX - 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 (TESTANY(run_flag, info->run_flags)) { \ WCHAR *canary_ops = TESTANY(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); /* XXX - 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"); /* XXX - 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