* Copyright (c) 2010-2021 Google, Inc. All rights reserved.
* Copyright (c) 2000-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.
*/
* os.c - win32 specific routines
*/
#include "../globals.h"
#include "../fragment.h"
#include "../fcache.h"
#include "ntdll.h"
#include "os_private.h"
#include "../nudge.h"
#include "../moduledb.h"
#include "../hotpatch.h"
#ifdef DEBUG
# include "../vmareas.h"
#endif
#include "../dispatch.h"
#include "instrument.h"
#include <windows.h>
#include <stddef.h>
#include "events.h"
#include "aslr.h"
#include "../synch.h"
#include "../perscache.h"
#include "../native_exec.h"
#ifdef NOT_DYNAMORIO_CORE_PROPER
# undef ASSERT
# undef ASSERT_NOT_IMPLEMENTED
# undef ASSERT_NOT_TESTED
# undef ASSERT_CURIOSITY_ONCE
# define ASSERT(x)
# define ASSERT_NOT_IMPLEMENTED(x)
# define ASSERT_NOT_TESTED(x)
# define ASSERT_CURIOSITY_ONCE(x)
# undef LOG
# define LOG(x, ...)
#else
# ifdef DEBUG
DECLARE_CXTSWPROT_VAR(static mutex_t snapshot_lock, INIT_LOCK_FREE(snapshot_lock));
# endif
DECLARE_CXTSWPROT_VAR(static mutex_t dump_core_lock, INIT_LOCK_FREE(dump_core_lock));
DECLARE_CXTSWPROT_VAR(static mutex_t debugbox_lock, INIT_LOCK_FREE(debugbox_lock));
* prior to its final syscall, yet the wow64 syscall writes to the
* stack b/c it makes a call, so we have a race that can lead to a
* hang or worse. we do not expect the syscall to return, so we can
* use a global single-entry stack (the wow64 layer swaps to a
* different stack: presumably for alignment and other reasons).
* We also use this for non-wow64, except on win8 wow64 where we need
* a per-thread stack and we use the TEB.
* We do leave room to store the 2 args to NtTerminateProcess for win8 wow64
* in case we can't get the target thread's TEB.
*/
# define WOW64_SYSCALL_SETUP_SIZE \
3 * XSP_SZ
# define WOW64_SYSCALL_STACK_SIZE 2 * XSP_SZ + (WOW64_SYSCALL_SETUP_SIZE)
DECLARE_NEVERPROT_VAR(static byte wow64_syscall_stack_array[WOW64_SYSCALL_STACK_SIZE],
{ 0 });
const byte *wow64_syscall_stack =
&wow64_syscall_stack_array[WOW64_SYSCALL_STACK_SIZE - WOW64_SYSCALL_SETUP_SIZE];
bool intercept_asynch = false;
bool intercept_callbacks = false;
process_id_t win32_pid = 0;
void *peb_ptr;
static int os_version;
static uint os_service_pack_major;
static uint os_service_pack_minor;
static uint os_build_number;
# define REGISTRY_VERSION_STRING_MAX_LEN 64
static char os_release_id[REGISTRY_VERSION_STRING_MAX_LEN];
static char os_edition[REGISTRY_VERSION_STRING_MAX_LEN];
static const char *os_name;
static char os_name_buf[MAXIMUM_PATH];
app_pc vsyscall_page_start = NULL;
app_pc vsyscall_after_syscall = NULL;
app_pc vsyscall_syscall_end_pc = NULL;
DECLARE_CXTSWPROT_VAR(static volatile int dynamo_detaching_flag, LOCK_FREE_STATE);
app_pc wow64_syscall_call_tgt;
# ifdef PROFILE_RDTSC
uint kilo_hertz;
# endif
# define HEAP_INITIAL_SIZE 1024 * 1024
app_pc dynamo_dll_start = NULL;
app_pc dynamo_dll_end = NULL;
static app_pc dynamo_dll_preferred_base = NULL;
enum { TLS_UNINITIALIZED = (ushort)0U };
static ushort tls_local_state_offs = TLS_UNINITIALIZED;
static ushort tls_dcontext_offs = TLS_UNINITIALIZED;
app_pc parent_early_inject_address = NULL;
* dr_early_injected_location is the location (if any) that the current
* process was injected at */
static int early_inject_location = INJECT_LOCATION_Invalid;
static app_pc early_inject_address = NULL;
static app_pc ldrpLoadDll_address_not_NT = NULL;
static app_pc ldrpLoadDll_address_NT = NULL;
static app_pc ldrpLoadImportModule_address = NULL;
dcontext_t *early_inject_load_helper_dcontext = NULL;
static char cwd[MAXIMUM_PATH];
static void
get_system_basic_info(void);
static bool
is_child_in_thin_client(HANDLE process_handle);
static const char *
get_process_SID_string(void);
static const PSID
get_Everyone_SID(void);
static const PSID
get_process_owner_SID(void);
static size_t
get_allocation_size_ex(HANDLE process, byte *pc, byte **base_pc);
static void
os_take_over_init(void);
static void
os_take_over_exit(void);
bool
os_user_directory_supports_ownership(void);
* frame (i.e. the entry point to that function), returns NULL on failure.
* NOTE - Be aware this routine may be called by DllMain before dr is
* initialized (before even syscalls_init, though note that safe_read should
* be fine as will just use the nt wrapper). */
static app_pc
get_nth_stack_frames_call_target(int num_frames, reg_t *ebp)
{
reg_t *cur_ebp = ebp;
reg_t next_frame[2];
int i;
for (i = 0; i < num_frames; i++) {
if (!d_r_safe_read(cur_ebp, sizeof(next_frame), next_frame))
break;
cur_ebp = (reg_t *)next_frame[0];
}
if (i == num_frames) {
* call address of the call that targeted this frame */
* not even be initialized yet and this is safer */
byte buf[5];
if (d_r_safe_read((byte *)(next_frame[1] - sizeof(buf)), sizeof(buf), &buf) &&
buf[0] == CALL_REL32_OPCODE) {
app_pc return_point = (app_pc)next_frame[1];
return (return_point + *(int *)&buf[1]);
}
}
return NULL;
}
* pointing to the just mapped region. */
void
check_for_ldrpLoadImportModule(byte *base, uint *ebp)
{
MEMORY_BASIC_INFORMATION mbi;
if (query_virtual_memory(base, &mbi, sizeof(mbi)) == sizeof(mbi) &&
mbi.Type == MEM_IMAGE && is_readable_pe_base(base)) {
const char *name;
bool match = false;
name = get_dll_short_name(base);
if (name != NULL) {
LOG(GLOBAL, LOG_TOP, 1, "early_inject hit mapview of image %s\n", name);
string_option_read_lock();
* strcmp (vs. strcasecmp), just to keep things tight */
match = strcmp(DYNAMO_OPTION(early_inject_helper_name), name) == 0;
string_option_read_unlock();
}
if (match) {
* (in NtMapViewOfSection)
* ntdll!LdrpMapDll
* ntdll!LdrpLoadImportModule (what we want)
* After that don't really care (is one of the
* Ldrp*ImportDescriptor* routines. So we walk the
* stack back and get the desired address.
* FIXME - would be nice if we had some way to double
* check this address, could try to decode and check against
* the versions we've seen.
* Note that NtMapViewOfSection in all its various platform forms
* (i.e. int, vsyscall, KiFastSystemCall etc.) doesn't set up a
* new frame (nor do its callees) so will always be depth 2
*/
# define STACK_DEPTH_LdrpLoadImportModule 2
ldrpLoadImportModule_address = get_nth_stack_frames_call_target(
STACK_DEPTH_LdrpLoadImportModule, (reg_t *)ebp);
LOG(GLOBAL, LOG_TOP, 1,
"early_inject found address " PFX " for LdrpLoadImportModule\n",
ldrpLoadImportModule_address);
}
}
}
** DllMain Routines
**
**/
# ifdef INTERNAL
void
DllMainThreadAttach()
{
if (INTERNAL_OPTION(noasynch) && dynamo_initialized && !dynamo_exited) {
* noasynch, we do it here (which is later, but better than nothing)
*/
LOG(GLOBAL, LOG_TOP | LOG_THREADS, 1,
"DllMain: initializing new thread " TIDFMT "\n", d_r_get_thread_id());
dynamo_thread_init(NULL, NULL, NULL, false);
}
}
# endif
DECLARE_FREQPROT_VAR(static bool do_once_DllMain, false);
* N.B.: dynamo interprets this routine!
*/
* so we cannot optimize it (i#566)
*/
START_DO_NOT_OPTIMIZE
bool WINAPI
DllMain(HANDLE hModule, DWORD reason_for_call, LPVOID Reserved)
{
switch (reason_for_call) {
case DLL_PROCESS_ATTACH:
* after all the statically-bound dlls are loaded (the loader
* blindly calls all the init routines regardless of whether a dll
* was explicitly loaded and already had its init routine called).
* We make that 2nd time a nop via a custom DO_ONCE (since default
* DO_ONCE will try to unprotect .data, but we're pre-init).
*/
if (!do_once_DllMain) {
byte *cur_ebp;
do_once_DllMain = true;
ASSERT(!dynamo_initialized);
ASSERT(ldrpLoadDll_address_NT == NULL);
ASSERT(ldrpLoadDll_address_not_NT == NULL);
* protect from app etc., but also can't check options. */
GET_FRAME_PTR(cur_ebp);
* stack to look like (for win2k and higher)
* here (our DllMain)
* ntdll!LdrpCallInitRoutine
* ntdll!LdrpRunInitializeRoutines
* ntdll!LdrpLoadDll
* ntdll!LdrLoadDll
* For NT is the same only doesn't have ntdll!LdrpCallInitRoutine.
*
* That's as far we care, after that is likely to be shimeng.dll
* or kernel32 (possibly someone else?) depending on how we were
* injected. For -early_inject, ntdll!LdrGetProcedureAddress is
* usually the root of the call to our DLLMain (likely something
* to do with load vs. init order at process startup? FIXME
* understand better, is there a flag we can send to have this
* called on load?), but in that case we use the address passed to
* us by the parent. */
# define STACK_DEPTH_LdrpLoadDll_NT 3
# define STACK_DEPTH_LdrpLoadDll 4
* so just grab both possible LdrpLoadDll addresses (NT and non NT)
* and we'll sort it out later in early_inject_init(). */
ldrpLoadDll_address_NT = get_nth_stack_frames_call_target(
STACK_DEPTH_LdrpLoadDll_NT, (reg_t *)cur_ebp);
ldrpLoadDll_address_not_NT = get_nth_stack_frames_call_target(
STACK_DEPTH_LdrpLoadDll, (reg_t *)cur_ebp);
* but after this frame there are too many possibilites (many
* of which are unexported) so is hard to find something we
* can check. */
} else
ASSERT(dynamo_initialized);
break;
# ifdef INTERNAL
case DLL_THREAD_ATTACH: DllMainThreadAttach(); break;
# endif
}
return true;
}
END_DO_NOT_OPTIMIZE
# ifdef WINDOWS_PC_SAMPLE
static profile_t *global_profile = NULL;
static profile_t *dynamo_dll_profile = NULL;
static profile_t *ntdll_profile = NULL;
file_t profile_file = INVALID_FILE;
DECLARE_CXTSWPROT_VAR(mutex_t profile_dump_lock, INIT_LOCK_FREE(profile_dump_lock));
static void
get_dll_bounds(wchar_t *name, app_pc *start, app_pc *end)
{
module_handle_t dllh;
size_t len;
PBYTE pb;
MEMORY_BASIC_INFORMATION mbi;
dllh = get_module_handle(name);
ASSERT(dllh != NULL);
pb = (PBYTE)dllh;
len = query_virtual_memory(pb, &mbi, sizeof(mbi));
ASSERT(len == sizeof(mbi));
ASSERT(mbi.State != MEM_FREE);
*start = (app_pc)mbi.AllocationBase;
do {
if (mbi.State == MEM_FREE || (app_pc)mbi.AllocationBase != *start)
break;
if (POINTER_OVERFLOW_ON_ADD(pb, mbi.RegionSize))
break;
pb += mbi.RegionSize;
} while (query_virtual_memory(pb, &mbi, sizeof(mbi)) == sizeof(mbi));
*end = (app_pc)pb;
}
static void
init_global_profiles()
{
app_pc start, end;
DOLOG(1, LOG_ALL, {
char buf[MAX_PATH];
uint size = BUFFER_SIZE_ELEMENTS(buf);
if (get_log_dir(PROCESS_DIR, buf, &size)) {
NULL_TERMINATE_BUFFER(buf);
strncat(buf, "\\profile", BUFFER_SIZE_ELEMENTS(buf) - strlen(buf));
NULL_TERMINATE_BUFFER(buf);
profile_file = os_open(buf, OS_OPEN_REQUIRE_NEW | OS_OPEN_WRITE);
LOG(GLOBAL, LOG_PROFILE, 1, "Profile file is \"%s\"\n", buf);
}
});
if (profile_file == INVALID_FILE) {
get_unique_logfile(".profile", NULL, 0, false, &profile_file);
}
DOLOG(1, LOG_PROFILE, {
if (profile_file == INVALID_FILE)
LOG(GLOBAL, LOG_PROFILE, 1, "Failed to create profile file\n");
});
ASSERT(profile_file != INVALID_FILE);
print_version_and_app_info(profile_file);
* 1221 gets set to 1221 on laptop was different minimum value, at least
* appears that os sets it close as possible to the requested (starting
* value was 39021 for me) */
LOG(GLOBAL, LOG_PROFILE, 1, "Profile interval was %d, setting to %d,",
nt_query_profile_interval(), dynamo_options.prof_pcs_freq);
nt_set_profile_interval(dynamo_options.prof_pcs_freq);
LOG(GLOBAL, LOG_PROFILE, 1, " is now %d (units of 100 nanoseconds)\n",
nt_query_profile_interval());
print_file(profile_file, "Interval %d\n\n", nt_query_profile_interval());
* allows us to separate kernel and user space (even in the case
* of 3GB user space). Note if invalid range given we default to
* 30, so we always have a global profile to use as denominator
* later.
*/
global_profile = create_profile(UNIVERSAL_REGION_BASE, UNIVERSAL_REGION_END,
DYNAMO_OPTION(prof_pcs_global), NULL);
if (dynamo_options.prof_pcs_DR >= 2 && dynamo_options.prof_pcs_DR <= 32) {
get_dll_bounds(L_DYNAMORIO_LIBRARY_NAME, &start, &end);
dynamo_dll_profile = create_profile(start, end, dynamo_options.prof_pcs_DR, NULL);
}
if (dynamo_options.prof_pcs_ntdll >= 2 && dynamo_options.prof_pcs_ntdll <= 32) {
get_dll_bounds(L"ntdll.dll", &start, &end);
ntdll_profile = create_profile(start, end, dynamo_options.prof_pcs_ntdll, NULL);
}
start_profile(global_profile);
if (dynamo_dll_profile)
start_profile(dynamo_dll_profile);
if (ntdll_profile)
start_profile(ntdll_profile);
}
static void
dump_dll_profile(profile_t *profile, uint global_sum, char *dll_name)
{
uint dll_sum;
uint top = 0, bottom = 0;
dll_sum = sum_profile(profile);
if (global_sum > 0)
divide_uint64_print(dll_sum, global_sum, true, 2, &top, &bottom);
print_file(profile_file, "\nDumping %s profile\n%d hits out of %d, %u.%.2u%%\n",
dll_name, dll_sum, global_sum, top, bottom);
LOG(GLOBAL, LOG_PROFILE, 1, "%s profile had %d hits out of %d total, %u.%.2u%%\n",
dll_name, dll_sum, global_sum, top, bottom);
dump_profile(profile_file, profile);
free_profile(profile);
}
static void
exit_global_profiles()
{
int global_sum;
if (dynamo_dll_profile)
stop_profile(dynamo_dll_profile);
if (ntdll_profile)
stop_profile(ntdll_profile);
stop_profile(global_profile);
global_sum = sum_profile(global_profile);
FIXME: we can remove the mutex_lock/unlock then */
d_r_mutex_lock(&profile_dump_lock);
if (dynamo_dll_profile)
dump_dll_profile(dynamo_dll_profile, global_sum, "dynamorio.dll");
if (ntdll_profile)
dump_dll_profile(ntdll_profile, global_sum, "ntdll.dll");
print_file(profile_file, "\nDumping global profile\n%d hits\n", global_sum);
dump_profile(profile_file, global_profile);
d_r_mutex_unlock(&profile_dump_lock);
LOG(GLOBAL, LOG_PROFILE, 1, "\nDumping global profile\n%d hits\n", global_sum);
DOLOG(1, LOG_PROFILE, dump_profile(GLOBAL, global_profile););
free_profile(global_profile);
DELETE_LOCK(profile_dump_lock);
}
# endif
**
****************************************************************************/
static uint
get_context_xstate_flag(void)
{
* win7sp1+ both should be 0x40.
*/
if (YMM_ENABLED()) {
* but we expect OS support only on Win7 SP1+.
* XXX: what about the WINDOWS Server 2008 R2?
*/
ASSERT_CURIOSITY(os_version >= WINDOWS_VERSION_8 ||
(os_version == WINDOWS_VERSION_7 && os_service_pack_major >= 1));
return (IF_X64_ELSE(CONTEXT_AMD64, CONTEXT_i386) | 0x40L);
}
return IF_X64_ELSE((CONTEXT_AMD64 | 0x20L), (CONTEXT_i386 | 0x40L));
}
static bool
read_version_registry_value(const wchar_t *name, char *value OUT, size_t value_sz)
{
reg_query_value_result_t result;
char buf_array[sizeof(KEY_VALUE_PARTIAL_INFORMATION) +
sizeof(wchar_t) * (MAX_REGISTRY_PARAMETER + 1)];
KEY_VALUE_PARTIAL_INFORMATION *kvpi = (KEY_VALUE_PARTIAL_INFORMATION *)buf_array;
result = reg_query_value(L"\\Registry\\Machine\\SOFTWARE\\Microsoft\\"
L"Windows NT\\CurrentVersion",
name, KeyValuePartialInformation, kvpi,
BUFFER_SIZE_BYTES(buf_array), 0);
if (result == REG_QUERY_SUCCESS) {
snprintf(value, value_sz, "%*ls", kvpi->DataLength / sizeof(wchar_t) - 1,
(wchar_t *)kvpi->Data);
value[value_sz - 1] = '\0';
return true;
}
value[0] = '\0';
return false;
}
* error reporting syscalls going through wrappers and requiring this
* init routine, we'll have to have a fallback here that dynamically
* determines the syscalls and finishes init, and then reports the error.
* We may never be able to report errors for the non-NT OS family.
* N.B.: this is too early for LOGs so don't do any -- any errors reported
* will not die, they will simply skip LOG.
* N.B.: This is before stderr_mask has been parsed, so don't print any
* informational-only messages, or tests will break.
* N.B.: this is prior to eventlog_init(), but then we've been reporting
* usage errors prior to that for a long time now anyway.
*/
bool
windows_version_init(int num_GetContextThread, int num_AllocateVirtualMemory)
{
PEB *peb = get_own_peb();
* one version of windows to the next!
* they may even change at different patch levels)
*/
syscalls = NULL;
DOCHECK(1, { check_syscall_array_sizes(); });
* stored in peb->OSCSDVersion, major in the top byte:
*/
os_service_pack_major = (peb->OSCSDVersion & 0xff00) >> 8;
os_service_pack_minor = (peb->OSCSDVersion & 0xff);
char buf[64];
if (read_version_registry_value(L"CurrentBuild", buf, BUFFER_SIZE_ELEMENTS(buf))) {
if (sscanf(buf, "%u", &os_build_number) != 1) {
SYSLOG_INTERNAL_WARNING("Failed to parse CurrentBuild '%s'", buf);
}
}
read_version_registry_value(L"EditionId", os_edition,
BUFFER_SIZE_ELEMENTS(os_edition));
read_version_registry_value(L"ReleaseId", os_release_id,
BUFFER_SIZE_ELEMENTS(os_release_id));
ASSERT(REGISTRY_VERSION_STRING_MAX_LEN >=
sizeof(((dr_os_version_info_t *)0)->release_id));
ASSERT(REGISTRY_VERSION_STRING_MAX_LEN >=
sizeof(((dr_os_version_info_t *)0)->edition));
if (peb->OSPlatformId == VER_PLATFORM_WIN32_NT) {
* handling code below to use the most recent enum and arrays.
*/
if (peb->OSMajorVersion == 10 && peb->OSMinorVersion == 0) {
* of newly added syscalls to distinguish major updates.
*/
if (d_r_get_proc_address(get_ntdll_base(), "NtAllocateVirtualMemoryEx") !=
NULL) {
if (module_is_64bit(get_ntdll_base())) {
syscalls = (int *)windows_10_1803_x64_syscalls;
os_name = "Microsoft Windows 10-1803 x64";
} else if (is_wow64_process(NT_CURRENT_PROCESS)) {
syscalls = (int *)windows_10_1803_wow64_syscalls;
os_name = "Microsoft Windows 10-1803 x64";
} else {
syscalls = (int *)windows_10_1803_x86_syscalls;
os_name = "Microsoft Windows 10-1803";
}
os_version = WINDOWS_VERSION_10_1803;
} else if (d_r_get_proc_address(get_ntdll_base(), "NtCallEnclave") != NULL) {
if (module_is_64bit(get_ntdll_base())) {
syscalls = (int *)windows_10_1709_x64_syscalls;
os_name = "Microsoft Windows 10-1709 x64";
} else if (is_wow64_process(NT_CURRENT_PROCESS)) {
syscalls = (int *)windows_10_1709_wow64_syscalls;
os_name = "Microsoft Windows 10-1709 x64";
} else {
syscalls = (int *)windows_10_1709_x86_syscalls;
os_name = "Microsoft Windows 10-1709";
}
os_version = WINDOWS_VERSION_10_1709;
} else if (d_r_get_proc_address(get_ntdll_base(), "NtLoadHotPatch") != NULL) {
if (module_is_64bit(get_ntdll_base())) {
syscalls = (int *)windows_10_1703_x64_syscalls;
os_name = "Microsoft Windows 10-1703 x64";
} else if (is_wow64_process(NT_CURRENT_PROCESS)) {
syscalls = (int *)windows_10_1703_wow64_syscalls;
os_name = "Microsoft Windows 10-1703 x64";
} else {
syscalls = (int *)windows_10_1703_x86_syscalls;
os_name = "Microsoft Windows 10-1703";
}
os_version = WINDOWS_VERSION_10_1703;
} else if (d_r_get_proc_address(get_ntdll_base(),
"NtCreateRegistryTransaction") != NULL) {
if (module_is_64bit(get_ntdll_base())) {
syscalls = (int *)windows_10_1607_x64_syscalls;
os_name = "Microsoft Windows 10-1607 x64";
} else if (is_wow64_process(NT_CURRENT_PROCESS)) {
syscalls = (int *)windows_10_1607_wow64_syscalls;
os_name = "Microsoft Windows 10-1607 x64";
} else {
syscalls = (int *)windows_10_1607_x86_syscalls;
os_name = "Microsoft Windows 10-1607";
}
os_version = WINDOWS_VERSION_10_1607;
} else if (d_r_get_proc_address(get_ntdll_base(), "NtCreateEnclave") !=
NULL) {
if (module_is_64bit(get_ntdll_base())) {
syscalls = (int *)windows_10_1511_x64_syscalls;
os_name = "Microsoft Windows 10-1511 x64";
} else if (is_wow64_process(NT_CURRENT_PROCESS)) {
syscalls = (int *)windows_10_1511_wow64_syscalls;
os_name = "Microsoft Windows 10-1511 x64";
} else {
syscalls = (int *)windows_10_1511_x86_syscalls;
os_name = "Microsoft Windows 10-1511";
}
os_version = WINDOWS_VERSION_10_1511;
} else {
if (module_is_64bit(get_ntdll_base())) {
syscalls = (int *)windows_10_x64_syscalls;
os_name = "Microsoft Windows 10 x64";
} else if (is_wow64_process(NT_CURRENT_PROCESS)) {
syscalls = (int *)windows_10_wow64_syscalls;
os_name = "Microsoft Windows 10 x64";
} else {
syscalls = (int *)windows_10_x86_syscalls;
os_name = "Microsoft Windows 10";
}
os_version = WINDOWS_VERSION_10;
}
* 10.0.sp0, so we have to use syscall #'s to distinguish.
* We check 2 different numbers currently toward the end of the
* list in order to handle hooks on one of them and to handle
* more weird reorderings.
*/
if ((num_GetContextThread != -1 &&
num_GetContextThread != syscalls[SYS_GetContextThread]) ||
(num_AllocateVirtualMemory != -1 &&
num_AllocateVirtualMemory != syscalls[SYS_AllocateVirtualMemory])) {
syscalls = NULL;
}
} else if (peb->OSMajorVersion == 6 && peb->OSMinorVersion == 3) {
if (module_is_64bit(get_ntdll_base())) {
syscalls = (int *)windows_81_x64_syscalls;
os_name = "Microsoft Windows 8.1 x64";
} else if (is_wow64_process(NT_CURRENT_PROCESS)) {
syscalls = (int *)windows_81_wow64_syscalls;
os_name = "Microsoft Windows 8.1 x64";
} else {
syscalls = (int *)windows_81_x86_syscalls;
os_name = "Microsoft Windows 8.1";
}
os_version = WINDOWS_VERSION_8_1;
} else if (peb->OSMajorVersion == 6 && peb->OSMinorVersion == 2) {
if (module_is_64bit(get_ntdll_base())) {
syscalls = (int *)windows_8_x64_syscalls;
os_name = "Microsoft Windows 8 x64";
} else if (is_wow64_process(NT_CURRENT_PROCESS)) {
syscalls = (int *)windows_8_wow64_syscalls;
os_name = "Microsoft Windows 8 x64";
} else {
syscalls = (int *)windows_8_x86_syscalls;
os_name = "Microsoft Windows 8";
}
os_version = WINDOWS_VERSION_8;
} else if (peb->OSMajorVersion == 6 && peb->OSMinorVersion == 1) {
module_handle_t ntdllh = get_ntdll_base();
if (os_service_pack_major >= 1) {
ASSERT(d_r_get_proc_address(ntdllh, "RtlCopyContext") != NULL);
if (module_is_64bit(get_ntdll_base()) ||
is_wow64_process(NT_CURRENT_PROCESS)) {
syscalls = (int *)windows_7_x64_syscalls;
os_name = "Microsoft Windows 7 x64 SP1";
} else {
syscalls = (int *)windows_7_syscalls;
os_name = "Microsoft Windows 7 SP1";
}
} else {
ASSERT(d_r_get_proc_address(ntdllh, "RtlCopyContext") == NULL);
if (module_is_64bit(get_ntdll_base()) ||
is_wow64_process(NT_CURRENT_PROCESS)) {
syscalls = (int *)windows_7_x64_syscalls;
os_name = "Microsoft Windows 7 x64 SP0";
} else {
syscalls = (int *)windows_7_syscalls;
os_name = "Microsoft Windows 7 SP0";
}
}
os_version = WINDOWS_VERSION_7;
} else if (peb->OSMajorVersion == 6 && peb->OSMinorVersion == 0) {
module_handle_t ntdllh = get_ntdll_base();
if (os_service_pack_major >= 1) {
* the existence of NtReplacePartitionUnit as a sanity check
* for sp1 - see PR 246402. They also differ for
* 32-bit vs 64-bit/wow64.
*/
ASSERT(d_r_get_proc_address(ntdllh, "NtReplacePartitionUnit") != NULL);
if (module_is_64bit(get_ntdll_base()) ||
is_wow64_process(NT_CURRENT_PROCESS)) {
syscalls = (int *)windows_vista_sp1_x64_syscalls;
os_name = "Microsoft Windows Vista x64 SP1";
} else {
syscalls = (int *)windows_vista_sp1_syscalls;
os_name = "Microsoft Windows Vista SP1";
}
} else {
ASSERT(d_r_get_proc_address(ntdllh, "NtReplacePartitionUnit") == NULL);
if (module_is_64bit(get_ntdll_base()) ||
is_wow64_process(NT_CURRENT_PROCESS)) {
syscalls = (int *)windows_vista_sp0_x64_syscalls;
os_name = "Microsoft Windows Vista x64 SP0";
} else {
syscalls = (int *)windows_vista_sp0_syscalls;
os_name = "Microsoft Windows Vista SP0";
}
}
os_version = WINDOWS_VERSION_VISTA;
} else if (peb->OSMajorVersion == 5 && peb->OSMinorVersion == 2) {
if (module_is_64bit(get_ntdll_base()) ||
is_wow64_process(NT_CURRENT_PROCESS)) {
* numbers but that has not been verified. System call numbers
* remain the same even under WOW64 (ignoring the extra WOW
* system calls, anyway). We do not split the version for WOW
* as most users do not care to distinguish; those that do must
* use a separate is_wow64_process() check.
*/
syscalls = (int *)windows_XP_x64_syscalls;
os_version = WINDOWS_VERSION_2003;
os_name = "Microsoft Windows x64 XP/2003";
} else {
syscalls = (int *)windows_2003_syscalls;
os_version = WINDOWS_VERSION_2003;
os_name = "Microsoft Windows 2003";
}
} else if (peb->OSMajorVersion == 5 && peb->OSMinorVersion == 1) {
syscalls = (int *)windows_XP_syscalls;
os_version = WINDOWS_VERSION_XP;
os_name = "Microsoft Windows XP";
} else if (peb->OSMajorVersion == 5 && peb->OSMinorVersion == 0) {
syscalls = (int *)windows_2000_syscalls;
os_version = WINDOWS_VERSION_2000;
os_name = "Microsoft Windows 2000";
} else if (peb->OSMajorVersion == 4) {
module_handle_t ntdllh = get_ntdll_base();
os_version = WINDOWS_VERSION_NT;
* Rather than reading the registry to find the service pack we
* directly check which system calls are there. We don't just
* check the number of the last syscall in our list b/c we want to
* avoid issues w/ hookers.
* We rely on these observations:
* SP3: + Nt{Read,Write}FileScatter
* SP4: - NtW32Call
*/
if (d_r_get_proc_address(ntdllh, "NtW32Call") != NULL) {
* or possibly are different from both, but we don't support them
*/
if (d_r_get_proc_address(ntdllh, "NtReadFileScatter") != NULL) {
syscalls = (int *)windows_NT_sp3_syscalls;
os_name = "Microsoft Windows NT SP3";
} else {
syscalls = (int *)windows_NT_sp0_syscalls;
os_name = "Microsoft Windows NT SP0";
}
} else {
syscalls = (int *)windows_NT_sp4_syscalls;
os_name = "Microsoft Windows NT SP4, 5, 6, or 6a";
}
}
if (syscalls == NULL) {
if (peb->OSMajorVersion == 10 && peb->OSMinorVersion == 0) {
if (os_release_id[0] != '\0') {
snprintf(os_name_buf, BUFFER_SIZE_ELEMENTS(os_name_buf) - 1,
"Microsoft Windows 10-%s%s", os_release_id,
(module_is_64bit(get_ntdll_base()) ||
is_wow64_process(NT_CURRENT_PROCESS))
? " x64"
: "");
NULL_TERMINATE_BUFFER(os_name_buf);
os_name = os_name_buf;
* have been parsed.
*/
} else {
os_name = "Unknown Windows 10+ version";
SYSLOG_INTERNAL_WARNING("WARNING: Running on unknown "
"Windows 10+ version");
}
} else {
SYSLOG_INTERNAL_ERROR("Unknown Windows NT-family version: %d.%d",
peb->OSMajorVersion, peb->OSMinorVersion);
os_name = "Unknown Windows NT-family version";
}
if (dynamo_options.max_supported_os_version <
peb->OSMajorVersion * 10 + peb->OSMinorVersion) {
if (standalone_library)
return false;
FATAL_USAGE_ERROR(BAD_OS_VERSION, 4, get_application_name(),
get_application_pid(), PRODUCT_NAME, os_name);
}
* First, we copy the latest numbers (mostly for SYSCALL_NOT_PRESENT).
* Then in syscalls_init() we try to update with real numbers from
* the wrappers (best-effort, modulo hooks).
*/
syscalls = windows_unknown_syscalls;
if (module_is_64bit(get_ntdll_base())) {
memcpy(syscalls, windows_10_1803_x64_syscalls,
SYS_MAX * sizeof(syscalls[0]));
} else if (is_wow64_process(NT_CURRENT_PROCESS)) {
memcpy(syscalls, windows_10_1803_wow64_syscalls,
SYS_MAX * sizeof(syscalls[0]));
} else {
memcpy(syscalls, windows_10_1803_x86_syscalls,
SYS_MAX * sizeof(syscalls[0]));
}
os_version = WINDOWS_VERSION_10_1803;
}
} else if (peb->OSPlatformId == VER_PLATFORM_WIN32_WINDOWS) {
uint ver_high = (peb->OSBuildNumber >> 8) & 0xff;
uint ver_low = peb->OSBuildNumber & 0xff;
if (standalone_library)
return false;
if (ver_low >= 90 || ver_high >= 5)
os_name = "Windows ME";
else if (ver_low >= 10 && ver_low < 90)
os_name = "Windows 98";
else if (ver_low < 5)
os_name = "Windows 3.1 / WfWg";
else if (ver_low < 10)
os_name = "Windows 98";
else
os_name = "this unknown version of Windows";
FATAL_USAGE_ERROR(BAD_OS_VERSION, 4, get_application_name(),
get_application_pid(), PRODUCT_NAME, os_name);
} else {
if (standalone_library)
return false;
os_name = "Win32s";
FATAL_USAGE_ERROR(BAD_OS_VERSION, 4, get_application_name(),
get_application_pid(), PRODUCT_NAME, os_name);
}
return true;
}
* been created - with ZwAssignProcessToJobObject(), and we may start
* before or after that has been done.
*/
static void
print_mem_quota()
{
QUOTA_LIMITS qlimits;
NTSTATUS res = get_process_mem_quota(NT_CURRENT_PROCESS, &qlimits);
if (!NT_SUCCESS(res)) {
ASSERT(false && "print_mem_quota");
return;
}
LOG(GLOBAL, LOG_TOP, 1, "Process Memory Limits:\n");
LOG(GLOBAL, LOG_TOP, 1, "\t Paged pool limit: %6d KB\n",
qlimits.PagedPoolLimit / 1024);
LOG(GLOBAL, LOG_TOP, 1, "\t Non Paged pool limit: %6d KB\n",
qlimits.NonPagedPoolLimit / 1024);
LOG(GLOBAL, LOG_TOP, 1, "\t Minimum working set size: %6d KB\n",
qlimits.MinimumWorkingSetSize / 1024);
LOG(GLOBAL, LOG_TOP, 1, "\t Maximum working set size: %6d KB\n",
qlimits.MaximumWorkingSetSize / 1024);
LOG(GLOBAL, LOG_TOP, 1, "\t Pagefile limit: %6d KB\n",
qlimits.PagefileLimit / 1024);
* Resource Manager) can definitely set, so expected to be
* supported on Win2003. Time in 100ns units.
*/
LOG(GLOBAL, LOG_TOP, 1, "\t TimeLimit: 0x%.8x%.8x\n", qlimits.TimeLimit.HighPart,
qlimits.TimeLimit.LowPart);
}
void
d_r_os_init(void)
{
PEB *peb = get_own_peb();
uint alignment = 0;
uint offs;
int res;
DEBUG_DECLARE(bool ok;)
if (dynamo_options.max_supported_os_version <
peb->OSMajorVersion * 10 + peb->OSMinorVersion) {
SYSLOG(SYSLOG_WARNING, UNSUPPORTED_OS_VERSION, 3, get_application_name(),
get_application_pid(), os_name);
}
if (peb->OSMajorVersion == 10 && peb->OSMinorVersion == 0 &&
syscalls == windows_unknown_syscalls && !standalone_library && os_name != NULL) {
* syscalls (in the absense of hooks, for which we might want
* a solution like Dr. Memory's: i#2713).
*/
SYSLOG_INTERNAL_INFO("Running on newer-than-this-build \"%s\"", os_name);
}
* multi-threaded and is no longer safe to do so on demand, this also
* takes care of initializing the static buffer get_appilication_name
* and get_application_pid */
debugbox_setup_title();
win32_pid = get_process_id();
LOG(GLOBAL, LOG_TOP, 1, "Process id: %d\n", win32_pid);
peb_ptr = (void *)get_own_peb();
LOG(GLOBAL, LOG_TOP, 1, "PEB: " PFX "\n", peb_ptr);
ASSERT((PEB *)peb_ptr == get_own_teb()->ProcessEnvironmentBlock);
# ifndef X64
* let's print it nonetheless
*/
DOLOG(1, LOG_TOP, {
if (is_wow64_process(NT_CURRENT_PROCESS)) {
uint64 peb64 = (ptr_uint_t)get_own_x64_peb();
LOG(GLOBAL, LOG_TOP, 1, "x64 PEB: " UINT64_FORMAT_STRING "\n", peb64);
}
});
# endif
ASSERT(EXCEPTION_LIST_TIB_OFFSET == offsetof(TEB, ExceptionList));
ASSERT(TOP_STACK_TIB_OFFSET == offsetof(TEB, StackBase));
ASSERT(BASE_STACK_TIB_OFFSET == offsetof(TEB, StackLimit));
ASSERT(FIBER_DATA_TIB_OFFSET == offsetof(TEB, FiberData));
ASSERT(SELF_TIB_OFFSET == offsetof(TEB, Self));
ASSERT(TID_TIB_OFFSET == offsetof(TEB, ClientId) + offsetof(CLIENT_ID, UniqueThread));
ASSERT(PID_TIB_OFFSET ==
offsetof(TEB, ClientId) + offsetof(CLIENT_ID, UniqueProcess));
ASSERT(ERRNO_TIB_OFFSET == offsetof(TEB, LastErrorValue));
ASSERT(WOW64_TIB_OFFSET == offsetof(TEB, WOW32Reserved));
ASSERT(PEB_TIB_OFFSET == offsetof(TEB, ProcessEnvironmentBlock));
ASSERT(syscalls != NULL);
LOG(GLOBAL, LOG_TOP, 1, "Running on %s == %d SP%d.%d\n", os_name, os_version,
os_service_pack_major, os_service_pack_minor);
context_xstate = get_context_xstate_flag();
ntdll_init();
callback_init();
syscall_interception_init();
eventlog_init();
if (os_version >= WINDOWS_VERSION_XP) {
* the 1st sysenter...solution for now is to hardcode initial values so
* we pass the 1st PROGRAM_SHEPHERDING code origins test, then re-set these once
* we see the 1st syscall.
*/
* routines in ntdll.dll FIXME : as a hack for now will leave
* page_start as 0 (as it would be for 2000, since region is
* executable no need for the code origins exception) and
* after syscall to the appropriate value, this means will still
* execute the return natively (as in xp/03) for simplicity even
* though we could intercept it much more easily than before since
* the ki routines are aligned (less concern about enough space for the
* interception stub, nicely exported for us etc.)
*/
module_handle_t ntdllh = get_ntdll_base();
app_pc return_point = (app_pc)d_r_get_proc_address(ntdllh, "KiFastSystemCallRet");
if (return_point != NULL) {
app_pc syscall_pc = (app_pc)d_r_get_proc_address(ntdllh, "KiFastSystemCall");
vsyscall_after_syscall = (app_pc)return_point;
* initial value to what it should be for go-native scenarios
* where we may not see the 1st syscall (DrMem i#1235).
*/
if (syscall_pc != NULL)
vsyscall_syscall_end_pc = syscall_pc + SYSENTER_LENGTH;
else
vsyscall_syscall_end_pc = NULL;
} else {
* security hole for the followin page */
vsyscall_page_start = VSYSCALL_PAGE_START_BOOTSTRAP_VALUE;
vsyscall_after_syscall = VSYSCALL_AFTER_SYSCALL_BOOTSTRAP_VALUE;
vsyscall_syscall_end_pc = vsyscall_after_syscall;
}
}
if (DYNAMO_OPTION(tls_align) == 0) {
IF_X64(ASSERT_TRUNCATE(alignment, uint, proc_get_cache_line_size()));
alignment = (uint)proc_get_cache_line_size();
} else {
alignment = DYNAMO_OPTION(tls_align);
}
* and case 6670 about TLS conflict in SQL2005 */
* we should be able to restructure the state to ensure first
* entry is indeed important. Should make sure we choose same
* position in both release and debug, see local_state_t.stats.
*/
res = tls_calloc(false , &offs, TLS_NUM_SLOTS, alignment);
DODEBUG({
if (!res) {
SYSLOG_INTERNAL_ERROR("Cannot allocate %d tls slots at %d alignment",
TLS_NUM_SLOTS, alignment);
}
});
if (!res) {
alignment = 0;
ASSERT_NOT_TESTED();
res = tls_calloc(false , &offs, TLS_NUM_SLOTS, alignment);
if (!res) {
ASSERT_NOT_TESTED();
SYSLOG_INTERNAL_ERROR("Cannot allocate %d tls slots at %d alignment",
TLS_NUM_SLOTS, alignment);
report_dynamorio_problem(NULL, DUMPCORE_INTERNAL_EXCEPTION, NULL, NULL,
"Unrecoverable error on TLS allocation", NULL, NULL,
NULL);
}
}
ASSERT(res);
ASSERT(offs != TLS_UNINITIALIZED);
ASSERT_TRUNCATE(tls_local_state_offs, ushort, offs);
tls_local_state_offs = (ushort)offs;
LOG(GLOBAL, LOG_TOP, 1, "%d TLS slots are @ %s:0x%x\n", TLS_NUM_SLOTS,
IF_X64_ELSE("gs", "fs"), tls_local_state_offs);
ASSERT_CURIOSITY(proc_is_cache_aligned(get_local_state()) ||
DYNAMO_OPTION(tls_align != 0));
if (IF_UNIT_TEST_ELSE(true, !standalone_library)) {
tls_dcontext_offs = os_tls_offset(TLS_DCONTEXT_SLOT);
ASSERT(tls_dcontext_offs != TLS_UNINITIALIZED);
}
DOLOG(1, LOG_VMAREAS, { print_modules(GLOBAL, DUMP_NOT_XML); });
DOLOG(2, LOG_TOP, { print_mem_quota(); });
# ifdef WINDOWS_PC_SAMPLE
if (dynamo_options.profile_pcs)
init_global_profiles();
# endif
# ifdef PROFILE_RDTSC
if (dynamo_options.profile_times) {
ASSERT_NOT_TESTED();
kilo_hertz = get_timer_frequency();
LOG(GLOBAL, LOG_TOP | LOG_STATS, 1, "CPU MHz is %d\n", kilo_hertz / 1000);
}
# endif
if (!dr_early_injected && !dr_earliest_injected)
inject_init();
get_dynamorio_library_path();
* path needs to be be preserved before hiding from module list.
*/
aslr_init();
* because it may not be safe to get this information later */
get_own_qualified_name();
get_own_unqualified_name();
get_own_short_qualified_name();
get_own_short_unqualified_name();
get_application_name();
get_application_short_name();
get_application_short_unqualified_name();
get_process_primary_SID();
get_process_SID_string();
get_process_owner_SID();
get_Everyone_SID();
get_dynamorio_dll_preferred_base();
get_image_entry();
get_application_base();
get_application_end();
get_system_basic_info();
if (!standalone_library)
os_user_directory_supports_ownership();
is_wow64_process(NT_CURRENT_PROCESS);
is_in_ntdll(get_ntdll_base());
# ifndef X64
nt_get_context64_size();
# endif
os_take_over_init();
* We just don't support later changes to cur dir.
*/
DEBUG_DECLARE(ok =)
os_get_current_dir(cwd, BUFFER_SIZE_ELEMENTS(cwd));
}
static void
print_mem_stats()
{
VM_COUNTERS mem;
bool ok = get_process_mem_stats(NT_CURRENT_PROCESS, &mem);
ASSERT(ok);
LOG(GLOBAL, LOG_TOP, 1, "Process Memory Statistics:\n");
LOG(GLOBAL, LOG_TOP, 1, "\tPeak virtual size: %6d KB\n",
mem.PeakVirtualSize / 1024);
LOG(GLOBAL, LOG_TOP, 1, "\tPeak working set size: %6d KB\n",
mem.PeakWorkingSetSize / 1024);
LOG(GLOBAL, LOG_TOP, 1, "\tPeak paged pool usage: %6d KB\n",
mem.QuotaPeakPagedPoolUsage / 1024);
LOG(GLOBAL, LOG_TOP, 1, "\tPeak non-paged pool usage: %6d KB\n",
mem.QuotaPeakNonPagedPoolUsage / 1024);
LOG(GLOBAL, LOG_TOP, 1, "\tPeak pagefile usage: %6d KB\n",
mem.PeakPagefileUsage / 1024);
}
* note that this is called even on the fast exit release path so don't add
* uneccesary cleanup without ifdef DEBUG, but be careful with ifdef DEBUG's
* also as Detach wants to leave nothing from us behind
* Called by dynamo_shared_exit() and the fast path in dynamo_process_exit().
*/
void
os_fast_exit(void)
{
* DllMain()'s -- unlikely, even .NET apps have an image entry
* just to jump to mscoree
*
* The curiosity is relaxed for thin_client and hotp_only; if nothing else
* in the core is has run into this, then reached_image_entry doesn't have
* to be set for thin_client & hotp_only. TODO: put in the image entry
* hook or not?
*
* The curiosity is also relaxed if we enter DR using the API
*/
ASSERT_CURIOSITY(reached_image_entry_yet() || standalone_library ||
RUNNING_WITHOUT_CODE_CACHE() IF_APP_EXPORTS(|| dr_api_entry)
|| CLIENTS_EXIST());
DOLOG(1, LOG_TOP, { print_mem_quota(); });
DOLOG(1, LOG_TOP, { print_mem_stats(); });
os_take_over_exit();
# ifdef WINDOWS_PC_SAMPLE
if (dynamo_options.profile_pcs) {
exit_global_profiles();
* if so dump profiles that were skipped */
# ifndef DEBUG
if (dynamo_detaching_flag == LOCK_FREE_STATE) {
if (dynamo_options.prof_pcs_gencode >= 2 &&
dynamo_options.prof_pcs_gencode <= 32) {
thread_record_t **threads;
int num, i;
arch_profile_exit();
d_r_mutex_lock(&thread_initexit_lock);
get_list_of_threads(&threads, &num);
for (i = 0; i < num; i++) {
arch_thread_profile_exit(threads[i]->dcontext);
}
global_heap_free(
threads, num * sizeof(thread_record_t *) HEAPACCT(ACCT_THREAD_MGT));
d_r_mutex_unlock(&thread_initexit_lock);
}
if (dynamo_options.prof_pcs_fcache >= 2 &&
dynamo_options.prof_pcs_fcache <= 32) {
* we are here on fast exit path in which case fcache_exit()
* is not called
*/
fcache_profile_exit();
}
if (dynamo_options.prof_pcs_stubs >= 2 &&
dynamo_options.prof_pcs_stubs <= 32) {
special_heap_profile_exit();
}
}
# endif
print_file(profile_file, "\nFinished dumping all profile info\n");
close_file(profile_file);
}
# endif
eventlog_fast_exit();
# ifdef DEBUG
module_info_exit();
DELETE_LOCK(snapshot_lock);
# endif
* are other active threads: we don't want to synchall on exit so
* we let other threads run and try not to crash them until
* the process is terminated.
*/
DELETE_LOCK(dump_core_lock);
DELETE_LOCK(debugbox_lock);
callback_exit();
ntdll_exit();
}
* us behind. In addition any debug cleanup should only be DODEBUG.
* Called by dynamo_shared_exit().
* Note it is expected to be called _after_ os_fast_exit().
*/
void
os_slow_exit(void)
{
DEBUG_DECLARE(int res =)
tls_cfree(true , (uint)tls_local_state_offs, TLS_NUM_SLOTS);
ASSERT(res);
syscall_interception_exit();
aslr_exit();
eventlog_slow_exit();
tls_dcontext_offs = TLS_UNINITIALIZED;
}
* put the args on the actual stack. We could have multiple threads
* writing to these same slots so we use the TEB which should be dead
* (unless the syscall fails and the app continues: which we do not handle).
* Xref i#565.
*/
byte *
os_terminate_wow64_stack(HANDLE thread_handle)
{
# ifdef X64
return (byte *)wow64_syscall_stack;
# else
if (syscall_uses_edx_param_base())
return (byte *)wow64_syscall_stack;
else {
TEB *teb;
if (thread_handle == INVALID_HANDLE_VALUE)
teb = get_own_teb();
else
teb = get_teb(thread_handle);
if (teb == NULL)
return (byte *)wow64_syscall_stack;
* call*'s retaddr below and 2 args for os_terminate_wow64_write_args()
* above, so we take our own xbx slot, which has xax below and xcx+xdx
* above. We do not have the extra safety slot that wow64_syscall_stack
* has, but that's not necessary, and if the wow64 wrapper wrote to it
* it would just be writing to an app slot that's likely unused (b/c DR
* takes TLS slots from the end).
*
* XXX: it would be cleaner to not write to this until we're done
* cleaning up private libraries, which examine the TEB.
* Then we could use any part of the TEB.
*
* XXX: we rely here on os_slow_exit()'s tls_cfree() not zeroing out
* our TLS slots during cleanup (i#1156).
*/
return (byte *)teb + os_tls_offset(TLS_XBX_SLOT);
}
# endif
}
void
os_terminate_wow64_write_args(bool exit_process, HANDLE proc_or_thread_handle,
int exit_status)
{
# ifndef X64
if (!syscall_uses_edx_param_base()) {
byte *xsp = os_terminate_wow64_stack(exit_process ? INVALID_HANDLE_VALUE
: proc_or_thread_handle);
ASSERT(ALIGNED(xsp, sizeof(reg_t)));
*(((reg_t *)xsp) + 1) = (reg_t)proc_or_thread_handle;
*(((reg_t *)xsp) + 2) = (reg_t)exit_status;
}
# endif
}
# define KILL_PROC_EXIT_STATUS -1
# define KILL_THREAD_EXIT_STATUS -1
static byte *
os_terminate_static_arguments(bool exit_process, bool custom_code, int exit_code)
{
byte *arguments;
typedef struct _terminate_args_t {
union {
const byte *debug_code;
byte pad_bytes[SYSCALL_PARAM_MAX_OFFSET];
} padding;
struct {
IN HANDLE ProcessOrThreadHandle;
IN NTSTATUS ExitStatus;
} args;
} terminate_args_t;
* We need to stick the arguments for NtTerminate* in a place that
* doesn't exacerbate the problem - esp may have been in attacker's
* hands - so we place args in const static (read only) dr memory.
*/
* retaddr at edx (two extra slots there) as esp will be set to edx
* by the kernel at the return from the sysenter. The kernel then sends
* control to a native ret which targets the debug infinite loop.
* (DEBUG only).
*/
static const terminate_args_t term_thread_args = {
IF_DEBUG_ELSE_0((byte *)debug_infinite_loop),
{ NT_CURRENT_THREAD, KILL_THREAD_EXIT_STATUS }
};
static const terminate_args_t term_proc_args = {
IF_DEBUG_ELSE_0((byte *)debug_infinite_loop),
{ NT_CURRENT_PROCESS, KILL_PROC_EXIT_STATUS }
};
static terminate_args_t sygate_term_thread_args = {
0,
{ NT_CURRENT_THREAD, KILL_THREAD_EXIT_STATUS }
};
static terminate_args_t sygate_term_proc_args = {
0,
{ NT_CURRENT_PROCESS, KILL_PROC_EXIT_STATUS }
};
static terminate_args_t custom_term_proc_args = {
IF_DEBUG_ELSE_0((byte *)debug_infinite_loop),
{ NT_CURRENT_PROCESS, KILL_PROC_EXIT_STATUS }
};
* offset below */
if (exit_process) {
if (custom_code) {
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
ATOMIC_4BYTE_WRITE((byte *)&custom_term_proc_args.args.ExitStatus, exit_code,
false);
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
arguments = (byte *)&custom_term_proc_args;
} else if (DYNAMO_OPTION(sygate_sysenter) &&
get_syscall_method() == SYSCALL_METHOD_SYSENTER) {
byte *tgt = (byte *)&sygate_term_proc_args;
* always with the same value so is ok */
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
ATOMIC_ADDR_WRITE(tgt, sysenter_ret_address, false);
DODEBUG({
ATOMIC_ADDR_WRITE(tgt + sizeof(sysenter_ret_address),
(byte *)debug_infinite_loop, false);
});
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
arguments = (byte *)&sygate_term_proc_args;
} else
arguments = (byte *)&term_proc_args;
} else {
if (DYNAMO_OPTION(sygate_sysenter) &&
get_syscall_method() == SYSCALL_METHOD_SYSENTER) {
byte *tgt = (byte *)&sygate_term_thread_args;
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
ATOMIC_ADDR_WRITE(tgt, sysenter_ret_address, false);
DODEBUG({
tgt += sizeof(sysenter_ret_address);
ATOMIC_ADDR_WRITE(tgt, (byte *)debug_infinite_loop, false);
});
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
arguments = (byte *)&sygate_term_thread_args;
} else
arguments = (byte *)&term_thread_args;
}
LOG(THREAD_GET, LOG_SYSCALLS, 2,
"Placing terminate arguments tombstone at " PFX " offset=0x%x\n", arguments,
SYSCALL_PARAM_OFFSET());
os_terminate_wow64_write_args(
exit_process, ((terminate_args_t *)arguments)->args.ProcessOrThreadHandle,
((terminate_args_t *)arguments)->args.ExitStatus);
arguments += offsetof(terminate_args_t, args) - SYSCALL_PARAM_OFFSET();
return arguments;
}
*/
void
os_terminate_common(dcontext_t *dcontext, terminate_flags_t terminate_type,
bool custom_code, int exit_code)
{
HANDLE currentThreadOrProcess = NT_CURRENT_PROCESS;
bool exit_process = true;
ASSERT(TEST(TERMINATE_PROCESS, terminate_type) !=
TEST(TERMINATE_THREAD, terminate_type));
* we will get a rank order violation with all_threads_synch_lock. if we
* unlock the bb lock, we risk an error about the non-owning thread
* releasing the lock.
* Our solution is for the caller to release it when possible --
* on an attack we know if we hold it or not. But for other callers
* they do not know who holds it...for now we do nothing, none of them
* terminate just a thread, so the process is going down anyway, and it's
* just a debug build assertion :)
*/
if (terminate_type == TERMINATE_PROCESS) {
* certainly already done so for a syslog */
if (TESTANY(DETACH_ON_TERMINATE | DETACH_ON_TERMINATE_NO_CLEAN,
DYNAMO_OPTION(internal_detach_mask))) {
* here though caller has likely alredy gone as deep as detach
* will since almost everyone SYSLOG's before calling this */
detach_helper(
TEST(DETACH_ON_TERMINATE_NO_CLEAN, DYNAMO_OPTION(internal_detach_mask))
? DETACH_BAD_STATE_NO_CLEANUP
: DETACH_BAD_STATE);
SYSLOG_INTERNAL_NO_OPTION_SYNCH(
SYSLOG_WARNING,
"detach on terminate failed or already started by another thread, "
"killing thread " TIDFMT "\n",
d_r_get_thread_id());
* to detach, just kill this thread so progress is made we don't
* have anything better to do with it */
* to get out alive, also avoids recursion problems, see caveat at
* remove_thread below */
terminate_type = TERMINATE_THREAD;
} else {
d_r_config_exit();
nt_terminate_process(currentThreadOrProcess,
custom_code ? exit_code : KILL_PROC_EXIT_STATUS);
ASSERT_NOT_REACHED();
}
}
access even to itself?
*/
if (TEST(TERMINATE_THREAD, terminate_type)) {
exit_process =
(!IS_CLIENT_THREAD(dcontext) && is_last_app_thread() && !dynamo_exited);
if (!exit_process) {
currentThreadOrProcess = NT_CURRENT_THREAD;
}
}
STATS_INC(num_threads_killed);
if (TEST(TERMINATE_CLEANUP, terminate_type)) {
byte *arguments =
os_terminate_static_arguments(exit_process, custom_code, exit_code);
* assumptions about offsets. We don't use the debug loop pointer for
* int2e/syscall/wow64 system calls (since they return to the invocation
* and can be handled there). For SYSENTER the SYSCALL_PARAM_OFFSET should
* match up with arguments such that arguments is pointing to debugme */
ASSERT(does_syscall_ret_to_callsite() ||
*(byte **)arguments == (byte *)&debug_infinite_loop ||
(DYNAMO_OPTION(sygate_sysenter) &&
*(((byte **)arguments) + 1) == (byte *)&debug_infinite_loop));
STATS_INC(num_threads_killed_cleanly);
KSTOP_REWIND_UNTIL(thread_measured);
* dstack that we should release */
* their way? Shouldn't happen since Terminate* are believed to be
* non-alertable. */
ASSERT(dcontext != NULL);
cleanup_and_terminate(
dcontext, syscalls[exit_process ? SYS_TerminateProcess : SYS_TerminateThread],
(ptr_uint_t)IF_X64_ELSE(
(exit_process ? NT_CURRENT_PROCESS : NT_CURRENT_THREAD), arguments),
(ptr_uint_t)IF_X64_ELSE(
(exit_process ? (custom_code ? exit_code : KILL_PROC_EXIT_STATUS)
: KILL_THREAD_EXIT_STATUS),
arguments ),
exit_process, 0, 0);
} else {
if (exit_process) {
d_r_config_exit();
nt_terminate_process(currentThreadOrProcess,
custom_code ? exit_code : KILL_PROC_EXIT_STATUS);
ASSERT_NOT_REACHED();
} else {
* to avoid synchronizing issues, though we are running the risk of
* an infinite loop with a failure in this function and detach on
* failure */
if (all_threads != NULL)
remove_thread(NULL, d_r_get_thread_id());
nt_terminate_thread(currentThreadOrProcess, KILL_THREAD_EXIT_STATUS);
ASSERT_NOT_REACHED();
}
ZwFreeVirtualMemory can be called by another thread
waiting on the thread object, hopefully someone will do it */
}
ASSERT_NOT_REACHED();
}
void
os_terminate_with_code(dcontext_t *dcontext, terminate_flags_t terminate_type,
int exit_code)
{
os_terminate_common(dcontext, terminate_type, true, exit_code);
}
void
os_terminate(dcontext_t *dcontext, terminate_flags_t terminate_type)
{
os_terminate_common(dcontext, terminate_type, false, 0);
}
void
os_tls_init()
{
}
void
os_tls_exit(local_state_t *local_state, bool other_thread)
{
* the freed TEB tls slots */
}
bool
os_tls_calloc(OUT uint *offset, uint num_slots, uint alignment)
{
bool need_synch = !dynamo_initialized;
if (num_slots == 0)
return false;
return (bool)tls_calloc(need_synch, offset, num_slots, alignment);
}
bool
os_tls_cfree(uint offset, uint num_slots)
{
return (bool)tls_cfree(true, offset, num_slots);
}
void
os_thread_init(dcontext_t *dcontext, void *os_data)
{
NTSTATUS res;
DEBUG_DECLARE(bool ok;)
os_thread_data_t *ostd = (os_thread_data_t *)heap_alloc(
dcontext, sizeof(os_thread_data_t) HEAPACCT(ACCT_OTHER));
dcontext->os_field = (void *)ostd;
ostd->stack_base = NULL;
ostd->stack_top = NULL;
ostd->teb_stack_no_longer_valid = false;
DEBUG_DECLARE(ok =) get_stack_bounds(dcontext, NULL, NULL);
ASSERT(ok);
res = query_win32_start_addr(NT_CURRENT_THREAD, &dcontext->win32_start_addr);
if (!NT_SUCCESS(res)) {
ASSERT(false && "failed to obtain win32 start address");
dcontext->win32_start_addr = (app_pc)0;
} else {
LOG(THREAD, LOG_THREADS, 2, "win32 start addr is " PFX "\n",
dcontext->win32_start_addr);
}
aslr_thread_init(dcontext);
}
void
os_thread_init_finalize(dcontext_t *dcontext, void *os_data)
{
}
void
os_thread_exit(dcontext_t *dcontext, bool other_thread)
{
os_thread_data_t *ostd = (os_thread_data_t *)dcontext->os_field;
aslr_thread_exit(dcontext);
# ifdef DEBUG
heap_free(dcontext, ostd, sizeof(os_thread_data_t) HEAPACCT(ACCT_OTHER));
# endif
}
void
os_thread_stack_exit(dcontext_t *dcontext)
{
os_thread_data_t *ostd = (os_thread_data_t *)dcontext->os_field;
ASSERT_OWN_MUTEX(true, &thread_initexit_lock);
* so we remove its stack from our executable region here
* ref also case 5518 where it is sometimes freed in process, we watch for
* that and set stack_base to NULL
* note: thin_client doesn't have executable or aslr areas, so this is moot.
*/
if (DYNAMO_OPTION(thin_client))
return;
if (IS_CLIENT_THREAD(dcontext)) {
return;
}
if (ostd->stack_base != NULL) {
LOG(THREAD, LOG_SYSCALLS | LOG_VMAREAS, 1,
"os_thread_stack_exit : removing " PFX " - " PFX "\n", ostd->stack_base,
ostd->stack_top);
ASSERT(ostd->stack_top != NULL);
DOCHECK(1, {
byte *alloc_base;
size_t size = get_allocation_size(ostd->stack_base, &alloc_base);
* exited (resulting in freed stack) before we clean it up. This could be do
* to using THREAD_SYNCH_TERMINATED_AND_CLEANED with a synch_with* routine
* (no current uses) or a race with detach resuming a translated thread
* before cleaning it up. The detach race is harmless so we allow it. */
ASSERT(doing_detach ||
((size ==
(size_t)ALIGN_FORWARD(
ostd->stack_top - (ptr_int_t)ostd->stack_base, PAGE_SIZE) ||
(size == PAGE_SIZE + (size_t)(ostd->stack_top - ostd->stack_base) &&
is_wow64_process(NT_CURRENT_PROCESS) &&
dcontext->nudge_target != NULL)) &&
ostd->stack_base == alloc_base));
});
* that but we run into problems with stacks that are never freed
* (TerminateThread, threads killed by TerminateProcess 0, last thread
* calling TerminateProcess, etc.) FIXME figure out way to add an
* assert_curiosity */
* from other_thread_exit) since flushing will end up using this dcontext
* for synchronization purposes */
* info (for PR 536058)
*/
if (!dynamo_exited) {
app_memory_deallocation(get_thread_private_dcontext(), ostd->stack_base,
ostd->stack_top - ostd->stack_base,
true ,
false );
}
if (TEST(ASLR_HEAP_FILL, DYNAMO_OPTION(aslr))) {
size_t stack_reserved_size = ostd->stack_top - ostd->stack_base;
* only the committed portion
*/
aslr_pre_process_free_virtual_memory(dcontext, ostd->stack_base,
stack_reserved_size);
}
} else {
LOG(THREAD, LOG_SYSCALLS | LOG_VMAREAS, 1,
"os_thread_stack_exit : Thread's os stack has alread been freed\n");
ASSERT(ostd->stack_top == NULL);
ASSERT_CURIOSITY(get_os_version() <= WINDOWS_VERSION_2000);
}
}
void
os_thread_under_dynamo(dcontext_t *dcontext)
{
ASSERT_MESSAGE(CHKLVL_ASSERTS + 1 , "can only act on executing thread",
dcontext == get_thread_private_dcontext());
set_asynch_interception(d_r_get_thread_id(), true);
}
void
os_thread_not_under_dynamo(dcontext_t *dcontext)
{
ASSERT_MESSAGE(CHKLVL_ASSERTS + 1 , "can only act on executing thread",
dcontext == get_thread_private_dcontext());
set_asynch_interception(d_r_get_thread_id(), false);
}
void
os_process_under_dynamorio_initiate(dcontext_t *dcontext)
{
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
init_apc_go_native = false;
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
}
void
os_process_under_dynamorio_complete(dcontext_t *dcontext)
{
}
void
os_process_not_under_dynamorio(dcontext_t *dcontext)
{
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
init_apc_go_native = true;
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
}
* THREAD TAKEOVER
*/
typedef struct _takeover_data_t {
app_pc continuation_pc;
bool in_progress;
thread_id_t tid;
# ifndef X64
HANDLE thread_handle;
CONTEXT_64 *cxt64;
byte *cxt64_alloc;
ptr_uint_t memval_stack;
ptr_uint_t memval_r14;
# endif
} takeover_data_t;
typedef struct _thread_list_t {
HANDLE handle;
thread_id_t tid;
void *user_data;
} thread_list_t;
enum {
TAKEOVER_NEW = 0,
TAKEOVER_TRIED,
TAKEOVER_SUCCESS,
};
* scheduled. Thus to avoid memory leaks we need global storage that lasts
* across calls to os_take_over_all_unknown_threads().
* We also use the table to ensure we (eventually) free any takeover_data_t for
* a thread that never gets scheduled.
* A final use is for cases where our set context doesn't seem to take
* effect except for eip.
* We do not hold the table lock while accessing table payloads because
* we rely on an invariant that only the owning thread can free its own
* data, or another thread during synchall.
*/
static generic_table_t *takeover_table;
# define INIT_HTABLE_SIZE_TAKEOVER 6
# define INVALID_PAYLOAD ((void *)(ptr_int_t)-2)
static void
takeover_table_entry_free(dcontext_t *dcontext, void *e)
{
takeover_data_t *data = (takeover_data_t *)e;
if (e == INVALID_PAYLOAD)
return;
# ifndef X64
if (data->thread_handle != NULL)
close_handle(data->thread_handle);
if (data->cxt64_alloc != NULL) {
global_heap_free(data->cxt64_alloc,
nt_get_context64_size() HEAPACCT(ACCT_THREAD_MGT));
}
# endif
global_heap_free(data, sizeof(*data) HEAPACCT(ACCT_THREAD_MGT));
}
static void
os_take_over_init(void)
{
takeover_table = generic_hash_create(
GLOBAL_DCONTEXT, INIT_HTABLE_SIZE_TAKEOVER,
80 , HASHTABLE_SHARED | HASHTABLE_PERSISTENT,
takeover_table_entry_free _IF_DEBUG("takeover table"));
# ifndef X64
d_r_set_ss_selector();
# endif
}
* code (e.g., waiting for a lock) so we mark threads during init prior to being
* added to the main thread table
*/
void
os_take_over_mark_thread(thread_id_t tid)
{
TABLE_RWLOCK(takeover_table, write, lock);
if (generic_hash_lookup(GLOBAL_DCONTEXT, takeover_table, tid) == NULL)
generic_hash_add(GLOBAL_DCONTEXT, takeover_table, tid, INVALID_PAYLOAD);
TABLE_RWLOCK(takeover_table, write, unlock);
}
void
os_take_over_unmark_thread(thread_id_t tid)
{
TABLE_RWLOCK(takeover_table, write, lock);
if (generic_hash_lookup(GLOBAL_DCONTEXT, takeover_table, tid) == INVALID_PAYLOAD)
generic_hash_remove(GLOBAL_DCONTEXT, takeover_table, tid);
TABLE_RWLOCK(takeover_table, write, unlock);
}
* global protected heap with HEAPACCT(ACCT_THREAD_MGT).
* Each HANDLE should be closed prior to freeing the array.
*/
static thread_list_t *
os_list_threads(uint *num_threads_out)
{
HANDLE hthread;
thread_list_t *threads = NULL;
NTSTATUS res =
nt_thread_iterator_next(NT_CURRENT_PROCESS, NULL, &hthread, THREAD_ALL_ACCESS);
ASSERT(num_threads_out != NULL);
if (NT_SUCCESS(res)) {
uint num_threads = 0;
uint num_alloc = 16;
threads =
global_heap_alloc(num_alloc * sizeof(*threads) HEAPACCT(ACCT_THREAD_MGT));
do {
if (num_threads == num_alloc) {
uint new_alloc = num_alloc * 2;
threads = global_heap_realloc(threads, num_alloc, new_alloc,
sizeof(*threads) HEAPACCT(ACCT_THREAD_MGT));
num_alloc = new_alloc;
}
LOG(GLOBAL, LOG_THREADS, 1, "%s: thread " TIDFMT " handle=" PFX "\n",
__FUNCTION__, num_threads, hthread);
threads[num_threads].handle = hthread;
threads[num_threads].tid = INVALID_THREAD_ID;
threads[num_threads].user_data = NULL;
num_threads++;
res = nt_thread_iterator_next(NT_CURRENT_PROCESS, hthread, &hthread,
THREAD_ALL_ACCESS);
} while (NT_SUCCESS(res));
*num_threads_out = num_threads;
threads = global_heap_realloc(threads, num_alloc, num_threads,
sizeof(*threads) HEAPACCT(ACCT_THREAD_MGT));
} else {
SYSTEM_PROCESSES *sp;
uint sysinfo_size;
byte *sysinfo;
sysinfo = get_system_processes(&sysinfo_size);
sp = (SYSTEM_PROCESSES *)sysinfo;
while (sysinfo != NULL) {
if (is_pid_me((process_id_t)sp->ProcessId)) {
uint i;
threads = global_heap_alloc(sp->ThreadCount *
sizeof(*threads) HEAPACCT(ACCT_THREAD_MGT));
for (i = 0; i < sp->ThreadCount; i++) {
thread_id_t tid = (thread_id_t)sp->Threads[i].ClientId.UniqueThread;
ASSERT(get_process_id() ==
(process_id_t)sp->Threads[i].ClientId.UniqueProcess);
LOG(GLOBAL, LOG_THREADS, 1,
"%s: thread " TIDFMT " UniqueThread=" PFX "\n", __FUNCTION__, i,
tid);
threads[i].handle = thread_handle_from_id(tid);
ASSERT(threads[i].handle != INVALID_HANDLE_VALUE);
threads[i].tid = tid;
threads[i].user_data = NULL;
}
*num_threads_out = sp->ThreadCount;
break;
}
if (sp->NextEntryDelta == 0)
break;
sp = (SYSTEM_PROCESSES *)(((byte *)sp) + sp->NextEntryDelta);
}
global_heap_free(sysinfo, sysinfo_size HEAPACCT(ACCT_OTHER));
}
return threads;
}
static void
thread_attach_remove_from_table(takeover_data_t *data)
{
TABLE_RWLOCK(takeover_table, write, lock);
generic_hash_remove(GLOBAL_DCONTEXT, takeover_table, data->tid);
TABLE_RWLOCK(takeover_table, write, unlock);
}
static void
thread_attach_restore_full_state(takeover_data_t *data)
{
# ifndef X64
if (data->cxt64 != NULL) {
if (data->memval_stack != 0) {
if (!safe_write((void *)(ptr_uint_t)data->cxt64->Rsp,
sizeof(data->memval_stack), &data->memval_stack)) {
LOG(GLOBAL, LOG_THREADS, 1, "%s: failed to restore *Rsp " PFX "\n",
__FUNCTION__, data->cxt64->Rsp);
}
}
if (data->memval_r14 != 0) {
if (!safe_write((void *)(ptr_uint_t)data->cxt64->R14,
sizeof(data->memval_r14), &data->memval_r14)) {
LOG(GLOBAL, LOG_THREADS, 1, "%s: failed to restore *R14 " PFX "\n",
__FUNCTION__, data->cxt64->R14);
}
}
if (!thread_set_context_64(data->thread_handle, data->cxt64)) {
LOG(GLOBAL, LOG_THREADS, 1, "%s: failed to set x64 context\n", __FUNCTION__);
}
}
# endif
}
void
thread_attach_translate(dcontext_t *dcontext, priv_mcontext_t *mc INOUT,
bool restore_memory)
{
takeover_data_t *data;
TABLE_RWLOCK(takeover_table, read, lock);
data = (takeover_data_t *)generic_hash_lookup(GLOBAL_DCONTEXT, takeover_table,
(ptr_uint_t)dcontext->owning_thread);
TABLE_RWLOCK(takeover_table, read, unlock);
if (data != NULL && data != INVALID_PAYLOAD) {
mc->pc = data->continuation_pc;
if (restore_memory)
thread_attach_restore_full_state(data);
} else
ASSERT_NOT_REACHED();
}
static void
thread_attach_context_revert_from_data(CONTEXT *cxt INOUT, takeover_data_t *data)
{
cxt->CXT_XIP = (ptr_uint_t)data->continuation_pc;
thread_attach_restore_full_state(data);
}
void
thread_attach_context_revert(CONTEXT *cxt INOUT)
{
takeover_data_t *data;
TABLE_RWLOCK(takeover_table, read, lock);
data = (takeover_data_t *)generic_hash_lookup(GLOBAL_DCONTEXT, takeover_table,
(ptr_uint_t)d_r_get_thread_id());
TABLE_RWLOCK(takeover_table, read, unlock);
if (data != NULL && data != INVALID_PAYLOAD) {
thread_attach_context_revert_from_data(cxt, data);
thread_attach_remove_from_table(data);
} else
ASSERT_NOT_REACHED();
}
void
thread_attach_exit(dcontext_t *dcontext, priv_mcontext_t *mc)
{
ASSERT(mc->pc == (app_pc)thread_attach_takeover);
TABLE_RWLOCK(takeover_table, write, lock);
generic_hash_remove(GLOBAL_DCONTEXT, takeover_table,
(ptr_uint_t)dcontext->owning_thread);
TABLE_RWLOCK(takeover_table, write, unlock);
}
static void
os_take_over_exit(void)
{
if (takeover_table == NULL)
return;
* can't just check init_apc_go_native in thread_attach_takeover_callee because
* it can't just return to go native: it's not interception a static PC, and the
* continuation PC is stored in our heap which we'll free when we exit! Just
* waiting for these threads prior to detach is not guaranteed, so instead we
* just revert the attach.
*/
DWORD cxt_flags = CONTEXT_DR_STATE;
size_t bufsz = nt_get_context_size(cxt_flags);
char *buf = (char *)global_heap_alloc(bufsz HEAPACCT(ACCT_THREAD_MGT));
TABLE_RWLOCK(takeover_table, write, lock);
int iter = 0;
takeover_data_t *data;
ptr_uint_t key;
do {
iter = generic_hash_iterate_next(GLOBAL_DCONTEXT, takeover_table, iter, &key,
(void **)&data);
if (iter < 0)
break;
CONTEXT *cxt = nt_initialize_context(buf, bufsz, cxt_flags);
HANDLE handle = thread_handle_from_id(data->tid);
LOG(GLOBAL, LOG_THREADS, 1,
"Reverting attached-but-never-scheduled thread " TIDFMT "\n", data->tid);
if (nt_thread_suspend(handle, NULL) && NT_SUCCESS(nt_get_context(handle, cxt))) {
thread_attach_context_revert_from_data(cxt, data);
if (!NT_SUCCESS(nt_set_context(handle, cxt)) ||
!nt_thread_resume(handle, NULL)) {
SYSLOG_INTERNAL_WARNING(
"Failed to resume attached-but-never-scheduled thread " TIDFMT,
data->tid);
}
} else {
SYSLOG_INTERNAL_WARNING(
"Failed to suspend attached-but-never-scheduled thread " TIDFMT,
data->tid);
}
iter = generic_hash_iterate_remove(GLOBAL_DCONTEXT, takeover_table, iter, key);
} while (true);
TABLE_RWLOCK(takeover_table, write, unlock);
generic_hash_destroy(GLOBAL_DCONTEXT, takeover_table);
takeover_table = NULL;
global_heap_free(buf, bufsz HEAPACCT(ACCT_THREAD_MGT));
}
# ifndef X64
*
* For wow64, when a thread is in the wow64 layer, 32-bit NtGetContextThread
* goes and reads from the CONTEXT32 (aka WOW64_CONTEXT) stored in userspace
* (TEB64->TlsSlots[1]) by the wow64 layer and returns that. Similary,
* NtSetContextThread goes and writes that stored CONTEXT32.
*
* If a target suspended thread is in the process of saving (on entry to wow64
* layer 64-bit mode), NtGetContextThread will return an incorrect context;
* and if the thread is in the process of restoring (on exit back to 32-bit
* mode), NtSetContextThread will have some of its values overwritten once the
* thread resumes.
*
* My solution is to get the x64 CONTEXT64, pattern-match the entry and exit,
* and set the appropriate registers or memory. Unfortunately this is fragile
* with respect to the exact code sequences in
* wow64cpu!CpupReturnFromSimulatedCode and wow64cpu!CpuSimulate changing in
* the future.
*
* As part of this I also changed the takeover to not store the context at
* suspend time and instead only change Eip then, capturing the context when
* the thread resumes. This requires an assume-nothing routine, which
* requires d_r_initstack: but these takeover points shouldn't be perf-critical.
* This really simplifies the wow64 entry/exit corner cases.
*/
static bool
wow64_cases_pre_win10(takeover_data_t *data, CONTEXT_64 *cxt64, HANDLE hthread,
thread_id_t tid, app_pc takeover)
{
* saved, and right after it's restored.
* It's saved here:
* wow64cpu!CpupReturnFromSimulatedCode:
* 00000000`78b83c2c 67448b0424 mov r8d,dword ptr [esp]
* 00000000`78b83c31 458985bc000000 mov dword ptr [r13+0BCh],r8d
* 00000000`78b83c38 83c404 add esp,4
* 00000000`78b83c3b 4189a5c8000000 mov dword ptr [r13+0C8h],esp
* And restored here:
* wow64cpu!CpuSimulate+0x161:
* 00000000`74ff2711 458b8dbc000000 mov r9d,dword ptr [r13+0BCh]
* 00000000`74ff2718 45890e mov dword ptr [r14],r9d
* 00000000`74ff271b 41ff2e jmp fword ptr [r14]
* We have to change either [esp], r8d, r9d, or [r14].
*/
static const byte WOW64_ENTER_INST12[] = { 0x67, 0x44, 0x8b, 0x04, 0x24, 0x45,
0x89, 0x85, 0xbc, 0x00, 0x00, 0x00 };
static const byte WOW64_ENTER_INST2[] = { 0x45, 0x89, 0x85, 0xbc, 0x00, 0x00, 0x00 };
static const byte WOW64_EXIT_INST12[] = { 0x45, 0x89, 0x0e, 0x41, 0xff, 0x2e };
static const byte WOW64_EXIT_INST2[] = { 0x41, 0xff, 0x2e };
bool changed_x64_cxt = false;
if (cxt64->Rip >= 0x100000000ULL)
return false;
if (memcmp((byte *)(ptr_uint_t)cxt64->Rip, WOW64_ENTER_INST12,
sizeof(WOW64_ENTER_INST12)) == 0) {
if (d_r_safe_read((void *)(ptr_uint_t)cxt64->Rsp, sizeof(data->memval_stack),
&data->memval_stack) &&
safe_write((void *)(ptr_uint_t)cxt64->Rsp, sizeof(takeover), &takeover)) {
changed_x64_cxt = true;
LOG(GLOBAL, LOG_THREADS, 2, "\ttid %d @ wow64 enter1 => changed [esp]\n",
tid);
} else {
data->memval_stack = 0;
LOG(GLOBAL, LOG_THREADS, 1,
"\ttid %d @ wow64 enter1, but FAILED to change [esp]\n", tid);
ASSERT_NOT_REACHED();
}
}
else if (memcmp((byte *)(ptr_uint_t)cxt64->Rip, WOW64_ENTER_INST2,
sizeof(WOW64_ENTER_INST2)) == 0) {
uint64 orig_r8 = cxt64->R8;
cxt64->R8 = (DWORD64)(ptr_uint_t)takeover;
if (thread_set_context_64(hthread, cxt64)) {
changed_x64_cxt = true;
LOG(GLOBAL, LOG_THREADS, 2, "\ttid %d @ wow64 enter2 => changed r8d\n", tid);
} else {
LOG(GLOBAL, LOG_THREADS, 1,
"\ttid %d @ wow64 enter2, but FAILED to change r8d\n", tid);
ASSERT_NOT_REACHED();
}
cxt64->R8 = orig_r8;
}
else if (memcmp((byte *)(ptr_uint_t)cxt64->Rip, WOW64_EXIT_INST12,
sizeof(WOW64_EXIT_INST12)) == 0) {
uint64 orig_r9 = cxt64->R9;
cxt64->R9 = (DWORD64)(ptr_uint_t)takeover;
if (thread_set_context_64(hthread, cxt64)) {
changed_x64_cxt = true;
LOG(GLOBAL, LOG_THREADS, 2, "\ttid %d @ wow64 exit1 => changed r9d\n", tid);
} else {
LOG(GLOBAL, LOG_THREADS, 1,
"\ttid %d @ wow64 exit1, but FAILED to change r9d\n", tid);
ASSERT_NOT_REACHED();
}
cxt64->R9 = orig_r9;
}
else if (memcmp((byte *)(ptr_uint_t)cxt64->Rip, WOW64_EXIT_INST2,
sizeof(WOW64_EXIT_INST2)) == 0) {
if (d_r_safe_read((void *)(ptr_uint_t)cxt64->R14, sizeof(data->memval_r14),
&data->memval_r14) &&
safe_write((void *)(ptr_uint_t)cxt64->R14, sizeof(takeover), &takeover)) {
changed_x64_cxt = true;
LOG(GLOBAL, LOG_THREADS, 2, "\ttid %d @ wow64 exit2 => changed [r14]\n", tid);
} else {
data->memval_r14 = 0;
LOG(GLOBAL, LOG_THREADS, 1,
"\ttid %d @ wow64 exit2, but FAILED to change *r14\n", tid);
ASSERT_NOT_REACHED();
}
}
return changed_x64_cxt;
}
static bool
wow64_cases_win10(takeover_data_t *data, CONTEXT_64 *cxt64, HANDLE hthread,
thread_id_t tid, app_pc takeover)
{
* wow64cpu!CpupReturnFromSimulatedCode:
* 00000000`59da18e6 4987e6 xchg rsp,r14
* 00000000`59da18e9 458b06 mov r8d,dword ptr [r14]
* 00000000`59da18ec 4983c604 add r14,4
* 00000000`59da18f0 4589453c mov dword ptr [r13+3Ch],r8d
*
* And restored in 2 places:
* wow64cpu!RunSimulatedCode+0x5f: (from earlier, r14==rsp)
* 00000000`59da183f 458b4d3c mov r9d,dword ptr [r13+3Ch]
* 00000000`59da1843 44890c24 mov dword ptr [rsp],r9d
* 00000000`59da1847 418b6548 mov esp,dword ptr [r13+48h]
* 00000000`59da184b 41ff2e jmp fword ptr [r14]
* wow64cpu!RunSimulatedCode+0xfc:
* 00000000`59da18dc 458b453c mov r8d,dword ptr [r13+3Ch]
* 00000000`59da18e0 4c890424 mov qword ptr [rsp],r8
* 00000000`59da18e4 48cf iretq
* We have to change either [esp], r8d, r9d, or [r14].
*/
static const byte WOW64_ENTER_INST12[] = { 0x49, 0x87, 0xe6, 0x45, 0x8b, 0x06 };
static const byte WOW64_ENTER_INST23[] = { 0x45, 0x8b, 0x06, 0x49, 0x83, 0xc6, 0x04 };
static const byte WOW64_ENTER_INST34[] = { 0x49, 0x83, 0xc6, 0x04,
0x45, 0x89, 0x45, 0x3c };
static const byte WOW64_ENTER_INST4[] = { 0x45, 0x89, 0x45, 0x3c };
static const byte WOW64_EXIT1_INST12[] = { 0x44, 0x89, 0x0c, 0x24,
0x41, 0x8b, 0x65, 0x48 };
static const byte WOW64_EXIT1_INST23[] = { 0x41, 0x8b, 0x65, 0x48, 0x41, 0xff, 0x2e };
static const byte WOW64_EXIT1_INST3[] = { 0x41, 0xff, 0x2e };
static const byte WOW64_EXIT2_INST12[] = { 0x4c, 0x89, 0x04, 0x24, 0x48, 0xcf };
static const byte WOW64_EXIT2_INST2[] = { 0x48, 0xcf };
bool changed_x64_cxt = false;
if (cxt64->Rip >= 0x100000000ULL)
return false;
if (memcmp((byte *)(ptr_uint_t)cxt64->Rip, WOW64_ENTER_INST12,
sizeof(WOW64_ENTER_INST12)) == 0) {
if (d_r_safe_read((void *)(ptr_uint_t)cxt64->Rsp, sizeof(data->memval_stack),
&data->memval_stack) &&
safe_write((void *)(ptr_uint_t)cxt64->Rsp, sizeof(takeover), &takeover)) {
changed_x64_cxt = true;
LOG(GLOBAL, LOG_THREADS, 2, "\ttid %d @ wow64 enter1 => changed [esp]\n",
tid);
} else {
data->memval_stack = 0;
LOG(GLOBAL, LOG_THREADS, 1,
"\ttid %d @ wow64 enter1, but FAILED to change [esp]\n", tid);
ASSERT_NOT_REACHED();
}
}
else if (memcmp((byte *)(ptr_uint_t)cxt64->Rip, WOW64_ENTER_INST23,
sizeof(WOW64_ENTER_INST23)) == 0) {
if (d_r_safe_read((void *)(ptr_uint_t)cxt64->R14, sizeof(data->memval_stack),
&data->memval_stack) &&
safe_write((void *)(ptr_uint_t)cxt64->R14, sizeof(takeover), &takeover)) {
changed_x64_cxt = true;
LOG(GLOBAL, LOG_THREADS, 2, "\ttid %d @ wow64 enter1 => changed [r14]\n",
tid);
} else {
data->memval_stack = 0;
LOG(GLOBAL, LOG_THREADS, 1,
"\ttid %d @ wow64 enter1, but FAILED to change [r14]\n", tid);
ASSERT_NOT_REACHED();
}
}
else if (memcmp((byte *)(ptr_uint_t)cxt64->Rip, WOW64_ENTER_INST34,
sizeof(WOW64_ENTER_INST34)) == 0 ||
memcmp((byte *)(ptr_uint_t)cxt64->Rip, WOW64_ENTER_INST4,
sizeof(WOW64_ENTER_INST4)) == 0) {
uint64 orig_r8 = cxt64->R8;
cxt64->R8 = (DWORD64)(ptr_uint_t)takeover;
if (thread_set_context_64(hthread, cxt64)) {
changed_x64_cxt = true;
LOG(GLOBAL, LOG_THREADS, 2, "\ttid %d @ wow64 enter2 => changed r8d\n", tid);
} else {
LOG(GLOBAL, LOG_THREADS, 1,
"\ttid %d @ wow64 enter2, but FAILED to change r8d\n", tid);
ASSERT_NOT_REACHED();
}
cxt64->R8 = orig_r8;
}
else if (memcmp((byte *)(ptr_uint_t)cxt64->Rip, WOW64_EXIT1_INST12,
sizeof(WOW64_EXIT1_INST12)) == 0) {
uint64 orig_r9 = cxt64->R9;
cxt64->R9 = (DWORD64)(ptr_uint_t)takeover;
if (thread_set_context_64(hthread, cxt64)) {
changed_x64_cxt = true;
LOG(GLOBAL, LOG_THREADS, 2, "\ttid %d @ wow64 exit1 => changed r9d\n", tid);
} else {
LOG(GLOBAL, LOG_THREADS, 1,
"\ttid %d @ wow64 exit1, but FAILED to change r9d\n", tid);
ASSERT_NOT_REACHED();
}
cxt64->R9 = orig_r9;
}
else if (memcmp((byte *)(ptr_uint_t)cxt64->Rip, WOW64_EXIT1_INST23,
sizeof(WOW64_EXIT1_INST23)) == 0 ||
memcmp((byte *)(ptr_uint_t)cxt64->Rip, WOW64_EXIT1_INST3,
sizeof(WOW64_EXIT1_INST3)) == 0) {
if (d_r_safe_read((void *)(ptr_uint_t)cxt64->R14, sizeof(data->memval_r14),
&data->memval_r14) &&
safe_write((void *)(ptr_uint_t)cxt64->R14, sizeof(takeover), &takeover)) {
changed_x64_cxt = true;
LOG(GLOBAL, LOG_THREADS, 2, "\ttid %d @ wow64 exit2 => changed [r14]\n", tid);
} else {
data->memval_r14 = 0;
LOG(GLOBAL, LOG_THREADS, 1,
"\ttid %d @ wow64 exit2, but FAILED to change *r14\n", tid);
ASSERT_NOT_REACHED();
}
}
else if (memcmp((byte *)(ptr_uint_t)cxt64->Rip, WOW64_EXIT2_INST12,
sizeof(WOW64_EXIT2_INST12)) == 0) {
uint64 orig_r8 = cxt64->R8;
cxt64->R8 = (DWORD64)(ptr_uint_t)takeover;
if (thread_set_context_64(hthread, cxt64)) {
changed_x64_cxt = true;
LOG(GLOBAL, LOG_THREADS, 2, "\ttid %d @ wow64 exit1 => changed r8d\n", tid);
} else {
LOG(GLOBAL, LOG_THREADS, 1,
"\ttid %d @ wow64 exit1, but FAILED to change r8d\n", tid);
ASSERT_NOT_REACHED();
}
cxt64->R8 = orig_r8;
}
else if (memcmp((byte *)(ptr_uint_t)cxt64->Rip, WOW64_EXIT2_INST2,
sizeof(WOW64_EXIT2_INST2)) == 0) {
if (d_r_safe_read((void *)(ptr_uint_t)cxt64->Rsp, sizeof(data->memval_stack),
&data->memval_stack) &&
safe_write((void *)(ptr_uint_t)cxt64->Rsp, sizeof(takeover), &takeover)) {
changed_x64_cxt = true;
LOG(GLOBAL, LOG_THREADS, 2, "\ttid %d @ wow64 exit2 => changed [rsp]\n", tid);
} else {
data->memval_stack = 0;
LOG(GLOBAL, LOG_THREADS, 1,
"\ttid %d @ wow64 exit2, but FAILED to change *rsp\n", tid);
ASSERT_NOT_REACHED();
}
}
return changed_x64_cxt;
}
static void
os_take_over_wow64_extra(takeover_data_t *data, HANDLE hthread, thread_id_t tid,
CONTEXT *cxt32)
{
CONTEXT_64 *cxt64;
bool changed_x64_cxt = false;
app_pc takeover = thread_attach_takeover;
byte *buf;
# ifdef DEBUG
* 7d8513eb 64ff15c0000000 call dword ptr fs:[000000c0]
*/
static const byte WOW64_SYSCALL_CALL[] = { 0x64, 0xff, 0x15, 0xc0, 0x00, 0x00, 0x00 };
# endif
if (!is_wow64_process(NT_CURRENT_PROCESS))
return;
* We can't easily use nt_initialize_context so we manually set the flags.
*/
buf = (byte *)global_heap_alloc(nt_get_context64_size() HEAPACCT(ACCT_THREAD_MGT));
cxt64 = (CONTEXT_64 *)ALIGN_FORWARD(buf, 0x10);
cxt64->ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
if (!thread_get_context_64(hthread, cxt64)) {
LOG(GLOBAL, LOG_THREADS, 1, "\tfailed to get x64 cxt for thread " TIDFMT "\n",
tid);
ASSERT_NOT_REACHED();
global_heap_free(buf, nt_get_context64_size() HEAPACCT(ACCT_THREAD_MGT));
return;
}
LOG(GLOBAL, LOG_THREADS, 2,
"x64 context for thread " TIDFMT ": xip is " HEX64_FORMAT_STRING
", xsp=" HEX64_FORMAT_STRING,
tid, cxt64->Rip, cxt64->Rsp);
if (cxt64->SegCs == CS32_SELECTOR ||
* success but fill cxt64 with zeroes. We hope this only happens when
* truly in the kernel.
*/
cxt64->Rip == 0) {
* work fine.
*/
global_heap_free(buf, nt_get_context64_size() HEAPACCT(ACCT_THREAD_MGT));
return;
}
* assert is_in_ntdll, but we do check that it's the wow64 syscall call*:
*/
# ifdef DEBUG
if (get_os_version() >= WINDOWS_VERSION_10) {
ASSERT_CURIOSITY(*(app_pc *)(data->continuation_pc - CTI_IND1_LENGTH -
sizeof(app_pc)) == wow64_syscall_call_tgt);
} else {
ASSERT_CURIOSITY(memcmp(data->continuation_pc - sizeof(WOW64_SYSCALL_CALL),
WOW64_SYSCALL_CALL, sizeof(WOW64_SYSCALL_CALL)) == 0);
}
# endif
if (get_os_version() >= WINDOWS_VERSION_10)
changed_x64_cxt = wow64_cases_pre_win10(data, cxt64, hthread, tid, takeover);
else
changed_x64_cxt = wow64_cases_win10(data, cxt64, hthread, tid, takeover);
if (changed_x64_cxt) {
* We shouldn't have to undo any of these changes on a successful
* takeover.
*/
duplicate_handle(NT_CURRENT_PROCESS, hthread, NT_CURRENT_PROCESS,
&data->thread_handle, 0, 0,
DUPLICATE_SAME_ACCESS | DUPLICATE_SAME_ATTRIBUTES);
data->cxt64 = cxt64;
data->cxt64_alloc = buf;
} else {
global_heap_free(buf, nt_get_context64_size() HEAPACCT(ACCT_THREAD_MGT));
}
}
# endif
static bool
os_take_over_thread(dcontext_t *dcontext, HANDLE hthread, thread_id_t tid, bool suspended)
{
bool success = true;
if (DYNAMO_OPTION(skip_terminating_threads)) {
if (nt_is_thread_terminating(hthread)) {
return success;
}
}
DWORD cxt_flags = CONTEXT_DR_STATE;
size_t bufsz = nt_get_context_size(cxt_flags);
char *buf = (char *)heap_alloc(dcontext, bufsz HEAPACCT(ACCT_THREAD_MGT));
CONTEXT *cxt = nt_initialize_context(buf, bufsz, cxt_flags);
ASSERT(tid == thread_id_from_handle(hthread));
if ((suspended || nt_thread_suspend(hthread, NULL)) &&
NT_SUCCESS(nt_get_context(hthread, cxt))) {
* on the stack and thus risking transparency violations, we
* just allocate it on our heap and put it into a hashtable.
*
* Originally I tried storing the context here, pointing at it in a
* register, and swapping to dstack now, for a simpler takeover routine:
* but the state changes between here and the takeover routine,
* resulting in corrupted registers. Thus, we have the takeover routine
* assume nothing and capture the context once the thread gets there.
* Then our only problem is the eip setting not sticking, meaning we
* don't take over at all.
*/
NTSTATUS res;
takeover_data_t *data;
void *already_taken_over;
* for DR to initialize. We have to check two things: first, whether
* the context is in DR; second, whether flagged (to cover the thread
* being in ntdll or vsyscall).
*/
if (is_in_dynamo_dll((app_pc)cxt->CXT_XIP) ||
new_thread_is_waiting_for_dr_init(tid, (app_pc)cxt->CXT_XIP)) {
LOG(GLOBAL, LOG_THREADS, 1, "\tthread " TIDFMT " is already waiting\n", tid);
heap_free(dcontext, buf, bufsz HEAPACCT(ACCT_THREAD_MGT));
return true;
}
* N.B.: is_dynamo_address() on xip and xsp is not sufficient as
* a newly set context may not show up until the thread is scheduled.
* We still want to check them to catch threads created after
* our APC hook was in place.
*/
TABLE_RWLOCK(takeover_table, read, lock);
already_taken_over =
generic_hash_lookup(GLOBAL_DCONTEXT, takeover_table, (ptr_uint_t)tid);
TABLE_RWLOCK(takeover_table, read, unlock);
if (already_taken_over != NULL || is_dynamo_address((byte *)cxt->CXT_XIP)) {
* yet added itself to main thread table.
*/
LOG(GLOBAL, LOG_THREADS, 1,
"\tthread " TIDFMT " partially taken over already; pc=" PFX "\n", tid,
cxt->CXT_XIP);
if (already_taken_over != NULL && already_taken_over != INVALID_PAYLOAD &&
!is_dynamo_address((byte *)cxt->CXT_XIP) &&
!((takeover_data_t *)already_taken_over)->in_progress &&
cxt->CXT_XIP != (ptr_uint_t)thread_attach_takeover) {
* confirms, and then later the thread's context is back to native
* and we never take it over! So we detect here and try again.
* See also comment above.
*/
data = (takeover_data_t *)already_taken_over;
LOG(GLOBAL, LOG_THREADS, 1, "\tthread " TIDFMT " reverted!", tid);
* know if it does.
*/
ASSERT_CURIOSITY(false && "thread takeover context reverted!");
} else {
heap_free(dcontext, buf, bufsz HEAPACCT(ACCT_THREAD_MGT));
return true;
}
} else {
thread_record_t *tr = thread_lookup(tid);
data = (takeover_data_t *)global_heap_alloc(sizeof(*data)
HEAPACCT(ACCT_THREAD_MGT));
}
LOG(GLOBAL, LOG_THREADS, 1, "thread " TIDFMT " context:\n", tid);
memset(data, 0, sizeof(*data));
data->tid = tid;
data->continuation_pc = (app_pc)cxt->CXT_XIP;
cxt->CXT_XIP = (ptr_uint_t)thread_attach_takeover;
# ifndef X64
os_take_over_wow64_extra(data, hthread, tid, cxt);
# endif
ASSERT(TESTALL(CONTEXT_DR_STATE, cxt->ContextFlags));
res = nt_set_context(hthread, cxt);
if (!NT_SUCCESS(res)) {
LOG(GLOBAL, LOG_THREADS, 1,
"\tfailed to set context for thread " TIDFMT " with error %d\n", tid,
res);
success = false;
global_heap_free(data, sizeof(*data) HEAPACCT(ACCT_THREAD_MGT));
if (!nt_thread_resume(hthread, NULL)) {
LOG(GLOBAL, LOG_THREADS, 1, "\tfailed to resume thread " TIDFMT "\n",
tid);
ASSERT_NOT_REACHED();
}
} else {
if (already_taken_over == NULL) {
TABLE_RWLOCK(takeover_table, write, lock);
generic_hash_add(GLOBAL_DCONTEXT, takeover_table, tid, data);
TABLE_RWLOCK(takeover_table, write, unlock);
}
LOG(GLOBAL, LOG_THREADS, 1,
"\tset context for thread " TIDFMT "; old xip=" PFX ", xsp=" PFX
", data=" PFX "\n",
tid, data->continuation_pc, cxt->CXT_XSP, data);
}
} else {
LOG(GLOBAL, LOG_THREADS, 1, "\tfailed to suspend/query thread " TIDFMT "\n", tid);
success = false;
}
heap_free(dcontext, buf, bufsz HEAPACCT(ACCT_THREAD_MGT));
return success;
}
bool
os_thread_take_over_suspended_native(dcontext_t *dcontext)
{
thread_record_t *tr = dcontext->thread_record;
if (!is_thread_currently_native(tr))
return false;
if (dcontext->currently_stopped)
return false;
* use this to signal syscall_while_native() to take this thread
* over if it makes it to one of our syscall hooks.
* The thread will still be considered is_thread_currently_native().
*/
tr->retakeover = true;
return os_take_over_thread(dcontext, tr->handle, tr->id, true );
}
* that called dr_app_setup().
*/
dcontext_t *
os_thread_take_over_secondary(priv_mcontext_t *mc)
{
IF_DEBUG(int r =)
dynamo_thread_init(NULL, mc, NULL, false);
ASSERT(r == SUCCESS);
return get_thread_private_dcontext();
}
bool
os_thread_re_take_over(void)
{
return false;
}
bool
os_take_over_all_unknown_threads(dcontext_t *dcontext)
{
uint i, iters;
const uint MAX_ITERS = 16;
uint num_threads = 0;
thread_list_t *threads = NULL;
thread_id_t my_id = d_r_get_thread_id();
bool took_over_all = true, found_new_threads = true;
ASSERT(TAKEOVER_NEW == (ptr_uint_t)NULL);
d_r_mutex_lock(&thread_initexit_lock);
* This ends up looking similar to synch_with_all_threads(), though it has
* some key differences, making it non-trivial to share code.
* We need to do at least 2 iters no matter what, but dr_app_start or
* external attach should be considered heavyweight events in any case.
*/
for (iters = 0; found_new_threads && iters < MAX_ITERS; iters++) {
uint num_new_threads, j;
thread_list_t *new_threads = os_list_threads(&num_new_threads);
LOG(GLOBAL, LOG_THREADS, 1, "TAKEOVER: iteration %d\n", iters);
if (new_threads == NULL) {
took_over_all = false;
break;
}
found_new_threads = false;
for (i = 0; i < num_new_threads; i++) {
if (new_threads[i].tid == INVALID_THREAD_ID)
new_threads[i].tid = thread_id_from_handle(new_threads[i].handle);
}
if (threads != NULL) {
for (i = 0; i < num_threads; i++) {
for (j = 0; j < num_new_threads; j++) {
if (new_threads[j].tid == threads[i].tid)
new_threads[j].user_data = threads[i].user_data;
}
if ((ptr_uint_t)threads[i].user_data == TAKEOVER_SUCCESS)
close_handle(threads[i].handle);
}
global_heap_free(threads,
num_threads * sizeof(*threads) HEAPACCT(ACCT_THREAD_MGT));
}
threads = new_threads;
num_threads = num_new_threads;
for (i = 0; i < num_threads; i++) {
thread_record_t *tr;
if ((ptr_uint_t)threads[i].user_data == TAKEOVER_NEW) {
found_new_threads = true;
threads[i].user_data = (void *)(ptr_uint_t)TAKEOVER_TRIED;
tr = thread_lookup(threads[i].tid);
if ((tr == NULL ||
* XXX i#95: we need a synchall-style loop for known threads as
* they can be in DR for syscall hook handling.
*/
(is_thread_currently_native(tr) &&
!IS_CLIENT_THREAD(tr->dcontext))) &&
threads[i].tid != my_id) {
LOG(GLOBAL, LOG_THREADS, 1,
"TAKEOVER: taking over thread " TIDFMT "\n", threads[i].tid);
if (os_take_over_thread(dcontext, threads[i].handle, threads[i].tid,
false )) {
threads[i].user_data = (void *)(ptr_uint_t)TAKEOVER_SUCCESS;
} else {
took_over_all = false;
* it with injected logon/logoff threads: let's see.
*/
ASSERT_CURIOSITY(false && "failed to take over a thread!");
}
}
}
if ((ptr_uint_t)threads[i].user_data != TAKEOVER_SUCCESS)
close_handle(threads[i].handle);
}
}
* the final list: but shouldn't happen unless the thread is destroyed in
* which case it's ok to never resume it.
*/
for (i = 0; i < num_threads; i++) {
if ((ptr_uint_t)threads[i].user_data == TAKEOVER_SUCCESS) {
if (!nt_thread_resume(threads[i].handle, NULL)) {
LOG(GLOBAL, LOG_THREADS, 1, "\tfailed to resume thread " TIDFMT "\n",
threads[i].tid);
took_over_all = false;
ASSERT_NOT_REACHED();
}
close_handle(threads[i].handle);
}
}
global_heap_free(threads, num_threads * sizeof(*threads) HEAPACCT(ACCT_THREAD_MGT));
if (iters == MAX_ITERS) {
LOG(GLOBAL, LOG_THREADS, 1, "TAKEOVER: hit max iters %d\n", iters);
took_over_all = false;
}
d_r_mutex_unlock(&thread_initexit_lock);
return !took_over_all;
}
void
thread_attach_setup(priv_mcontext_t *mc)
{
dcontext_t *dcontext;
takeover_data_t *data;
ENTERING_DR();
TABLE_RWLOCK(takeover_table, write, lock);
data = (takeover_data_t *)generic_hash_lookup(GLOBAL_DCONTEXT, takeover_table,
(ptr_uint_t)d_r_get_thread_id());
TABLE_RWLOCK(takeover_table, write, unlock);
if (data == NULL || data == INVALID_PAYLOAD) {
ASSERT(standalone_library);
* However, returning here does not just go back native: we've lost the
* PC to go back to and the thread will just crash.
*/
ASSERT_NOT_REACHED();
EXITING_DR();
return;
}
if (init_apc_go_native) {
* point is dynamic. We directly do an NtContinue. (For threads that
* are still not scheduled when we go to exit, os_take_over_exit()
* sets the context back).
*/
mc->pc = data->continuation_pc;
thread_set_self_mcontext(mc);
ASSERT_NOT_REACHED();
}
data->in_progress = true;
* already initialized.
*/
if (!is_thread_initialized()) {
int rc = dynamo_thread_init(NULL, mc, NULL, false);
ASSERT(rc == SUCCESS);
}
dcontext = get_thread_private_dcontext();
ASSERT(dcontext != NULL);
dynamo_thread_under_dynamo(dcontext);
dcontext->thread_record->retakeover = false;
* at_syscall flag set, so make sure to clear it.
*/
set_at_syscall(dcontext, false);
LOG(GLOBAL, LOG_THREADS, 1, "TAKEOVER: thread " TIDFMT ", start pc " PFX "\n",
d_r_get_thread_id(), data->continuation_pc);
ASSERT(os_using_app_state(dcontext));
dcontext->next_tag = data->continuation_pc;
*get_mcontext(dcontext) = *mc;
thread_attach_remove_from_table(data);
data = NULL;
transfer_to_dispatch(dcontext, get_mcontext(dcontext), false );
ASSERT_NOT_REACHED();
}
* CLIENT THREADS
*/
* * thread must have dcontext since many API routines require one and we
* don't expose GLOBAL_DCONTEXT (xref PR 243008, PR 216936, PR 536058)
* * reversed the old design of not using dstack (partly b/c want dcontext)
* and avoiding needing a temp stack by just creating dstack up front,
* like is done on linux. dstack should be big enough for client threads
* (xref PR 202669)
* * reversed the old design of explicit dr_terminate_client_thread(): now
* the thread is auto-terminated and stack cleaned up on return from run
* function
*/
* 1) All dlls will be notifed of thread creation by DLL_THREAD_ATTACH
* => this is now solved by not running the Ldr code: intercept_new_thread()
* just comes straight here
* 2) The thread will show up in the list of threads accessed by
* NtQuerySystemInformation's SystemProcessesAndThreadsInformation structure.
* 3) check_sole_thread()
* 4) Vista+'s NtGetNextThread and NtGetNextProcess
* (which I am assuming expose the iterator interface of
* PsGetNextProcessThread, should check)
*/
void
client_thread_target(void *param)
{
dcontext_t *dcontext = get_thread_private_dcontext();
void **arg_buf = (void **)param;
void (*func)(void *param) = (void (*)(void *))convert_data_to_function(arg_buf[0]);
void *arg = arg_buf[1];
byte *dstack = dcontext->dstack;
ASSERT(IS_CLIENT_THREAD(dcontext));
LOG(THREAD, LOG_ALL, 1, "\n***** CLIENT THREAD %d *****\n\n", d_r_get_thread_id());
LOG(THREAD, LOG_ALL, 1, "func=" PFX ", arg=" PFX "\n", func, arg);
(*func)(arg);
LOG(THREAD, LOG_ALL, 1, "\n***** CLIENT THREAD %d EXITING *****\n\n",
d_r_get_thread_id());
os_terminate(dcontext, TERMINATE_THREAD | TERMINATE_CLEANUP);
}
bool
is_new_thread_client_thread(CONTEXT *cxt, OUT byte **dstack)
{
bool is_client = (void *)cxt->CXT_XIP == (void *)client_thread_target ||
(void *)cxt->THREAD_START_ADDR == (void *)our_create_thread_wrapper;
if (is_client && dstack != NULL) {
if (get_os_version() >= WINDOWS_VERSION_8) {
*dstack = (byte *)cxt->THREAD_START_ARG;
} else {
byte *stack;
GET_STACK_PTR(stack);
stack = (byte *)ALIGN_FORWARD(stack, PAGE_SIZE);
*dstack = stack;
}
}
return is_client;
}
DR_API bool
dr_create_client_thread(void (*func)(void *param), void *arg)
{
dcontext_t *dcontext = get_thread_private_dcontext();
byte *dstack = stack_alloc(DYNAMORIO_STACK_SIZE, NULL);
HANDLE hthread;
bool res;
thread_id_t tid;
void *arg_buf[2];
LOG(THREAD, LOG_ASYNCH, 1,
"dr_create_client_thread: dstack for new thread is " PFX "\n", dstack);
pre_second_thread();
arg_buf[0] = (void *)func;
arg_buf[1] = arg;
hthread = our_create_thread_have_stack(
NT_CURRENT_PROCESS, IF_X64_ELSE(true, false), (void *)client_thread_target, NULL,
arg_buf, BUFFER_SIZE_BYTES(arg_buf), dstack, DYNAMORIO_STACK_SIZE, false, &tid);
CLIENT_ASSERT(hthread != INVALID_HANDLE_VALUE, "error creating thread");
if (hthread == INVALID_HANDLE_VALUE) {
stack_free(dstack, DYNAMORIO_STACK_SIZE);
return false;
}
res = close_handle(hthread);
CLIENT_ASSERT(res, "error closing thread handle");
return res;
}
int
get_os_version()
{
return os_version;
}
void
get_os_version_ex(int *version OUT, uint *service_pack_major OUT,
uint *service_pack_minor OUT, uint *build_number OUT,
const char **release_id OUT, const char **edition OUT)
{
if (version != NULL)
*version = os_version;
if (service_pack_major != NULL)
*service_pack_major = os_service_pack_major;
if (service_pack_minor != NULL)
*service_pack_minor = os_service_pack_minor;
if (build_number != NULL)
*build_number = os_build_number;
if (release_id != NULL)
*release_id = os_release_id;
if (edition != NULL)
*edition = os_edition;
}
bool
is_in_dynamo_dll(app_pc pc)
{
ASSERT(dynamo_dll_start != NULL && dynamo_dll_end != NULL);
return (pc >= dynamo_dll_start && pc < dynamo_dll_end);
}
static char *
mem_state_string(uint state)
{
switch (state) {
case 0: return "none";
case MEM_COMMIT: return "COMMIT";
case MEM_FREE: return "FREE";
case MEM_RESERVE: return "RESERVE";
}
return "(error)";
}
static char *
mem_type_string(uint type)
{
switch (type) {
case 0: return "none";
case MEM_IMAGE: return "IMAGE";
case MEM_MAPPED: return "MAPPED";
case MEM_PRIVATE: return "PRIVATE";
}
return "(error)";
}
char *
prot_string(uint prot)
{
uint ignore_extras = prot & ~PAGE_PROTECTION_QUALIFIERS;
switch (ignore_extras) {
case PAGE_NOACCESS: return "----";
case PAGE_READONLY: return "r---";
case PAGE_READWRITE: return "rw--";
case PAGE_WRITECOPY: return "rw-c";
case PAGE_EXECUTE: return "--x-";
case PAGE_EXECUTE_READ: return "r-x-";
case PAGE_EXECUTE_READWRITE: return "rwx-";
case PAGE_EXECUTE_WRITECOPY: return "rwxc";
}
return "(error)";
}
static bool
prot_is_readable(uint prot)
{
prot &= ~PAGE_PROTECTION_QUALIFIERS;
* do not do exclusions, sometimes prot == 0 or something
*/
switch (prot) {
case PAGE_READONLY:
case PAGE_READWRITE:
case PAGE_WRITECOPY:
case PAGE_EXECUTE:
case PAGE_EXECUTE_READ:
case PAGE_EXECUTE_READWRITE:
case PAGE_EXECUTE_WRITECOPY: return true;
}
return false;
}
bool
prot_is_writable(uint prot)
{
prot &= ~PAGE_PROTECTION_QUALIFIERS;
return (prot == PAGE_READWRITE || prot == PAGE_WRITECOPY ||
prot == PAGE_EXECUTE_READWRITE || prot == PAGE_EXECUTE_WRITECOPY);
}
bool
prot_is_executable(uint prot)
{
prot &= ~PAGE_PROTECTION_QUALIFIERS;
return (prot == PAGE_EXECUTE || prot == PAGE_EXECUTE_READ ||
prot == PAGE_EXECUTE_READWRITE || prot == PAGE_EXECUTE_WRITECOPY);
}
bool
prot_is_copyonwrite(uint prot)
{
prot &= ~PAGE_PROTECTION_QUALIFIERS;
* values use separate bit flags. We use TESTANY in case new
* PAGE_PROTECTION_QUALIFIERS show up.
*/
return TESTANY(PAGE_WRITECOPY | PAGE_EXECUTE_WRITECOPY, prot);
}
bool
prot_is_guard(uint prot)
{
return TEST(PAGE_GUARD, prot);
}
int
memprot_to_osprot(uint prot)
{
uint os_prot = 0;
if (TEST(MEMPROT_EXEC, prot)) {
if (!TEST(MEMPROT_READ, prot)) {
ASSERT(!TEST(MEMPROT_WRITE, prot));
os_prot = PAGE_EXECUTE;
} else if (TEST(MEMPROT_WRITE, prot))
os_prot = PAGE_EXECUTE_READWRITE;
else
os_prot = PAGE_EXECUTE_READ;
} else if (TEST(MEMPROT_READ, prot)) {
if (TEST(MEMPROT_WRITE, prot))
os_prot = PAGE_READWRITE;
else
os_prot = PAGE_READONLY;
} else
os_prot = PAGE_NOACCESS;
if (TEST(MEMPROT_GUARD, prot))
os_prot |= PAGE_GUARD;
return os_prot;
}
int
osprot_to_memprot(uint prot)
{
uint mem_prot = 0;
if (prot_is_readable(prot))
mem_prot |= MEMPROT_READ;
if (prot_is_writable(prot))
mem_prot |= MEMPROT_WRITE;
if (prot_is_executable(prot))
mem_prot |= MEMPROT_EXEC;
if (prot_is_guard(prot))
mem_prot |= MEMPROT_GUARD;
return mem_prot;
}
int
osprot_add_writecopy(uint prot)
{
int pr = prot & ~PAGE_PROTECTION_QUALIFIERS;
switch (pr) {
case PAGE_READWRITE: return (prot & (~pr)) | PAGE_WRITECOPY;
case PAGE_EXECUTE_READWRITE: return (prot & (~pr)) | PAGE_EXECUTE_WRITECOPY;
default: ASSERT_NOT_REACHED();
}
return prot;
}
static uint
osprot_add_write(uint prot)
{
int pr = prot & ~PAGE_PROTECTION_QUALIFIERS;
switch (pr) {
case PAGE_READONLY: return (prot & (~pr)) | PAGE_READWRITE;
case PAGE_EXECUTE_READ: return (prot & (~pr)) | PAGE_EXECUTE_READWRITE;
}
return prot;
}
* for RWX, which are replaced according to memprot */
uint
osprot_replace_memprot(uint old_osprot, uint memprot)
{
uint old_qualifiers = old_osprot & PAGE_PROTECTION_QUALIFIERS;
uint new_osprot = memprot_to_osprot(memprot);
if (prot_is_copyonwrite(old_osprot) && prot_is_writable(new_osprot))
new_osprot = osprot_add_writecopy(new_osprot);
new_osprot |= old_qualifiers;
return new_osprot;
}
void
dump_mbi(file_t file, MEMORY_BASIC_INFORMATION *mbi, bool dump_xml)
{
print_file(file,
dump_xml ? "\t\tBaseAddress= \"" PFX "\"\n"
"\t\tAllocationBase= \"" PFX "\"\n"
"\t\tAllocationProtect= \"0x%08x %s\"\n"
"\t\tRegionSize= \"0x%08x\"\n"
"\t\tState= \"0x%08x %s\"\n"
"\t\tProtect= \"0x%08x %s\"\n"
"\t\tType= \"0x%08x %s\"\n"
: "BaseAddress: " PFX "\n"
"AllocationBase: " PFX "\n"
"AllocationProtect: 0x%08x %s\n"
"RegionSize: 0x%08x\n"
"State: 0x%08x %s\n"
"Protect: 0x%08x %s\n"
"Type: 0x%08x %s\n",
mbi->BaseAddress, mbi->AllocationBase, mbi->AllocationProtect,
prot_string(mbi->AllocationProtect), mbi->RegionSize, mbi->State,
mem_state_string(mbi->State), mbi->Protect, prot_string(mbi->Protect),
mbi->Type, mem_type_string(mbi->Type));
}
void
dump_mbi_addr(file_t file, app_pc target, bool dump_xml)
{
MEMORY_BASIC_INFORMATION mbi;
size_t len;
len = query_virtual_memory(target, &mbi, sizeof(mbi));
if (len == sizeof(mbi))
dump_mbi(file, &mbi, dump_xml);
else {
if (dump_xml) {
print_file(file, "<-- Unable to dump mbi for addr " PFX "\n -->", target);
} else {
print_file(file, "Unable to dump mbi for addr " PFX "\n", target);
}
}
}
* We need to be able to distinguish our own pid from that of a child
* process. We observe that after CreateProcess a child has pid of 0 (as
* determined by process_id_from_handle, calling NtQueryInformationProcess).
* For our current injection methods pid is always set when we take over,
* but for future early-injection methods what if the pid is still 0 when
* we start executing in the process' context?
*/
bool
is_pid_me(process_id_t pid)
{
return (pid == get_process_id());
}
bool
is_phandle_me(HANDLE phandle)
{
if (phandle == NT_CURRENT_PROCESS) {
return true;
} else {
* so we go to pid
*/
process_id_t pid = process_id_from_handle(phandle);
return is_pid_me(pid);
}
}
int
num_app_args()
{
ASSERT_NOT_IMPLEMENTED(false);
set_client_error_code(NULL, DR_ERROR_NOT_IMPLEMENTED);
return -1;
}
int
get_app_args(OUT dr_app_arg_t *args_buf, int buf_size)
{
ASSERT_NOT_IMPLEMENTED(false);
set_client_error_code(NULL, DR_ERROR_NOT_IMPLEMENTED);
return -1;
}
* so it is easily available to windbg scripts */
static char dynamorio_library_path[MAXIMUM_PATH];
char *
get_dynamorio_library_path()
{
* once at startup when there is a single thread only
*/
if (!dynamorio_library_path[0]) {
* so we simply take the address of this function
* instead of using get_module_handle to find the base
*/
app_pc pb = (app_pc)&get_dynamorio_library_path;
ASSERT(!dr_earliest_injected);
get_module_name(pb, dynamorio_library_path, MAXIMUM_PATH);
}
return dynamorio_library_path;
}
* verify whether we should be taking control over it */
* inject_settings is set if non-NULL */
bool
should_inject_into_process(dcontext_t *dcontext, HANDLE process_handle,
int *rununder_mask,
inject_setting_mask_t *inject_settings )
{
bool inject = false;
synchronize_dynamic_options();
if (DYNAMO_OPTION(follow_children) || DYNAMO_OPTION(follow_explicit_children) ||
DYNAMO_OPTION(follow_systemwide)) {
inject_setting_mask_t should_inject =
systemwide_should_inject(process_handle, rununder_mask);
if (DYNAMO_OPTION(follow_systemwide) && TEST(INJECT_TRUE, should_inject)) {
LOG(THREAD, LOG_SYSCALLS | LOG_THREADS, 1,
"\tconfigured child should be injected\n");
inject = true;
}
if (!inject && DYNAMO_OPTION(follow_explicit_children) &&
TESTALL(INJECT_EXPLICIT | INJECT_TRUE, should_inject)) {
LOG(THREAD, LOG_SYSCALLS | LOG_THREADS, 1,
"\texplicit child should be injected\n");
inject = true;
}
if (!inject && DYNAMO_OPTION(follow_children)) {
inject = true;
if (TEST(INJECT_EXCLUDED, should_inject)) {
LOG(THREAD, LOG_SYSCALLS | LOG_THREADS, 1,
"\tchild is excluded, not injecting\n");
inject = false;
}
if (TEST(INJECT_TRUE, should_inject) && systemwide_inject_enabled() &&
!TEST(INJECT_EXPLICIT, should_inject)) {
ASSERT(!DYNAMO_OPTION(follow_systemwide));
LOG(THREAD, LOG_SYSCALLS | LOG_THREADS, 1,
"\tletting preinjector inject into child\n");
inject = false;
}
DODEBUG({
if (inject) {
LOG(THREAD, LOG_SYSCALLS | LOG_THREADS, 1,
"\tnon-excluded, non-preinjected child should be injected\n");
}
});
}
if (inject) {
ASSERT(!TEST(INJECT_EXCLUDED, should_inject));
if (inject_settings != NULL)
*inject_settings = should_inject;
}
}
DODEBUG({
if (inject) {
LOG(THREAD, LOG_SYSCALLS | LOG_THREADS, 1,
"\tinjecting into child process\n");
} else {
LOG(THREAD, LOG_SYSCALLS | LOG_THREADS, 1,
"\tletting child execute natively "
"(may still get injected by systemwide injector!)\n");
}
});
return inject;
}
static int
inject_into_process(dcontext_t *dcontext, HANDLE process_handle, HANDLE thread_handle,
CONTEXT *cxt, inject_setting_mask_t should_inject)
{
mechanism take place. If an app specific AUTOINJECT value is
provided, then we should of course use it. However, if no
specific one is given we should not use the global default when
follow_children. For follow_explicit_children it is actually OK
to use the global default value, it will be the GUI's
responsibility to set both the parent and child if it is desired
to have them use the same library.
*/
char library_path_buf[MAXIMUM_PATH];
char alt_arch_path[MAXIMUM_PATH];
char *library = library_path_buf;
bool res;
int err = get_process_parameter(process_handle, PARAM_STR(DYNAMORIO_VAR_AUTOINJECT),
library_path_buf, sizeof(library_path_buf));
* what mode are we injecting.
* If we are in fact in follow_children - meaning all children are followed,
* and there is no app specific option then we should use the parent library,
* unless the child is in fact explicit in which case we just use the global library.
*/
bool custom_library = false;
switch (err) {
case GET_PARAMETER_SUCCESS: custom_library = true; break;
case GET_PARAMETER_NOAPPSPECIFIC:
* reason we're injecting is -follow_children (i.e. reading RUNUNDER gave us
* !INJECT_TRUE). */
if (!TEST(INJECT_TRUE, should_inject)) {
ASSERT(DYNAMO_OPTION(follow_children));
library = get_dynamorio_library_path();
}
break;
case GET_PARAMETER_BUF_TOO_SMALL:
case GET_PARAMETER_FAILURE: library = get_dynamorio_library_path(); break;
default: ASSERT_NOT_REACHED();
}
if (!custom_library IF_X64(&&!DYNAMO_OPTION(inject_x64))) {
if (IF_NOT_X64(!) is_32bit_process(process_handle)) {
# define DR_LIBDIR_X86 STRINGIFY(LIBDIR_X86)
# define DR_LIBDIR_X64 STRINGIFY(LIBDIR_X64)
strncpy(alt_arch_path, library, BUFFER_SIZE_ELEMENTS(alt_arch_path));
char *libdir =
strstr(alt_arch_path, IF_X64_ELSE(DR_LIBDIR_X64, DR_LIBDIR_X86));
if (libdir != NULL) {
const char *newdir = IF_X64_ELSE(DR_LIBDIR_X86, DR_LIBDIR_X64);
strncpy(libdir, newdir, strlen(newdir));
NULL_TERMINATE_BUFFER(alt_arch_path);
library = alt_arch_path;
LOG(THREAD, LOG_SYSCALLS | LOG_THREADS, 1,
"alternate-bitwidth library path: %s", library);
} else {
REPORT_FATAL_ERROR_AND_EXIT(
INJECTION_LIBRARY_MISSING, 3, get_application_name(),
get_application_pid(),
"<failed to determine alternate bitwidth path>");
}
}
}
LOG(THREAD, LOG_SYSCALLS | LOG_THREADS, 1, "\tinjecting %s into child process\n",
library);
if (!os_file_exists(library, false)) {
REPORT_FATAL_ERROR_AND_EXIT(INJECTION_LIBRARY_MISSING, 3, get_application_name(),
get_application_pid(), library);
}
if (DYNAMO_OPTION(aslr_dr) &&
process_handle != NULL && !is_child_in_thin_client(process_handle)) {
aslr_force_dynamorio_rebase(process_handle);
}
* ntdll32.dll at early inject point, so thread injection only. PR 215423.
* This is only true for xp64/2003. It happens to work on vista+ because
* it turns out ntdll32 is mapped in by the kernel. (xref i#381)
*/
if (DYNAMO_OPTION(early_inject) &&
(get_os_version() >= WINDOWS_VERSION_VISTA ||
!is_wow64_process(process_handle))) {
ASSERT(early_inject_address != NULL ||
!INJECT_LOCATION_IS_LDR(early_inject_location));
* to find the correct address to use. Don't expect that to happen,
* but if it does could fall back to late injection (though we can't
* be sure that would work, i.e. early thread process for ex.) or
* do a SYSLOG error. */
res = inject_into_new_process(process_handle, thread_handle, library,
DYNAMO_OPTION(early_inject_map),
early_inject_location, early_inject_address);
} else {
ASSERT(cxt != NULL);
res = inject_into_thread(process_handle, cxt, NULL, library);
}
if (!res) {
SYSLOG_INTERNAL_ERROR("ERROR: injection from pid=%d of %s into child "
"process %d failed",
get_process_id(), library,
process_id_from_handle(process_handle));
ASSERT_CURIOSITY(false && "injection into child failed: 32 to 64?");
return false;
}
return true;
}
bool
is_first_thread_in_new_process(HANDLE process_handle, CONTEXT *cxt)
{
* pid 0 until its first thread is created. This is not true on XP
* so we also check if the argument value is the PEB address
* (which it should be if it is the first thread in the process,
* according to inside win2k). This is a slight risk of double
* or late injection if someone creates a remote thread that
* happens to have an argument that equals the address of PEB.
* Better would be able to tell from Eip if it is pointing at the
* kernel32 thread start thunk or the kernel32 process start thunk,
* or to check if the number of threads in the process equals 0,
* but no easy way to do either here. FIXME
*/
process_id_t pid = process_id_from_handle(process_handle);
if (pid == 0) {
LOG(THREAD_GET, LOG_SYSCALLS | LOG_THREADS, 2, "%s: failed to get pid\n");
return true;
}
if (!is_pid_me(pid)) {
uint64 peb = get_peb_maybe64(process_handle);
uint64 start_arg =
IF_X64_ELSE(cxt->THREAD_START_ARG64,
is_32bit_process(process_handle) ? cxt->THREAD_START_ARG32
: cxt->THREAD_START_ARG64);
LOG(THREAD_GET, LOG_SYSCALLS | LOG_THREADS, 2,
"%s: pid=" PIFX " vs me=" PIFX ", arg=" PFX " vs peb=" PFX "\n", __FUNCTION__,
pid, get_process_id(), start_arg, peb);
if (start_arg == peb)
return true;
else if (is_wow64_process(process_handle) &&
get_os_version() >= WINDOWS_VERSION_VISTA) {
* will be the x86 PEB. On Vista and Win7 the x86 PEB seems to
* always be one page below but we don't want to rely on that, and
* it doesn't hold on Win8. Instead we ensure the start addr's
* first 3 fields match the x64 PEB: boolean flags, Mutant, and
* ImageBaseAddress.
*
* XXX: We now have get_peb32() with a thread handle. But this is no
* longer used for the default injection.
*/
int64 peb64[3];
int peb32[3];
byte *base = NULL;
size_t sz;
if (!read_remote_memory_maybe64(process_handle, peb, peb64, sizeof(peb64),
&sz) ||
sz != sizeof(peb64) ||
!read_remote_memory_maybe64(process_handle, start_arg, peb32,
sizeof(peb32), &sz) ||
sz != sizeof(peb32))
return false;
LOG(THREAD_GET, LOG_SYSCALLS | LOG_THREADS, 2,
"%s: peb64 " PIFX "," PIFX "," PIFX " vs peb32 " PIFX "," PIFX "," PIFX
"\n",
__FUNCTION__, peb64[0], peb64[1], peb64[2], peb32[0], peb32[1], peb32[2]);
if (peb64[0] == peb32[0] && peb64[1] == peb32[1] && peb64[2] == peb32[2])
return true;
}
}
return false;
}
* handle process_handle. Called by SYS_CreateThread in pre_system_call (in
* which case cxt is non-NULL) and by CreateProcess[Ex] in post_system_call (in
* which case cxt is NULL).
* Does not support cross-arch injection for cxt!=NULL.
*/
bool
maybe_inject_into_process(dcontext_t *dcontext, HANDLE process_handle,
HANDLE thread_handle, CONTEXT *cxt)
{
* the synchronize dynamic options */
* value in peb field except in Vista. Could pass it in. */
* ntdll32.dll at early inject point, so thread injection only. PR 215423.
* This is only true for xp64/2003. It happens to work on vista+ because
* it turns out ntdll32 is mapped in by the kernel. (xref i#381)
*/
bool injected = false;
if ((cxt == NULL &&
(DYNAMO_OPTION(inject_at_create_process) ||
(get_os_version() >= WINDOWS_VERSION_VISTA &&
DYNAMO_OPTION(vista_inject_at_create_process))) &&
(!is_wow64_process(process_handle) ||
get_os_version() >= WINDOWS_VERSION_VISTA)) ||
(cxt != NULL && is_first_thread_in_new_process(process_handle, cxt))) {
int rununder_mask;
inject_setting_mask_t should_inject;
DEBUG_DECLARE(process_id_t pid = process_id_from_handle(process_handle);)
DOLOG(3, LOG_SYSCALLS | LOG_THREADS,
{ SYSLOG_INTERNAL_INFO("found a fork: pid %d", pid); });
LOG(THREAD, LOG_SYSCALLS | LOG_THREADS, 1, "found a fork: pid %d\n", pid);
if (should_inject_into_process(dcontext, process_handle, &rununder_mask,
&should_inject)) {
if (cxt == NULL && !DYNAMO_OPTION(early_inject)) {
* without -early_inject and without a context we're forced to
* wait and assume NtCreateThread will be called later.
* FIXME i#1898: on win10 for heap crash handling we hit this, and
* we are currently missing the child.
*/
SYSLOG_INTERNAL_WARNING("legacy process creation detected: may miss "
"child");
} else {
injected = true;
ASSERT(cxt != NULL || DYNAMO_OPTION(early_inject));
* to cxt, which may be unsafe.
*/
if (inject_into_process(dcontext, process_handle, thread_handle, cxt,
should_inject)) {
check_for_run_once(process_handle, rununder_mask);
}
}
}
}
return injected;
}
static bool
is_child_in_thin_client(HANDLE process_handle)
{
bool res;
const options_t *opts;
ASSERT(process_handle != NT_CURRENT_PROCESS && process_handle != NT_CURRENT_THREAD &&
process_handle != NULL);
opts = get_process_options(process_handle);
ASSERT_OWN_READWRITE_LOCK(true, &options_lock);
ASSERT(opts != NULL);
* if you can't read it and say yes which will prevent aslr dr. Note: this
* isn't the secure option, which is to say no, so that we alsr dr.
* Interesting tradeoff; choosing safety as this scenario is rare in which
* case first goal is to do no harm.
*/
if (opts == NULL) {
res = false;
} else {
res = opts->thin_client;
}
d_r_write_unlock(&options_lock);
return res;
}
app_pc
get_dynamorio_dll_start()
{
if (dynamo_dll_start == NULL)
dynamo_dll_start = get_allocation_base((app_pc)get_dynamorio_dll_start);
return dynamo_dll_start;
}
app_pc
get_dynamorio_dll_preferred_base(void)
{
if (dynamo_dll_preferred_base == NULL) {
dynamo_dll_preferred_base = get_module_preferred_base(get_dynamorio_dll_start());
ASSERT(dynamo_dll_preferred_base != NULL);
}
return dynamo_dll_preferred_base;
}
static app_pc highest_user_address =
(app_pc)(ptr_uint_t)IF_X64_ELSE(0x000007fffffeffffLL, 0x7ffeffff);
static void
get_system_basic_info(void)
{
SYSTEM_BASIC_INFORMATION sbasic_info;
NTSTATUS result = query_system_info(SystemBasicInformation,
sizeof(SYSTEM_BASIC_INFORMATION), &sbasic_info);
ASSERT(NT_SUCCESS(result));
highest_user_address = (app_pc)sbasic_info.HighestUserAddress;
* lkd> dd nt!MmUserProbeAddress l1
* 8055ee34 7fff0000
* lkd> dd nt!MmHighestUserAddress l1
* 8055ee3c 7ffeffff
*/
LOG(GLOBAL, LOG_VMAREAS, 1,
"get_system_basic_info: "
"HighestUserAddress " PFX "\n",
highest_user_address);
if (INTERNAL_OPTION(stress_fake_userva) != 0) {
if (highest_user_address > (app_pc)INTERNAL_OPTION(stress_fake_userva)) {
highest_user_address = (app_pc)INTERNAL_OPTION(stress_fake_userva);
SYSLOG_INTERNAL_WARNING("using stress_fake_userva " PFX "\n",
highest_user_address);
} else {
ASSERT_CURIOSITY("useless stress_fake_userva");
}
}
ASSERT(OS_ALLOC_GRANULARITY == sbasic_info.AllocationGranularity);
}
bool
is_user_address(app_pc pc)
{
* page which should normally be invalid.
*
* FIXME: case 10899 although users can in fact allocate in the
* NULL allocation region (by using base=1), as typically done in
* a local NULL pointer attack. Natively the address is still
* visible for execution, and the OS should handle base=NULL on
* our queries, but we should check if we will. Of course, this
* is likely an attack so it is OK for us to fail it.
*
* we only check upper bound and treat all smaller addresses as user addresses
*/
return pc <= highest_user_address;
}
void
merge_writecopy_pages(app_pc start, app_pc end)
{
MEMORY_BASIC_INFORMATION mbi;
PBYTE pb = start;
uint prot;
size_t len = query_virtual_memory(pb, &mbi, sizeof(mbi));
ASSERT(len == sizeof(mbi));
LOG(GLOBAL, LOG_VMAREAS, 2, "merge_writecopy_pages " PFX "-" PFX "\n", start, end);
do {
if ((app_pc)mbi.BaseAddress >= end)
break;
ASSERT(mbi.State == MEM_COMMIT);
ASSERT(prot_is_writable(mbi.Protect));
prot = mbi.Protect & ~PAGE_PROTECTION_QUALIFIERS;
if (prot == PAGE_WRITECOPY) {
* entire section into single region, for more efficient protection!
* Yes all the writable regions are already contiguous, but they
* have different flags, and so are different regions, and
* NtProtectVirtualMemory refuses to do more than one region at a time.
* However, regions seem to be merged when they have the
* same flags, so we just remove the C flag.
* Calling NtProtectVirtualMemory w/ PAGE_READWRITE to try
* and remove the copy-on-write bits does not work, so we
* write to every page!
* FIXME: test on other versions of windows!
* it's not documented so it may not be everywhere!
* works on Win2K Professional
* N.B.: since make_writable doesn't preserve copy-on-write,
* it's a good thing we do this hack.
* FIXME: how many of these pages would never have been made private?
* (case 8069 covers that inquiry)
*/
volatile app_pc pc = mbi.BaseAddress;
app_pc stop = ((app_pc)mbi.BaseAddress) + mbi.RegionSize;
ASSERT(stop <= end);
LOG(GLOBAL, LOG_VMAREAS, 2,
"writing to " SZFMT " pages to get local copy of"
" copy-on-write section @" PFX "\n",
mbi.RegionSize / PAGE_SIZE, pc);
while (pc < stop) {
*pc = *pc;
pc += PAGE_SIZE;
}
}
if (POINTER_OVERFLOW_ON_ADD(pb, mbi.RegionSize))
break;
pb += mbi.RegionSize;
} while (query_virtual_memory(pb, &mbi, sizeof(mbi)) == sizeof(mbi));
LOG(GLOBAL, LOG_VMAREAS, 2, "checking that " PFX "-" PFX " merger worked\n", start,
end);
len = query_virtual_memory(start, &mbi, sizeof(mbi));
ASSERT(len == sizeof(mbi));
ASSERT(prot_is_writable(mbi.Protect));
ASSERT(end <= start + mbi.RegionSize);
ASSERT(dynamo_dll_end == NULL ||
(is_in_dynamo_dll(start) && is_in_dynamo_dll(end)));
LOG(GLOBAL, LOG_VMAREAS, 2, "DR regions post-merger:\n");
DOLOG(1, LOG_VMAREAS, {
print_dynamo_regions();
LOG(GLOBAL, LOG_VMAREAS, 2, "\n");
});
}
int
find_dynamo_library_vm_areas()
{
size_t len;
PBYTE pb;
MEMORY_BASIC_INFORMATION mbi;
int num_regions = 0;
get_dynamorio_library_path();
LOG(GLOBAL, LOG_VMAREAS, 1, PRODUCT_NAME " dll path: %s\n",
get_dynamorio_library_path());
get_dynamorio_dll_start();
ASSERT(dynamo_dll_start != NULL);
pb = dynamo_dll_start;
len = query_virtual_memory(pb, &mbi, sizeof(mbi));
ASSERT(len == sizeof(mbi));
ASSERT(mbi.State != MEM_FREE);
LOG(GLOBAL, LOG_VMAREAS, 1, "\nOur regions:\n");
do {
if (mbi.State == MEM_FREE || (app_pc)mbi.AllocationBase != dynamo_dll_start)
break;
if (mbi.State == MEM_COMMIT) {
LOG(GLOBAL, LOG_VMAREAS, 1, PFX "-" PFX " %s\n", mbi.BaseAddress,
((app_pc)mbi.BaseAddress) + mbi.RegionSize, prot_string(mbi.Protect));
num_regions++;
# ifndef STATIC_LIBRARY
* to be a DR area.
*/
add_dynamo_vm_area(mbi.BaseAddress,
((app_pc)mbi.BaseAddress) + mbi.RegionSize,
osprot_to_memprot(mbi.Protect),
true
_IF_DEBUG(prot_string(mbi.Protect)));
* sections that we protect */
ASSERT(
!prot_is_writable(mbi.Protect) ||
data_sections_enclose_region((app_pc)mbi.BaseAddress,
((app_pc)mbi.BaseAddress) + mbi.RegionSize));
# endif
}
if (POINTER_OVERFLOW_ON_ADD(pb, mbi.RegionSize))
break;
pb += mbi.RegionSize;
} while (query_virtual_memory(pb, &mbi, sizeof(mbi)) == sizeof(mbi));
dynamo_dll_end = (app_pc)pb;
LOG(GLOBAL, LOG_VMAREAS, 1, PRODUCT_NAME " dll: from " PFX " to " PFX "\n\n",
dynamo_dll_start, dynamo_dll_end);
return num_regions;
}
void
print_dynamo_regions()
{
size_t len;
PBYTE pb;
MEMORY_BASIC_INFORMATION mbi;
ASSERT(dynamo_dll_start != NULL);
pb = dynamo_dll_start;
len = query_virtual_memory(pb, &mbi, sizeof(mbi));
ASSERT(len == sizeof(mbi));
ASSERT(mbi.State != MEM_FREE);
do {
if (mbi.State == MEM_FREE || (app_pc)mbi.AllocationBase != dynamo_dll_start)
break;
if (mbi.State == MEM_COMMIT) {
LOG(GLOBAL, LOG_ALL, 1, PFX "-" PFX " %s\n", mbi.BaseAddress,
((app_pc)mbi.BaseAddress) + mbi.RegionSize, prot_string(mbi.Protect));
}
if (POINTER_OVERFLOW_ON_ADD(pb, mbi.RegionSize))
break;
pb += mbi.RegionSize;
} while (query_virtual_memory(pb, &mbi, sizeof(mbi)) == sizeof(mbi));
}
# ifdef DEBUG
# define MEM_STATS_ADD(stat, sz) \
do { \
if ((sz) != 0) { \
STATS_ADD(stat, sz); \
LOG(GLOBAL, LOG_MEMSTATS, 4, #stat " sz " SZFMT "\n", sz); \
} \
} while (0);
* and committed are disjoint, returns whether or not it was our memory
*/
static bool
add_mem_stats(app_pc region, size_t r_commit, size_t r_reserve, bool r_is_stack,
uint r_type, size_t r_exec, size_t r_rw, size_t r_ro)
{
bool ours = false;
if (r_type == MEM_IMAGE) {
if (is_in_dynamo_dll(region)) {
ours = true;
MEM_STATS_ADD(dr_library_space, r_commit);
ASSERT(r_reserve == 0);
} else {
MEM_STATS_ADD(app_image_capacity, r_commit + r_reserve);
}
} else {
if (is_dynamo_address(region)) {
ours = true;
} else if (r_type == MEM_MAPPED) {
MEM_STATS_ADD(app_mmap_capacity, r_commit);
} else {
if (r_is_stack) {
MEM_STATS_ADD(app_stack_capacity, r_commit);
} else {
MEM_STATS_ADD(app_heap_capacity, r_commit);
}
}
}
LOG(GLOBAL, LOG_MEMSTATS, 4,
"Region " PFX "-" PFX " commit=" SZFMT " reserve=" SZFMT " stack=" SZFMT
" ours=" SZFMT "\n",
region, region + r_commit + r_reserve, r_commit, r_reserve, r_is_stack, ours);
if (ours) {
MEM_STATS_ADD(dr_commited_capacity, r_commit);
MEM_STATS_ADD(dr_reserved_capacity, r_reserve);
MEM_STATS_ADD(dr_vsize, r_commit + r_reserve);
} else {
MEM_STATS_ADD(app_reserved_capacity, r_reserve);
MEM_STATS_ADD(app_committed_capacity, r_commit);
MEM_STATS_ADD(app_vsize, r_commit + r_reserve);
MEM_STATS_ADD(app_exec_capacity, r_exec);
MEM_STATS_ADD(app_rw_capacity, r_rw);
MEM_STATS_ADD(app_ro_capacity, r_ro);
}
MEM_STATS_ADD(total_vsize, r_commit + r_reserve);
if (!ALIGNED(region, OS_ALLOC_GRANULARITY)) {
STATS_INC(unaligned_allocations);
}
return ours;
}
void
mem_stats_snapshot()
{
PBYTE pb = NULL;
MEMORY_BASIC_INFORMATION mbi;
* so locally cache sum-of-block values until then
*/
size_t r_commit = 0, r_reserve = 0, r_exec = 0, r_ro = 0, r_rw = 0;
bool r_is_stack = false;
uint r_type = 0;
app_pc r_start = NULL;
if (!dynamo_initialized) {
* dump_global_stats() we come here prior to dynamo_areas or DR's
* library bounds being set up: best to just abort until we can
* gather accurate stats.
*/
return;
}
* record prior to NtAllocateVirtualMemory all of the reserved regions to
* know which went from reserved to committed, and on freeing to know what
* was committed and what reserved, etc., so we only do complete snapshots,
* resetting the stats to 0 each time.
*/
d_r_mutex_lock(&snapshot_lock);
STATS_RESET(unaligned_allocations);
STATS_RESET(dr_library_space);
STATS_RESET(dr_commited_capacity);
STATS_RESET(dr_reserved_capacity);
STATS_RESET(total_wasted_vsize);
STATS_RESET(dr_wasted_vsize);
STATS_RESET(app_wasted_vsize);
STATS_RESET(total_vsize);
STATS_RESET(dr_vsize);
STATS_RESET(app_vsize);
STATS_RESET(app_reserved_capacity);
STATS_RESET(app_committed_capacity);
STATS_RESET(app_stack_capacity);
STATS_RESET(app_heap_capacity);
STATS_RESET(app_image_capacity);
STATS_RESET(app_mmap_capacity);
STATS_RESET(app_exec_capacity);
STATS_RESET(app_ro_capacity);
STATS_RESET(app_rw_capacity);
while (query_virtual_memory(pb, &mbi, sizeof(mbi)) == sizeof(mbi)) {
* allocation base into a single region
*/
if (mbi.State == MEM_FREE || mbi.AllocationBase == mbi.BaseAddress) {
bool ours = false;
if (r_start != NULL) {
ours = add_mem_stats(r_start, r_commit, r_reserve, r_is_stack, r_type,
r_exec, r_ro, r_rw);
}
r_commit = r_reserve = r_exec = r_ro = r_rw = 0;
r_is_stack = false;
r_type = mbi.Type;
if (mbi.State == MEM_FREE) {
LOG(GLOBAL, LOG_MEMSTATS, 4, "Free " PFX "-" PFX "\n", mbi.BaseAddress,
((app_pc)mbi.BaseAddress) + mbi.RegionSize);
if (r_start != NULL && !ALIGNED(mbi.BaseAddress, OS_ALLOC_GRANULARITY)) {
* region is unusable */
size_t wasted = ALIGN_FORWARD(mbi.BaseAddress, OS_ALLOC_GRANULARITY) -
(ptr_uint_t)mbi.BaseAddress;
if (ours) {
MEM_STATS_ADD(dr_wasted_vsize, (stats_int_t)wasted);
} else {
MEM_STATS_ADD(app_wasted_vsize, (stats_int_t)wasted);
}
MEM_STATS_ADD(total_wasted_vsize, (stats_int_t)wasted);
}
r_start = NULL;
} else {
r_start = mbi.AllocationBase;
}
}
if (mbi.State == MEM_RESERVE) {
r_reserve += mbi.RegionSize;
} else if (mbi.State == MEM_COMMIT) {
r_commit += mbi.RegionSize;
if (TEST(PAGE_GUARD, mbi.Protect)) {
* is a stack
*/
r_is_stack = true;
}
* for being DR memory.
* mbi.Protect is invalid for reserved memory, only useful for committed.
*/
if (prot_is_executable(mbi.Protect))
r_exec += mbi.RegionSize;
else if (prot_is_writable(mbi.Protect))
r_rw += mbi.RegionSize;
else if (prot_is_readable(mbi.Protect))
r_ro += mbi.RegionSize;
}
if (POINTER_OVERFLOW_ON_ADD(pb, mbi.RegionSize))
break;
pb += mbi.RegionSize;
}
if (r_start != NULL) {
add_mem_stats(r_start, r_commit, r_reserve, r_is_stack, r_type, r_exec, r_ro,
r_rw);
}
STATS_PEAK(unaligned_allocations);
STATS_PEAK(dr_commited_capacity);
STATS_PEAK(dr_reserved_capacity);
STATS_PEAK(total_wasted_vsize);
STATS_PEAK(dr_wasted_vsize);
STATS_PEAK(app_wasted_vsize);
STATS_PEAK(total_vsize);
STATS_PEAK(dr_vsize);
STATS_PEAK(app_vsize);
STATS_PEAK(app_reserved_capacity);
STATS_PEAK(app_committed_capacity);
STATS_PEAK(app_stack_capacity);
STATS_PEAK(app_heap_capacity);
STATS_PEAK(app_image_capacity);
STATS_PEAK(app_mmap_capacity);
STATS_PEAK(app_exec_capacity);
STATS_PEAK(app_ro_capacity);
STATS_PEAK(app_rw_capacity);
d_r_mutex_unlock(&snapshot_lock);
}
# endif
* unset if called when processing a system call for (un)map.
* Returns true if this mapped image is a library.
*/
static bool
process_image(app_pc base, size_t size, uint prot, bool add, bool rewalking,
const char *filepath)
{
const char *name = NULL;
bool module_is_native_exec = false;
bool already_added_native_exec = false;
size_t image_size;
ASSERT(prot_is_readable(prot));
ASSERT(!rewalking || add);
* we still need to be careful to check it is a real PE
* We could optimize out these system calls, but for now staying safe
*/
if (!is_readable_pe_base(base)) {
DOCHECK(1, {
wchar_t buf[MAXIMUM_PATH];
NTSTATUS res = get_mapped_file_name(base, buf, BUFFER_SIZE_BYTES(buf));
if (NT_SUCCESS(res)) {
LOG(GLOBAL, LOG_VMAREAS, 2,
"\t%s: WARNING: image but non-PE mapping @" PFX " backed by \"%S\"\n",
__FUNCTION__, base, buf);
}
* \Device\HarddiskVolume1\Windows\System32\apisetschema.dll
*/
if (!NT_SUCCESS(res) || wcsstr(buf, L"apisetschema") == NULL)
SYSLOG_INTERNAL_WARNING_ONCE("image but non-PE mapping found");
});
return false;
}
* (several are visible: wow64cpu.dll, wow64win.dll, wow64.dll, and ntdll.dll)
* For 64-bit DR both should be handled.
*/
# ifdef X64
DODEBUG({
if (module_is_32bit(base)) {
LOG(GLOBAL, LOG_VMAREAS, 1,
"image " PFX "-" PFX " is 32-bit dll (wow64 process?)\n", base,
base + size);
* child: CreateProcess maps in the child executable in
* this process first (i#817)
*/
ASSERT_CURIOSITY(is_wow64_process(NT_CURRENT_PROCESS) ||
!TEST(IMAGE_FILE_DLL, get_module_characteristics(base)));
}
});
# else
if (module_is_64bit(base)) {
LOG(GLOBAL, LOG_VMAREAS, 1,
"image " PFX "-" PFX " is 64-bit dll (wow64 process?): ignoring it!\n", base,
base + size);
ASSERT(is_wow64_process(NT_CURRENT_PROCESS));
return false;
}
# endif
* not normal libraries loaded by the system loader but instead are
* usually mapped in to read resources or other data from the file.
* If code is executed from a partial map, DR will still perform proper
* cache consistency as that's done in the caller.
* Having native_exec not apply seems ok: we'll err on the side of executing
* it, which is the conservative side. Hot patches and
* patch-proof list should only apply to system-loaded libs.
*/
if (!get_module_info_pe(base, NULL, NULL, &image_size, NULL, NULL) ||
size < image_size) {
LOG(GLOBAL, LOG_VMAREAS, 2,
"not treating partially-mapped (" PIFX " < " PIFX ") image @" PFX
"as module\n",
size, image_size, base);
return false;
}
* hotp_process_image() and any caller of get_module_short_name()
* or other data that we cache in the list.
*/
if (add)
module_list_add(base, size, !rewalking , filepath);
else
os_module_set_flag(base, MODULE_BEING_UNLOADED);
* primary user of module name */
name = os_get_module_name_strdup(base HEAPACCT(ACCT_VMAREAS));
LOG(GLOBAL, LOG_VMAREAS, 1, "image %-15s %smapped @ " PFX "-" PFX "\n",
name == NULL ? "<no name>" : name, add ? "" : "un", base, base + size);
* removing the module, it will also be there from the load earlier.
*/
if (DYNAMO_OPTION(native_exec) &&
vmvector_overlap(native_exec_areas, base, base + size)) {
LOG(GLOBAL, LOG_INTERP | LOG_VMAREAS, 1, "module %s is on native_exec list\n",
name);
module_is_native_exec = true;
already_added_native_exec = true;
# ifdef GBOP
* need better dgc identification for gbop; see case 8087.
*/
if (add && TEST(GBOP_IS_DGC, DYNAMO_OPTION(gbop)) && !gbop_vm_loaded) {
* protection change from happenning for each vm load, not that any
* process load a vm multiple times or multiple vms.
*/
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
gbop_vm_loaded = true;
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
}
# endif
}
moduledb_process_image(name, base, add);
* for now.
* FIXME: should try to execute non-managed code under DR, when possible.
*/
if (DYNAMO_OPTION(native_exec) && DYNAMO_OPTION(native_exec_managed_code) &&
module_has_cor20_header(base)) {
DODEBUG({
if (add) {
LOG(GLOBAL, LOG_INTERP | LOG_VMAREAS, 1,
"process_image: module=%s, base=" PFX " has cor20 header, "
"adding to native exec areas\n",
name ? name : "<noname>", base);
SYSLOG_INTERNAL_INFO_ONCE("cor20 module %s added to native exec area",
name ? name : "<noname>");
}
});
module_is_native_exec = true;
}
* int 3 strangeness. FIXME - restrict further? only observed on Vista, known .pexe
* sections from problematic dlls all begin with mostly the same 0x60 first bytes,
* .pexe is observed to always be the first section, etc. */
if (DYNAMO_OPTION(native_exec) && DYNAMO_OPTION(native_exec_dot_pexe) &&
get_named_section_bounds(base, ".pexe", NULL, NULL)) {
DODEBUG({
if (add) {
LOG(GLOBAL, LOG_INTERP | LOG_VMAREAS, 1,
"process_image: module=%s, base=" PFX " has .pexe section, "
"adding to native exec areas\n",
name ? name : "<noname>", base);
SYSLOG_INTERNAL_INFO(".pexe module %s added to native exec area",
name ? name : "<noname>");
ASSERT_CURIOSITY(
name != NULL &&
check_filter(
"AuxiliaryDisplayCpl.dll;AuxiliaryDisplayDriverLib.dll;"
"AuxiliaryDisplayServices.dll;NetProjW.dll;P2PGraph.dll;"
"localspl.dll;lsasrv.dll;mssrch.dll;p2psvc.dll;pmcsnap.dll;"
"shell32.dll;spoolss.dll;uDWM.dll",
name));
}
});
module_is_native_exec = true;
}
if (module_is_native_exec && add && !already_added_native_exec) {
RSTATS_INC(num_native_module_loads);
vmvector_add(native_exec_areas, base, base + size, NULL);
} else if (!already_added_native_exec) {
* any possibility of having stale entries in the vector overlap into new
* non-native regions. Also see case 7628. */
ASSERT(!module_is_native_exec ||
!vmvector_overlap(native_exec_areas, base, base + size));
vmvector_remove(native_exec_areas, base, base + size);
}
if (!IS_STRING_OPTION_EMPTY(patch_proof_list) ||
!IS_STRING_OPTION_EMPTY(patch_proof_default_list)) {
if ((IS_LISTSTRING_OPTION_FORALL(patch_proof_list) ||
IS_LISTSTRING_OPTION_FORALL(patch_proof_default_list)) ||
(name != NULL &&
check_list_default_and_append(dynamo_options.patch_proof_default_list,
dynamo_options.patch_proof_list, name))) {
if (add) {
LOG(GLOBAL, LOG_INTERP | LOG_VMAREAS, 1,
"module %s is on patch proof list\n", name ? name : "<noname>");
STATS_INC(num_patch_proof_module_loads);
* will prevent them from becoming writable */
* app_memory_protection_change() will filter out only
* CODE. FIXME: [minor perf] alternatively could walk
* module and add only code sections here.
*/
vmvector_add(patch_proof_areas, base, base + size, NULL);
} else {
vmvector_remove(patch_proof_areas, base, base + size);
}
}
}
# ifdef HOT_PATCHING_INTERFACE
if (DYNAMO_OPTION(hot_patching)) {
if (!DYNAMO_OPTION(hotp_only)) {
hotp_process_image(base, add, false, false, NULL, NULL, 0);
} else {
bool needs_processing = false;
int num_threads = 0;
thread_record_t **thread_table = NULL;
* first one is done without suspending all threads (expensive if
* done for each dll load or unload). Only if the first step
* identified a module match, all threads (known to the core, of
* course) are suspended and the image is processed, i.e., hot
* patches are either injected or removed both of which in
* hotp_only need all threads to be suspended.
*/
hotp_process_image(base, add, false ,
* Risk is low; rest of DR assumes it as well.
* Can't do two-step since have no dcontext yet
* and hit synch_with_all_threads assert. */
dynamo_initialized ,
dynamo_initialized ? &needs_processing : NULL, NULL, 0);
if (needs_processing) {
DEBUG_DECLARE(bool ok =)
synch_with_all_threads(THREAD_SYNCH_SUSPENDED, &thread_table,
* only care about threads carrying fcache
* state can ignore us
*/
&num_threads, THREAD_SYNCH_NO_LOCKS_NO_XFER,
* privilege problems) ignore it.
* FIXME: retry instead? */
THREAD_SYNCH_SUSPEND_FAILURE_IGNORE);
ASSERT(ok);
hotp_process_image(base, add, false, false, NULL, thread_table,
num_threads);
end_synch_with_all_threads(thread_table, num_threads, true );
}
}
}
# endif
if (DYNAMO_OPTION(IAT_convert)) {
app_pc IAT_start, IAT_end;
bool valid = get_IAT_section_bounds(base, &IAT_start, &IAT_end);
if (valid && IAT_start != IAT_end) {
LOG(GLOBAL, LOG_INTERP, 2, "module %s IAT(" PFX "," PFX ") %s\n",
name ? name : "<noname>", IAT_start, IAT_end, add ? "added" : "removed");
ASSERT_CURIOSITY(IAT_start != NULL && IAT_end != NULL);
ASSERT(IAT_start < IAT_end);
if (add) {
ASSERT(!vmvector_overlap(IAT_areas, IAT_start, IAT_end));
STATS_INC(num_IAT_areas);
if (!module_is_native_exec) {
LOG(GLOBAL, LOG_INTERP, 1, "module %s IAT(" PFX "," PFX ") added\n",
name ? name : "<noname>", IAT_start, IAT_end);
vmvector_add(IAT_areas, IAT_start, IAT_end, NULL);
} else {
LOG(GLOBAL, LOG_INTERP, 1,
"skipping native module %s IAT(" PFX "," PFX
"), native modules seen\n",
name ? name : "<noname>", IAT_start, IAT_end);
}
} else {
STATS_DEC(num_IAT_areas);
vmvector_remove(IAT_areas, IAT_start, IAT_end);
}
} else {
ASSERT(!valid || IAT_start == base);
ASSERT_CURIOSITY(valid && "bad module");
}
}
# ifdef RETURN_AFTER_CALL
DODEBUG({
if (!add && DYNAMO_OPTION(ret_after_call)) {
* here we just check for exec areas before we flush them */
* e.g. hpzst3zm.dll from case 9121
*/
if (!executable_vm_area_overlap(base, base + size, false )) {
SYSLOG_INTERNAL_WARNING_ONCE(
"DLL with no executable areas " PFX "-" PFX "\n", base, base + size);
}
}
});
# endif
DOLOG(1, LOG_SYMBOLS, {
if (add) {
* FIXME: but, this is MEM_IMAGE, and above we verify the header
* is readable, so we can get rid of all of these system calls here
*/
add_module_info((app_pc)base, size);
} else {
remove_module_info((app_pc)base, size);
}
});
if (name != NULL)
dr_strfree(name HEAPACCT(ACCT_VMAREAS));
return true;
}
* persisted cache loading)
*/
* unset if called when processing a system call for (un)map
*/
static void
process_image_post_vmarea(app_pc base, size_t size, uint prot, bool add, bool rewalking)
{
* (several are visible: wow64cpu.dll, wow64win.dll, wow64.dll, and ntdll.dll)
* This includes a 64-bit child process (i#838).
* For 64-bit DR both should be handled.
*/
# ifndef X64
if (module_is_64bit(base))
return;
# endif
ASSERT(prot_is_readable(prot));
ASSERT(!rewalking || add);
* we still need to be careful to check it is a real PE
* We could optimize out these system calls, but for now staying safe
*/
if (!is_readable_pe_base(base)) {
return;
}
# ifdef RCT_IND_BRANCH
if (TEST(OPTION_ENABLED, DYNAMO_OPTION(rct_ind_call)) ||
TEST(OPTION_ENABLED, DYNAMO_OPTION(rct_ind_jump))) {
* whether or not we'll act on it right now
*/
rct_process_module_mmap(base, size, add, rewalking);
}
# endif
if (!add)
module_list_remove(base, size);
}
* ok for dcontext to be NULL if init==true and add==true
*/
static bool
process_memory_region(dcontext_t *dcontext, MEMORY_BASIC_INFORMATION *mbi, bool init,
bool add)
{
bool from_image = (mbi->Type == MEM_IMAGE);
* (several are visible: wow64cpu.dll, wow64win.dll, wow64.dll, and ntdll.dll)
* We go ahead and track the memory, but we do not treat as an image
*/
if (is_wow64_process(NT_CURRENT_PROCESS) && from_image &&
module_is_64bit(mbi->AllocationBase ))
from_image = false;
ASSERT(dcontext != NULL || (init && add));
DOLOG(2, LOG_VMAREAS, {
if (mbi->State != MEM_FREE) {
LOG(GLOBAL, LOG_VMAREAS, prot_is_executable(mbi->Protect) ? 1U : 2U,
PFX "-" PFX " %s %s allocbase=" PFX "\n", mbi->BaseAddress,
((app_pc)mbi->BaseAddress) + mbi->RegionSize, prot_string(mbi->Protect),
(mbi->State == MEM_RESERVE) ? "reserve" : "commit ", mbi->AllocationBase);
}
});
if (mbi->State != MEM_COMMIT)
return false;
* could have them on future list or something
*/
if (add) {
return app_memory_allocation(
dcontext, mbi->BaseAddress, mbi->RegionSize, osprot_to_memprot(mbi->Protect),
from_image _IF_DEBUG(from_image ? "module" : "alloc"));
} else {
app_memory_deallocation(dcontext, mbi->BaseAddress, mbi->RegionSize,
false , from_image);
}
return false;
}
int
find_executable_vm_areas()
{
PBYTE pb = NULL;
MEMORY_BASIC_INFORMATION mbi;
PBYTE image_base = NULL;
size_t view_size = 0;
uint image_prot = 0;
int num_executable = 0;
LOG(GLOBAL, LOG_VMAREAS, 2, "Executable regions:\n");
DOLOG(1, LOG_MEMSTATS, { mem_stats_snapshot(); });
while (query_virtual_memory(pb, &mbi, sizeof(mbi)) == sizeof(mbi)) {
* on our mod list or executable area list
*/
bool skip = dynamo_vm_area_overlap(pb, pb + mbi.RegionSize) &&
!is_in_dynamo_dll(pb)
&& !is_in_client_lib(pb);
bool full_image = true;
ASSERT(pb == mbi.BaseAddress);
DOLOG(2, LOG_VMAREAS, {
if (skip) {
LOG(GLOBAL, LOG_VMAREAS, 2, PFX "-" PFX " skipping: internal DR region\n",
pb, pb + mbi.RegionSize);
}
});
if (!skip && mbi.State != MEM_FREE && mbi.Type == MEM_IMAGE &&
pb == mbi.AllocationBase) {
MEMORY_BASIC_INFORMATION mbi_image;
PBYTE pb_image = pb + mbi.RegionSize;
image_base = pb;
image_prot = mbi.Protect;
* obtain info when processing each +x region. We need the view size
* to call process_image with so we walk the image here. */
* to not require the view size (by moving more things into
* process_image_post_vmarea or remembering the queryies). */
while (query_virtual_memory(pb_image, &mbi_image, sizeof(mbi_image)) ==
sizeof(mbi_image) &&
mbi_image.State != MEM_FREE && mbi_image.AllocationBase == pb) {
ASSERT(mbi_image.Type == MEM_IMAGE);
pb_image += mbi_image.RegionSize;
}
view_size = pb_image - pb;
full_image = process_image(image_base, view_size, image_prot, true ,
true , NULL);
}
if (!skip && process_memory_region(NULL, &mbi, true , true ))
num_executable++;
if (POINTER_OVERFLOW_ON_ADD(pb, mbi.RegionSize))
break;
pb += mbi.RegionSize;
if (!skip && image_base != NULL && pb >= image_base + view_size) {
ASSERT(pb == image_base + view_size);
if (full_image) {
process_image_post_vmarea(image_base, view_size, image_prot,
true , true );
}
image_base = NULL;
}
}
ASSERT(image_base == NULL);
LOG(GLOBAL, LOG_VMAREAS, 2, "\n");
STATS_ADD(num_app_code_modules, num_executable);
return num_executable;
}
void
all_memory_areas_lock()
{
}
void
all_memory_areas_unlock()
{
}
void
update_all_memory_areas(app_pc start, app_pc end, uint prot, int type)
{
}
bool
remove_from_all_memory_areas(app_pc start, app_pc end)
{
return true;
}
* if add is false, assumes caller has already called flush_fragments_and_remove_region
* for all executable areas in region (probably just for entire super-region).
* returns the number of executable areas added to DR's list
*/
int
process_mmap(dcontext_t *dcontext, app_pc pc, size_t size, bool add, const char *filepath)
{
PBYTE pb;
MEMORY_BASIC_INFORMATION mbi;
app_pc region_base;
int num_executable = 0;
bool image = false;
uint image_prot = 0;
ASSERT(!DYNAMO_OPTION(thin_client));
LOG(GLOBAL, LOG_VMAREAS, 2, "%s exec areas in region " PFX "\n",
add ? "adding" : "removing", pc);
pb = (PBYTE)pc;
if (query_virtual_memory(pb, &mbi, sizeof(mbi)) != sizeof(mbi))
ASSERT(false);
if (mbi.State == MEM_FREE)
return num_executable;
region_base = (app_pc)mbi.AllocationBase;
if (mbi.Type == MEM_IMAGE) {
image = process_image(region_base, size, mbi.Protect, add,
false , filepath);
image_prot = mbi.Protect;
}
* The protection flag doesn't tell us if there are executable areas inside,
* must walk all the individual regions.
* FIXME: for remove, optimize to do single flush but multiple area removals?
*/
while (query_virtual_memory(pb, &mbi, sizeof(mbi)) == sizeof(mbi)) {
if (mbi.State == MEM_FREE || mbi.AllocationBase != region_base)
break;
if (process_memory_region(dcontext, &mbi, false , add)) {
num_executable++;
STATS_INC(num_app_code_modules);
}
if (POINTER_OVERFLOW_ON_ADD(pb, mbi.RegionSize))
break;
pb += mbi.RegionSize;
}
if (image) {
process_image_post_vmarea(region_base, size, image_prot, add,
false );
}
LOG(GLOBAL, LOG_SYSCALLS | LOG_VMAREAS, 3, "Executable areas are now:\n");
DOLOG(3, LOG_SYSCALLS | LOG_VMAREAS, { print_executable_areas(GLOBAL); });
return num_executable;
}
app_pc
get_application_base(void)
{
static app_pc app_start = NULL;
if (app_start == NULL) {
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
app_start = get_own_peb()->ImageBaseAddress;
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
}
return app_start;
}
app_pc
get_application_end(void)
{
static app_pc app_end = NULL;
if (app_end == NULL) {
app_pc start;
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
start = get_own_peb()->ImageBaseAddress;
app_end = start + get_allocation_size(start, NULL);
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
}
return app_end;
}
app_pc
get_image_entry(void)
{
static app_pc image_entry_point = NULL;
if (image_entry_point == NULL) {
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
image_entry_point = get_module_entry(get_own_peb()->ImageBaseAddress);
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
}
return image_entry_point;
}
ushort
os_tls_offset(ushort tls_offs)
{
ASSERT_TRUNCATE(tls_offs, ushort, tls_local_state_offs + tls_offs);
return (ushort)(tls_local_state_offs + tls_offs);
}
ushort
os_local_state_offset(ushort seg_offs)
{
return (ushort)(seg_offs - tls_local_state_offs);
}
local_state_t *
get_local_state()
{
byte *teb_addr = (byte *)get_own_teb();
return (local_state_t *)(teb_addr + tls_local_state_offs);
}
local_state_extended_t *
get_local_state_extended()
{
ASSERT(DYNAMO_OPTION(ibl_table_in_tls));
return (local_state_extended_t *)get_local_state();
}
dcontext_t *
get_thread_private_dcontext(void)
{
if (tls_dcontext_offs == TLS_UNINITIALIZED)
return standalone_library ? GLOBAL_DCONTEXT : NULL;
* We don't need to check whether this thread has been initialized under us -
* Windows sets the value to 0 for us, so we'll just return NULL.
*/
return (dcontext_t *)d_r_get_tls(tls_dcontext_offs);
}
void
set_thread_private_dcontext(dcontext_t *dcontext)
{
d_r_set_tls(tls_dcontext_offs, dcontext);
}
# ifdef WINDOWS_PC_SAMPLE
profile_t *
create_profile(void *start, void *end, uint bucket_shift, dcontext_t *dcontext)
{
profile_t *profile;
size_t buffer_size =
((((ptr_uint_t)end - (ptr_uint_t)start) >> bucket_shift) + 1) * sizeof(uint);
if (dcontext == NULL) {
LOG(GLOBAL, LOG_PROFILE, 1,
"Creating global profile from " PFX " to " PFX " with shift %d "
"for buffer size " SZFMT " bytes\n",
start, end, bucket_shift, buffer_size);
profile = (profile_t *)global_heap_alloc(sizeof(*profile) HEAPACCT(ACCT_STATS));
profile->buffer =
(uint *)UNPROTECTED_GLOBAL_ALLOC(buffer_size HEAPACCT(ACCT_STATS));
} else {
LOG(THREAD, LOG_PROFILE, 1,
"Creating local profile from " PFX " to " PFX " with shift %d "
"(buffer size " SZFMT " bytes)\n",
start, end, bucket_shift, buffer_size);
profile =
(profile_t *)heap_alloc(dcontext, sizeof(*profile) HEAPACCT(ACCT_STATS));
profile->buffer =
(uint *)UNPROTECTED_LOCAL_ALLOC(dcontext, buffer_size HEAPACCT(ACCT_STATS));
}
memset(profile->buffer, 0, buffer_size);
profile->start = start;
profile->end = end;
profile->bucket_shift = bucket_shift;
profile->buffer_size = buffer_size;
profile->enabled = false;
profile->dcontext = dcontext;
IF_X64(ASSERT(CHECK_TRUNCATE_TYPE_uint((byte *)end - (byte *)start)));
IF_X64(ASSERT(CHECK_TRUNCATE_TYPE_uint(buffer_size)));
profile->handle =
nt_create_profile(NT_CURRENT_PROCESS, start, (uint)((byte *)end - (byte *)start),
profile->buffer, (uint)buffer_size, bucket_shift);
return profile;
}
void
free_profile(profile_t *profile)
{
ASSERT(!profile->enabled);
close_handle(profile->handle);
if (profile->dcontext == NULL) {
LOG(GLOBAL, LOG_PROFILE, 2,
"Freeing global profile from " PFX " to " PFX " with shift %d "
"(buffer size " SZFMT " bytes)\n",
profile->start, profile->end, profile->bucket_shift, profile->buffer_size);
UNPROTECTED_GLOBAL_FREE(profile->buffer,
profile->buffer_size HEAPACCT(ACCT_STATS));
global_heap_free(profile, sizeof(*profile) HEAPACCT(ACCT_STATS));
} else {
dcontext_t *dcontext = profile->dcontext;
LOG(THREAD, LOG_PROFILE, 2,
"Freeing local profile from " PFX " to " PFX " with shift %d "
"(buffer size " SZFMT " bytes)\n",
profile->start, profile->end, profile->bucket_shift, profile->buffer_size);
UNPROTECTED_LOCAL_FREE(dcontext, profile->buffer,
profile->buffer_size HEAPACCT(ACCT_STATS));
heap_free(dcontext, profile, sizeof(*profile) HEAPACCT(ACCT_STATS));
}
}
void
start_profile(profile_t *profile)
{
ASSERT(!profile->enabled);
nt_start_profile(profile->handle);
profile->enabled = true;
}
void
stop_profile(profile_t *profile)
{
ASSERT(profile->enabled);
nt_stop_profile(profile->handle);
profile->enabled = false;
}
void
dump_profile_range(file_t file, profile_t *profile, byte *start, byte *end)
{
uint i, step = (1 << profile->bucket_shift);
uint start_i = (uint)(start - (byte *)profile->start) / step;
uint end_i = (uint)(end - (byte *)profile->start) / step;
IF_X64(ASSERT_TRUNCATE(start_i, uint, (start - (byte *)profile->start) / step));
IF_X64(ASSERT_TRUNCATE(start_i, uint, (end - (byte *)profile->start) / step));
print_file(file, "Profile Dump\nRange " PFX "-" PFX "\nStep " PFX " (%u-%u)\n", start,
end, step, start_i, end_i);
ASSERT(start_i < profile->buffer_size / sizeof(uint) &&
end_i < profile->buffer_size / sizeof(uint));
for (i = start_i; i <= end_i; i++) {
if (profile->buffer[i] > 0) {
print_file(file, PFX " %10d\n", (byte *)profile->start + i * step,
profile->buffer[i]);
}
}
print_file(file, "Finished Profile Dump\n");
}
void
dump_profile(file_t file, profile_t *profile)
{
dump_profile_range(file, profile, (byte *)profile->start, (byte *)profile->end);
}
uint
sum_profile_range(profile_t *profile, byte *start, byte *end)
{
uint i, ret = 0, step = (1 << profile->bucket_shift);
uint start_i = (uint)(start - (byte *)profile->start) / step;
uint end_i = (uint)(end - (byte *)profile->start) / step;
IF_X64(ASSERT_TRUNCATE(start_i, uint, (start - (byte *)profile->start) / step));
IF_X64(ASSERT_TRUNCATE(start_i, uint, (end - (byte *)profile->start) / step));
ASSERT(start_i < profile->buffer_size / sizeof(uint) &&
end_i < profile->buffer_size / sizeof(uint));
for (i = start_i; i <= end_i; i++) {
if (profile->buffer[i] > 0)
ret += profile->buffer[i];
}
return ret;
}
uint
sum_profile(profile_t *profile)
{
return sum_profile_range(profile, (byte *)profile->start, (byte *)profile->end);
}
void
reset_profile(profile_t *profile)
{
memset(profile->buffer, 0, profile->buffer_size);
}
# endif
bool
os_raw_mem_free(void *p, size_t size, uint flags, heap_error_code_t *error_code)
{
ASSERT(error_code != NULL);
ASSERT(size > 0 && ALIGNED(size, PAGE_SIZE));
if (!TEST(RAW_ALLOC_RESERVE_ONLY, flags)) {
*error_code = nt_decommit_virtual_memory(p, size);
if (!NT_SUCCESS(*error_code))
return false;
}
if (!TEST(RAW_ALLOC_COMMIT_ONLY, flags))
*error_code = nt_free_virtual_memory(p);
LOG(GLOBAL, LOG_HEAP, 2, "os_raw_mem_free: " SZFMT " bytes @ " PFX "\n", size, p);
return NT_SUCCESS(*error_code);
}
void *
os_raw_mem_alloc(void *preferred, size_t size, uint prot, uint flags,
heap_error_code_t *error_code)
{
void *p = preferred;
uint os_prot = memprot_to_osprot(prot);
ASSERT(error_code != NULL);
ASSERT(size > 0 && ALIGNED(size, PAGE_SIZE));
*error_code = nt_allocate_virtual_memory(
&p, size, os_prot,
TEST(RAW_ALLOC_RESERVE_ONLY, flags)
? MEMORY_RESERVE_ONLY
: (TEST(RAW_ALLOC_COMMIT_ONLY, flags) ? MEM_COMMIT : MEMORY_COMMIT));
if (!NT_SUCCESS(*error_code)) {
LOG(GLOBAL, LOG_HEAP, 3, "os_raw_mem_alloc %d bytes failed" PFX "\n", size, p);
return NULL;
}
LOG(GLOBAL, LOG_HEAP, 2, "os_raw_mem_alloc: " SZFMT " bytes @ " PFX "\n", size, p);
return p;
}
* pointer */
void
os_heap_free(void *p, size_t size, heap_error_code_t *error_code)
{
ASSERT(error_code != NULL);
DOSTATS({
if (!dynamo_exited_log_and_stats) {
LOG(GLOBAL, LOG_HEAP, 4, "os_heap_free: " SZFMT " bytes @ " PFX "\n", size,
p);
}
});
*error_code = nt_free_virtual_memory(p);
ASSERT(NT_SUCCESS(*error_code));
}
* and of course no physical pages since it will never be touched.
* executable arg is ignored.
*/
void *
os_heap_reserve(void *preferred, size_t size, heap_error_code_t *error_code,
bool executable)
{
void *p = preferred;
ASSERT(error_code != NULL);
ASSERT(size > 0 && ALIGNED(size, PAGE_SIZE));
*error_code =
nt_allocate_virtual_memory(&p, size, PAGE_NOACCESS, MEMORY_RESERVE_ONLY);
if (!NT_SUCCESS(*error_code))
return NULL;
LOG(GLOBAL, LOG_HEAP, 2, "os_heap_reserve: " SZFMT " bytes @ " PFX "\n", size, p);
ASSERT(preferred == NULL || p == preferred);
return p;
}
static bool
find_free_memory_in_region(byte *start, byte *end, size_t size, byte **found_start OUT,
byte **found_end OUT)
{
byte *cur;
MEMORY_BASIC_INFORMATION mbi;
cur = (byte *)ALIGN_FORWARD(start, VM_ALLOCATION_BOUNDARY);
if (cur == NULL)
cur = (byte *)(ptr_uint_t)VM_ALLOCATION_BOUNDARY;
while (cur + size <= (byte *)end &&
query_virtual_memory(cur, &mbi, sizeof(mbi)) == sizeof(mbi)) {
if (mbi.State == MEM_FREE &&
mbi.RegionSize - (cur - (byte *)mbi.BaseAddress) >= size) {
if (found_start != NULL)
*found_start = cur;
if (found_end != NULL)
*found_end = (byte *)mbi.BaseAddress + mbi.RegionSize;
return true;
}
cur = (byte *)ALIGN_FORWARD((byte *)mbi.BaseAddress + mbi.RegionSize,
VM_ALLOCATION_BOUNDARY);
if (cur <= (byte *)mbi.BaseAddress)
break;
}
return false;
}
void *
os_heap_reserve_in_region(void *start, void *end, size_t size,
heap_error_code_t *error_code, bool executable)
{
byte *try_start, *p = NULL;
uint iters = 0;
# define MAX_REGION_ITERS 100
ASSERT(ALIGNED(start, PAGE_SIZE) && ALIGNED(end, PAGE_SIZE));
ASSERT(ALIGNED(size, PAGE_SIZE));
ASSERT(start < end);
LOG(GLOBAL, LOG_HEAP, 3,
"os_heap_reserve_in_region: " SZFMT " bytes in " PFX "-" PFX "\n", size, start,
end);
if (start == (void *)PTR_UINT_0 && end == (void *)POINTER_MAX)
return os_heap_reserve(NULL, size, error_code, executable);
*error_code = HEAP_ERROR_CANT_RESERVE_IN_REGION;
while (find_free_memory_in_region(start, end, size, &try_start, NULL)) {
p = (byte *)os_heap_reserve(try_start, size, error_code, executable);
LOG(GLOBAL, LOG_HEAP, (p == NULL) ? 1U : 3U,
"os_heap_reserve_in_region: got " PFX " reserving " SZFMT " byte @ " PFX "\n",
p, size, try_start);
if (p != NULL)
break;
if (++iters > MAX_REGION_ITERS) {
ASSERT_NOT_REACHED();
break;
}
}
LOG(GLOBAL, LOG_HEAP, 2,
"os_heap_reserve_in_region: reserved " SZFMT " bytes @ " PFX " in " PFX "-" PFX
"\n",
size, p, start, end);
return p;
}
and os_heap_commit on a subset of the reserved pages. */
bool
os_heap_commit(void *p, size_t size, uint prot, heap_error_code_t *error_code)
{
uint os_prot = memprot_to_osprot(prot);
ASSERT(error_code != NULL);
ASSERT(size > 0 && ALIGNED(size, PAGE_SIZE));
ASSERT(p);
LOG(GLOBAL, LOG_HEAP, 4, "os_heap_commit attempt: " SZFMT " bytes @ " PFX "\n", size,
p);
*error_code = nt_commit_virtual_memory(p, size, os_prot);
if (!NT_SUCCESS(*error_code))
return false;
LOG(GLOBAL, LOG_HEAP, 3, "os_heap_commit: " SZFMT " bytes @ " PFX "\n", size, p);
return true;
}
void
os_heap_decommit(void *p, size_t size, heap_error_code_t *error_code)
{
ASSERT(error_code != NULL);
if (!dynamo_exited) {
LOG(GLOBAL, LOG_HEAP, 3, "os_heap_decommit: " SZFMT " bytes @ " PFX "\n", size,
p);
}
*error_code = nt_decommit_virtual_memory(p, size);
ASSERT(NT_SUCCESS(*error_code));
}
bool
os_heap_systemwide_overcommit(heap_error_code_t last_error_code)
{
* e.g. for win32/ STATUS_COMMITMENT_MINIMUM may be a good one
* to retry, and maybe worth trying if systemwide memory
* pressure has brought us to the limit
*
* FIXME: case 7032 covers detecting this. In fact a pagefile resize,
* will also cause an allocation failure, and TotalCommitLimit seems to be
* the current pagefile size + physical memory not used by the OS.
*
* PeakCommitment should be close to TotalCommitLimit, unless
* the pagefile has been resized, or if the OS has trimmed the
* system cache and has made it available in the
* TotalCommitLimit
*/
return true;
}
bool
os_heap_get_commit_limit(size_t *commit_used, size_t *commit_limit)
{
NTSTATUS res;
SYSTEM_PERFORMANCE_INFORMATION sperf_info;
STATS_INC(commit_limit_queries);
res =
query_system_info(SystemPerformanceInformation, sizeof(sperf_info), &sperf_info);
if (NT_SUCCESS(res)) {
*commit_used = sperf_info.TotalCommittedPages;
*commit_limit = sperf_info.TotalCommitLimit;
return true;
} else {
LOG(GLOBAL, LOG_ALL, 1, "ERROR: query_system_info failed 0x%x\n", res);
ASSERT_NOT_REACHED();
return false;
}
}
* allocate new memory within rel32 distance. Thus we clobber the padding at
* the end of x64 ntdll.dll's +rx section. For typical x64 landing pads w/
* returned memory that need 5 bytes for displaced code, we need 19+5=24 bytes
* each. We use 35 landing pads in a normal run. That's 0x348 bytes, so we
* will fail if a new version of x64 ntdll uses more than 0xcb8 of its final +rx
* page (FTR, only the win2003 versions of x64 ntdll have ever done this).
*
* Currently looks for one contiguous piece of executable memory and returns it.
* Does not mark it as used so will return the same piece to subsequent callers!
*
* XXX: If this isn't enough space, we should support multiple regions
* (end of .text has its own padding, separate from end of "RT" which
* this returns), look for padding inside .text (have to be careful
* there), and/or split the landing pads up to do 6-byte hooks with
* only an 8-byte target and give up on hook chaining robustness.
*/
bool
os_find_free_code_space_in_libs(void **start OUT, void **end OUT)
{
app_pc rx_end_nopad, rx_end_padded;
ASSERT_CURIOSITY(get_os_version() >= WINDOWS_VERSION_8 &&
is_wow64_process(NT_CURRENT_PROCESS) && IF_X64_ELSE(true, false));
if (!get_executable_segment(get_ntdll_base(), NULL, &rx_end_padded, &rx_end_nopad))
return false;
if (start != NULL)
*start = rx_end_nopad;
if (end != NULL)
*end = rx_end_padded;
return true;
}
void
os_thread_yield()
{
nt_yield();
}
void
os_thread_sleep(uint64 milliseconds)
{
LARGE_INTEGER liDueTime;
liDueTime.QuadPart = -(int64)milliseconds * TIMER_UNITS_PER_MILLISECOND;
nt_sleep(&liDueTime);
}
*/
int
os_timeout(int time_in_milliseconds)
{
int res;
LARGE_INTEGER liDueTime;
liDueTime.QuadPart = -time_in_milliseconds * TIMER_UNITS_PER_MILLISECOND;
LOG(THREAD_GET, LOG_ALL, 2, "os_timeout(%d)\n", time_in_milliseconds);
res = nt_sleep(&liDueTime);
LOG(THREAD_GET, LOG_ALL, 2, "Timeout expired res=%d.\n", res);
return res;
}
bool
os_thread_suspend(thread_record_t *tr)
{
return nt_thread_suspend(tr->handle, NULL);
}
bool
os_thread_resume(thread_record_t *tr)
{
return nt_thread_resume(tr->handle, NULL);
}
bool
os_thread_terminate(thread_record_t *tr)
{
return nt_terminate_thread(tr->handle, 0);
}
bool
thread_get_mcontext(thread_record_t *tr, priv_mcontext_t *mc)
{
DWORD cxt_flags = CONTEXT_DR_STATE;
size_t bufsz = nt_get_context_size(cxt_flags);
char *buf = (char *)heap_alloc(tr->dcontext, bufsz HEAPACCT(ACCT_THREAD_MGT));
CONTEXT *cxt = nt_initialize_context(buf, bufsz, cxt_flags);
bool res = false;
if (thread_get_context(tr, cxt)) {
context_to_mcontext(mc, cxt);
res = true;
}
heap_free(tr->dcontext, buf, bufsz HEAPACCT(ACCT_THREAD_MGT));
return res;
}
bool
thread_set_mcontext(thread_record_t *tr, priv_mcontext_t *mc)
{
DWORD cxt_flags = CONTEXT_DR_STATE;
size_t bufsz = nt_get_context_size(cxt_flags);
char *buf = (char *)heap_alloc(tr->dcontext, bufsz HEAPACCT(ACCT_THREAD_MGT));
CONTEXT *cxt = nt_initialize_context(buf, bufsz, cxt_flags);
* segments are correctly set.
*/
thread_get_context(tr, cxt);
mcontext_to_context(cxt, mc, false );
bool res = thread_set_context(tr, cxt);
heap_free(tr->dcontext, buf, bufsz HEAPACCT(ACCT_THREAD_MGT));
return res;
}
bool
thread_get_context(thread_record_t *tr, CONTEXT *context)
{
return NT_SUCCESS(nt_get_context(tr->handle, context));
}
bool
thread_set_context(thread_record_t *tr, CONTEXT *context)
{
return NT_SUCCESS(nt_set_context(tr->handle, context));
}
void
thread_set_self_context(void *cxt)
{
nt_continue((CONTEXT *)cxt);
ASSERT_NOT_REACHED();
}
void
thread_set_self_mcontext(priv_mcontext_t *mc)
{
* We assume call paths can handle a large stack buffer as size something
* larger than the largest Win10 x64 CONTEXT at this time, which is 3375 bytes.
*/
char buf[4096];
DWORD cxt_flags = CONTEXT_DR_STATE;
size_t bufsz = nt_get_context_size(cxt_flags);
CONTEXT *cxt;
if (bufsz > sizeof(buf)) {
* We could conceivably store it in the dcontext for freeing later.
*/
SYSLOG_INTERNAL_WARNING_ONCE("CONTEXT stack buffer too small in %s",
__FUNCTION__);
char *lost = (char *)global_heap_alloc(bufsz HEAPACCT(ACCT_THREAD_MGT));
cxt = nt_initialize_context(lost, bufsz, cxt_flags);
} else
cxt = nt_initialize_context(buf, bufsz, cxt_flags);
mcontext_to_context(cxt, mc, true );
thread_set_self_context(cxt);
ASSERT_NOT_REACHED();
}
DR_API
bool
dr_mcontext_to_context(CONTEXT *dst, dr_mcontext_t *src)
{
* exposing nt_initialize_context()?
* XXX: should we add the reverse, dr_context_to_mcontext()?
*/
if (src->size != sizeof(dr_mcontext_t))
return false;
* We want to keep the assert to catch invalid internal uses, so we just
* fill it all in and then adjust the flags.
*/
if (TEST(DR_MC_MULTIMEDIA, src->flags))
dst->ContextFlags = CONTEXT_DR_STATE;
else
dst->ContextFlags = CONTEXT_DR_STATE_NO_YMM;
mcontext_to_context(dst, dr_mcontext_as_priv_mcontext(src),
true );
* We document this difference and recommend passing both to avoid problems.
*/
if (!TEST(DR_MC_INTEGER, src->flags))
dst->ContextFlags &= ~(CONTEXT_INTEGER);
if (!TEST(DR_MC_CONTROL, src->flags))
dst->ContextFlags &= ~(CONTEXT_CONTROL);
return true;
}
static dr_mcontext_flags_t
match_mcontext_flags_to_context_flags(dr_mcontext_flags_t mc_flags, DWORD cxt_flags)
{
if (TEST(DR_MC_INTEGER, mc_flags) && !TESTALL(CONTEXT_INTEGER, cxt_flags))
mc_flags &= ~DR_MC_INTEGER;
if (TEST(DR_MC_CONTROL, mc_flags) && !TESTALL(CONTEXT_CONTROL, cxt_flags))
mc_flags &= ~DR_MC_CONTROL;
if (TEST(DR_MC_MULTIMEDIA, mc_flags) &&
!TESTALL(CONTEXT_DR_STATE & ~(CONTEXT_INTEGER | CONTEXT_CONTROL), cxt_flags))
mc_flags &= ~DR_MC_MULTIMEDIA;
return mc_flags;
}
bool
os_context_to_mcontext(dr_mcontext_t *dmc, priv_mcontext_t *mc, os_cxt_ptr_t osc)
{
if (dmc != NULL) {
* come here on NtContinue where often only CONTROL|INTEGER|SEGMENTS are
* available. Our general strategy: keep context_to_mcontext() happy and fix
* up here. We assume it's ok to clobber parts of dmc not requested by its
* flags, and ok to temporarily write to osc, even though it may be app
* memory.
*/
DWORD orig_flags = osc->ContextFlags;
if (!TESTALL(CONTEXT_DR_STATE_NO_YMM, osc->ContextFlags))
osc->ContextFlags = CONTEXT_DR_STATE_NO_YMM;
context_to_mcontext(dr_mcontext_as_priv_mcontext(dmc), osc);
osc->ContextFlags = orig_flags;
* events instead of the kernel xfer events that come through here.
*/
dmc->flags = match_mcontext_flags_to_context_flags(dmc->flags, orig_flags);
} else if (mc != NULL) {
* happen in the code base currently.
*/
ASSERT(TESTALL(CONTEXT_DR_STATE_NO_YMM, osc->ContextFlags));
context_to_mcontext(mc, osc);
} else
return false;
return true;
}
bool
mcontext_to_os_context(os_cxt_ptr_t osc, dr_mcontext_t *dmc, priv_mcontext_t *mc)
{
if (dmc != NULL) {
* events instead of the kernel xfer events that come through here.
*/
dmc->flags = match_mcontext_flags_to_context_flags(dmc->flags, osc->ContextFlags);
dr_mcontext_to_context(osc, dmc);
} else if (mc != NULL)
mcontext_to_context(osc, mc, true );
else
return false;
return true;
}
int
get_num_processors()
{
static uint num_cpu = 0;
if (!num_cpu) {
SYSTEM_BASIC_INFORMATION sbasic_info;
NTSTATUS result = query_system_info(
SystemBasicInformation, sizeof(SYSTEM_BASIC_INFORMATION), &sbasic_info);
if (!NT_SUCCESS(result))
num_cpu = 1;
else
num_cpu = sbasic_info.NumberProcessors;
ASSERT(num_cpu);
}
return num_cpu;
}
* at os_init (whichever is earlier), we are guaranteed to be single threaded
* at os_init so no race conditions even though there shouldn't be any anyways
* unless snwprintf does something funny with the buffer. This also ensures
* that the static buffers in get_application_name and get_application_pid
* get initialized while we are still single threaded. */
static wchar_t debugbox_title_buf[MAXIMUM_PATH + 64];
void
debugbox_setup_title(void)
{
snwprintf(debugbox_title_buf, BUFFER_SIZE_ELEMENTS(debugbox_title_buf),
L"%hs Notice: %hs(%hs)", exception_label_core, get_application_name(),
get_application_pid());
NULL_TERMINATE_BUFFER(debugbox_title_buf);
}
const wchar_t *
debugbox_get_title(void)
{
return debugbox_title_buf;
}
* the big space hogs when reporting a crash and we risk exhausting
* the stack.
*/
DECLARE_NEVERPROT_VAR(static wchar_t debugbox_msg_buf[MAX_LOG_LENGTH], { 0 });
int
debugbox(char *msg)
{
int res;
if (debugbox_title_buf[0] == 0)
debugbox_setup_title();
* we come back here.
*/
d_r_mutex_lock(&debugbox_lock);
snwprintf(debugbox_msg_buf, BUFFER_SIZE_ELEMENTS(debugbox_msg_buf), L"%hs", msg);
NULL_TERMINATE_BUFFER(debugbox_msg_buf);
res = nt_messagebox(debugbox_msg_buf, debugbox_title_buf);
d_r_mutex_unlock(&debugbox_lock);
return res;
}
# ifdef FANCY_COUNTDOWN
# define ID_MSGBOX_STATIC_TEXT 0x0000ffff
typedef struct {
char *message;
char *title;
HANDLE timer;
int seconds_left;
bool done;
} timeout_context_t;
# define print_timeout_message(buf, context) \
snprintf(buf, sizeof(buf), \
"%s\n" \
"You have %d seconds to respond", \
((timeout_context_t *)context)->message, \
((timeout_context_t *)context)->seconds_left);
don't intercept its asynch events. Not clear how to do that -- you can
turn off interception once it's created, but to not intercept its init
APC, currently all you can do is globally turn off event interception,
or else try to identify it when we see the init APC. */
DWORD WINAPI
message_box_timeout_thread(void *context)
{
timeout_context_t *tcontext = (timeout_context_t *)context;
return 0;
LOG(GLOBAL, LOG_ALL, 2, "message_box_timeout_thread(%d)\n", tcontext->seconds_left);
do {
WaitForSingleObject(tcontext->timer, tcontext->seconds_left * 1000);
{
HWND hwnd = FindWindow(NULL, tcontext->title);
LOG(THREAD_GET, LOG_ALL, 2, "message_box_timeout_thread(%d) hwnd=" PIFX "\n",
tcontext->seconds_left, hwnd);
if (hwnd != NULL) {
char countdown[MAX_LOG_LENGTH];
tcontext->seconds_left--;
print_timeout_message(countdown, context);
SetDlgItemText(hwnd, ID_MSGBOX_STATIC_TEXT, countdown);
if (tcontext->seconds_left == 0) {
EndDialog(hwnd, IDOK);
return 1;
}
}
}
} while (!tcontext->done);
return 0;
}
int
os_countdown_messagebox(char *message, int time_in_milliseconds)
{
char title[MAXIMUM_PATH + 64];
char buf[MAX_LOG_LENGTH];
LONG update_period = 1000;
uint seconds_left = time_in_milliseconds / update_period;
LARGE_INTEGER liDueTime;
HANDLE htimer;
HANDLE hthread;
timeout_context_t context = { message, title, 0, seconds_left, false };
int res;
LOG(THREAD_GET, LOG_ALL, 2, "os_countdown_messagebox(%s, %d)\n", message,
time_in_milliseconds);
ASSERT_NOT_IMPLEMENTED(false);
get_debugbox_title(title, sizeof(title));
print_timeout_message(buf, &context);
liDueTime.QuadPart = -update_period * TIMER_UNITS_PER_MILLISECOND;
htimer = nt_create_and_set_timer(&liDueTime, update_period);
context.timer = htimer;
hthread = CreateThread(NULL, 0, &message_box_timeout_thread, NULL, 0, NULL);
LOG(THREAD_GET, LOG_ALL, 2, "os_countdown_messagebox(%s, %d)\n", message,
time_in_milliseconds);
debugbox(buf);
context.done = true;
WaitForSingleObject(hthread, INFINITE);
close_handle(htimer);
close_handle(hthread);
return 0;
}
# else
int
os_countdown_messagebox(char *message, int time_in_milliseconds)
{
char buf[MAX_LOG_LENGTH];
snprintf(buf, sizeof(buf), "%sTimeout ignored", message);
NULL_TERMINATE_BUFFER(buf);
debugbox(buf);
return 0;
}
# endif
shlib_handle_t
load_shared_library(const char *name, bool client)
{
# ifdef STATIC_LIBRARY
if (strcmp(name, get_application_name()) == 0) {
wchar_t wname[MAX_PATH];
snwprintf(wname, BUFFER_SIZE_ELEMENTS(wname), L"%hs", name);
NULL_TERMINATE_BUFFER(wname);
return get_module_handle(wname);
}
# endif
if (INTERNAL_OPTION(private_loader)) {
* a pathless name.
*/
return (shlib_handle_t)locate_and_load_private_library(name, client);
} else {
wchar_t buf[MAX_PATH];
snwprintf(buf, BUFFER_SIZE_ELEMENTS(buf), L"%hs", name);
NULL_TERMINATE_BUFFER(buf);
return load_library(buf);
}
}
shlib_routine_ptr_t
lookup_library_routine(shlib_handle_t lib, const char *name)
{
return (shlib_routine_ptr_t)d_r_get_proc_address(lib, name);
}
void
unload_shared_library(shlib_handle_t lib)
{
if (INTERNAL_OPTION(private_loader)) {
unload_private_library((app_pc)lib);
} else {
free_library(lib);
}
}
void
shared_library_error(char *buf, int maxlen)
{
* to report errors, but now that we are kernel32 independent that will no
* longer work. Would be nice if we could do something with the nt status
* codes, but unclear how to propagate them to here. */
buf[0] = '\0';
}
* for linux, one of addr or name is needed; for windows, neither is needed.
*/
bool
shared_library_bounds(IN shlib_handle_t lib, IN byte *addr, IN const char *name,
OUT byte **start, OUT byte **end)
{
size_t sz = get_allocation_size(lib, start);
ASSERT(start != NULL && end != NULL);
*end = *start + sz;
ASSERT(addr == NULL || (addr >= *start && addr < *end));
return true;
}
* Note the current protection settings may not be uniform in the whole region.
* Returns NULL for free memory or invalid user mode addresses.
* Use get_allocation_size() when size is also needed.
*/
byte *
get_allocation_base(byte *pc)
{
MEMORY_BASIC_INFORMATION mbi;
size_t res = query_virtual_memory(pc, &mbi, sizeof(mbi));
if (res != sizeof(mbi)) {
return NULL;
}
if (mbi.State == MEM_FREE) {
ASSERT_CURIOSITY(mbi.BaseAddress == (byte *)ALIGN_BACKWARD(pc, PAGE_SIZE));
return NULL;
}
return mbi.AllocationBase;
}
* we won't infinite loop. Until then we keep it very large
* (we've seen 128MB with a ton of single-page regions inside in case 4502)
* such that should never hit it (@ 1 block per page will hit 4GB first)
*/
enum { MAX_QUERY_VM_BLOCKS = 512 * 1024 };
* Note that this may include several pieces of memory with different
* protection and state attributes.
* If base_pc != NULL returns base pc as well
* If memory is free we set base_pc to NULL, but return free region
* size - note that we can't efficiently go backwards to find the
* maximum possible allocation size in a free hole.
*/
static size_t
get_allocation_size_ex(HANDLE process, byte *pc, byte **base_pc)
{
PBYTE pb = (PBYTE)pc;
MEMORY_BASIC_INFORMATION mbi;
PVOID region_base;
PVOID pb_base;
size_t pb_size;
NTSTATUS res;
int num_blocks = 0;
size_t size, got;
res = nt_remote_query_virtual_memory(process, pb, &mbi, sizeof(mbi), &got);
if (!NT_SUCCESS(res) || got != sizeof(mbi)) {
LOG(THREAD_GET, LOG_VMAREAS, 3, "%s failed to query " PFX "\n", pb);
if (base_pc != NULL)
*base_pc = NULL;
return 0;
}
if (mbi.State == MEM_FREE ) {
LOG(THREAD_GET, LOG_VMAREAS, 3, "%s memory is free " PFX "\n", pb);
if (base_pc != NULL)
*base_pc = NULL;
return mbi.RegionSize;
}
pb_base = mbi.BaseAddress;
pb_size = mbi.RegionSize;
region_base = mbi.AllocationBase;
pb = (byte *)pb_base + mbi.RegionSize;
size = (app_pc)pb - (app_pc)region_base;
* to find this region's size
*/
LOG(THREAD_GET, LOG_VMAREAS, 3,
"%s pc=" PFX " base=" PFX " region=" PFX " size=" PIFX "\n", __FUNCTION__, pc,
pb_base, region_base, mbi.RegionSize);
do {
res = nt_remote_query_virtual_memory(process, pb, &mbi, sizeof(mbi), &got);
LOG(THREAD_GET, LOG_VMAREAS, 4,
"%s pc=" PFX " base=" PFX " type=" PIFX " region=" PFX " size=" PIFX "\n",
__FUNCTION__, pb, mbi.BaseAddress, mbi.State, mbi.AllocationBase,
mbi.RegionSize);
if (!NT_SUCCESS(res) || got != sizeof(mbi) || mbi.State == MEM_FREE ||
mbi.AllocationBase != region_base)
break;
ASSERT(mbi.RegionSize > 0);
size += mbi.RegionSize;
if (POINTER_OVERFLOW_ON_ADD(pb, mbi.RegionSize))
break;
pb += mbi.RegionSize;
* it, we could have problems: but, if region becomes free, we'll break,
* and so long as RegionSize > 0, we should make progress and hit
* end of address space in worst case -- so we shouldn't need this
* num_blocks max, but we'll keep it for now. FIXME.
*/
num_blocks++;
} while (num_blocks < MAX_QUERY_VM_BLOCKS);
ASSERT_CURIOSITY(num_blocks < MAX_QUERY_VM_BLOCKS);
ASSERT((ptr_uint_t)region_base + size > (ptr_uint_t)pc ||
(app_pc)region_base + size == NULL);
if (base_pc != NULL)
*base_pc = (byte *)region_base;
return size;
}
size_t
get_allocation_size(byte *pc, byte **base_pc)
{
return get_allocation_size_ex(NT_CURRENT_PROCESS, pc, base_pc);
}
static void
set_memtype_from_mbi(MEMORY_BASIC_INFORMATION *mbi, OUT dr_mem_info_t *info)
{
if (mbi->State == MEM_FREE) {
info->type = DR_MEMTYPE_FREE;
info->prot = osprot_to_memprot(mbi->Protect);
} else if (mbi->State == MEM_RESERVE) {
info->type = DR_MEMTYPE_RESERVED;
info->prot = DR_MEMPROT_NONE;
} else {
info->prot = osprot_to_memprot(mbi->Protect);
if (mbi->Type == MEM_IMAGE)
info->type = DR_MEMTYPE_IMAGE;
else
info->type = DR_MEMTYPE_DATA;
}
}
* containing pc. This is a single memory area all from the same allocation
* region and all with the same protection and state attributes.
*/
static bool
query_memory_internal(const byte *pc, OUT dr_mem_info_t *info,
bool get_real_base)
{
MEMORY_BASIC_INFORMATION mbi;
byte *pb = (byte *)pc;
byte *alloc_base;
int num_blocks = 0;
ASSERT(info != NULL);
if (query_virtual_memory(pb, &mbi, sizeof(mbi)) != sizeof(mbi)) {
* distinguish that from some other failure (i#1538).
*/
if (!is_user_address((app_pc)pc))
info->type = DR_MEMTYPE_ERROR_WINKERNEL;
else
info->type = DR_MEMTYPE_ERROR;
return false;
}
if (mbi.State == MEM_FREE ||
!get_real_base) {
info->base_pc = mbi.BaseAddress;
info->size = mbi.RegionSize;
set_memtype_from_mbi(&mbi, info);
return true;
} else {
* want: we have to loop for that information (i#345)
*/
byte *forward_query_start;
alloc_base = (byte *)mbi.AllocationBase;
forward_query_start = alloc_base;
* seen 10,000+ subregions), so we first try to walk backward and find
* a different region to start from instead of the alloc base.
* Experimentally this is worthwhile for even just >PAGE_SIZE differences
* and not just OS_ALLOC_GRANULARITY or larger.
* We subtract exponentially larger amounts, up to 2^13 to cover large
* reservations.
*/
# define MAX_BACK_QUERY_HEURISTIC 14
if ((size_t)(pc - alloc_base) > PAGE_SIZE) {
uint exponential = 1;
pb = (byte *)ALIGN_BACKWARD(pc - PAGE_SIZE, PAGE_SIZE);
do {
if (query_virtual_memory(pb, &mbi, sizeof(mbi)) != sizeof(mbi) ||
mbi.State == MEM_FREE ||
mbi.AllocationBase != alloc_base || mbi.RegionSize == 0)
break;
if ((byte *)mbi.BaseAddress + mbi.RegionSize <= pc) {
forward_query_start = (byte *)mbi.BaseAddress + mbi.RegionSize;
break;
}
if (POINTER_UNDERFLOW_ON_SUB(pb, PAGE_SIZE * exponential))
break;
pb -= PAGE_SIZE * exponential;
num_blocks++;
exponential *= 2;
} while (pb > alloc_base && num_blocks < MAX_BACK_QUERY_HEURISTIC);
}
pb = forward_query_start;
num_blocks = 0;
do {
if (query_virtual_memory(pb, &mbi, sizeof(mbi)) != sizeof(mbi))
break;
if (mbi.State == MEM_FREE)
break;
ASSERT(mbi.RegionSize > 0);
if ((byte *)mbi.BaseAddress + mbi.RegionSize > pc) {
* and this time mbi.BaseAddress is the real lowest base of
* that all-same-prot region
*/
ASSERT(pc >= (byte *)mbi.BaseAddress);
* target b/c we've seen cases with anomalous alloc bases: i#4588.
*/
ASSERT(mbi.AllocationBase == alloc_base);
info->base_pc = mbi.BaseAddress;
info->size = mbi.RegionSize;
set_memtype_from_mbi(&mbi, info);
return true;
}
if (POINTER_OVERFLOW_ON_ADD(pb, mbi.RegionSize))
break;
pb += mbi.RegionSize;
* it, we could have problems: but, if region becomes free, we'll break,
* and so long as RegionSize > 0, we should make progress and hit
* end of address space in worst case -- so we shouldn't need this
* num_blocks max, but we'll keep it for now.
*/
num_blocks++;
DODEBUG({
if (num_blocks > 10) {
SYSLOG_INTERNAL_WARNING_ONCE("i#1462: >10 queries!");
}
});
} while (num_blocks < MAX_QUERY_VM_BLOCKS);
ASSERT_CURIOSITY(num_blocks < MAX_QUERY_VM_BLOCKS);
}
info->type = DR_MEMTYPE_ERROR;
return false;
}
* containing pc. This is a single memory area all from the same allocation
* region and all with the same protection and state attributes.
*/
bool
query_memory_ex(const byte *pc, OUT dr_mem_info_t *info)
{
return query_memory_internal(pc, info, true );
}
* This does not look backward to find the real base of this memory region but instead
* returns the cur page as the base. The size can still be used to locate the
* subsequent memory region.
*/
bool
query_memory_cur_base(const byte *pc, OUT dr_mem_info_t *info)
{
return query_memory_internal(pc, info, false );
}
* containing pc. This is a single memory area all from the same allocation
* region and all with the same protection and state attributes.
* If base_pc != NULL returns base pc of the area.
*/
bool
get_memory_info(const byte *pc, byte **base_pc, size_t *size, uint *prot)
{
if (base_pc != NULL || size != NULL) {
* want: we have to loop for that information (i#345)
*/
dr_mem_info_t info;
if (!query_memory_internal(pc, &info, base_pc != NULL || size != NULL) ||
info.type == DR_MEMTYPE_FREE)
return false;
if (base_pc != NULL)
*base_pc = info.base_pc;
if (size != NULL)
*size = info.size;
if (prot != NULL)
*prot = info.prot;
return true;
} else {
MEMORY_BASIC_INFORMATION mbi;
size_t res = query_virtual_memory(pc, &mbi, sizeof(mbi));
if (res != sizeof(mbi) || mbi.State == MEM_FREE)
return false;
if (prot != NULL)
*prot = osprot_to_memprot(mbi.Protect);
}
return true;
}
DR_API
size_t
dr_virtual_query(const byte *pc, MEMORY_BASIC_INFORMATION *mbi, size_t mbi_size)
{
size_t res = query_virtual_memory(pc, mbi, mbi_size);
if (is_pretend_or_executable_writable((app_pc)pc)) {
* as executable-but-writable and we'll come here.
*/
* users won't expect it due to the bulk of the flags not being
* bitmasks. Should we not pretend these regions are writable, then?
* User can always call dr_query_memory().
*/
mbi->Protect = osprot_add_write(mbi->Protect);
}
return res;
}
bool
get_stack_bounds(dcontext_t *dcontext, byte **base, byte **top)
{
os_thread_data_t *ostd = NULL;
if (dcontext != NULL) {
ostd = (os_thread_data_t *)dcontext->os_field;
if (ostd->teb_stack_no_longer_valid) {
* as the stack for ExitThread. Xref fixme in check_for_stack_free()
* about possibly handling this differently. */
return false;
}
if (IS_CLIENT_THREAD(dcontext) && dcontext->nudge_target == NULL) {
ostd->stack_base = dcontext->dstack - DYNAMORIO_STACK_SIZE;
ostd->stack_top = dcontext->dstack;
}
}
if (dcontext == NULL || ostd->stack_base == NULL) {
byte *stack_base = NULL;
byte *stack_top = NULL;
ASSERT(dcontext == NULL || dcontext == get_thread_private_dcontext());
* PVOID pvStackUserTop; // 04h Top of user stack
* PVOID pvStackUserBase; // 08h Base of user stack
* and assume fs is always a valid TIB pointer when called here
*/
stack_top = (byte *)d_r_get_tls(TOP_STACK_TIB_OFFSET);
stack_base = (byte *)d_r_get_tls(BASE_STACK_TIB_OFFSET);
LOG(THREAD, LOG_THREADS, 1, "app stack now is " PFX "-" PFX "\n", stack_base,
stack_top);
stack_base = get_allocation_base(stack_base);
LOG(THREAD, LOG_THREADS, 1, "app stack region is " PFX "-" PFX "\n", stack_base,
stack_top);
* stack and we would largely be fine with that. */
ASSERT(stack_base != NULL);
ASSERT(stack_base < stack_top);
ASSERT((get_allocation_base(stack_top - 1) == stack_base &&
(get_allocation_base(stack_top) != stack_base ||
* We would test dcontext->nudge_thread but that's not set yet. */
is_wow64_process(NT_CURRENT_PROCESS)))
|| is_dynamo_address(stack_base));
if (dcontext == NULL) {
if (base != NULL)
*base = stack_base;
if (top != NULL)
*top = stack_top;
return true;
}
ostd->stack_base = stack_base;
ostd->stack_top = stack_top;
}
if (base != NULL)
*base = ostd->stack_base;
if (top != NULL)
*top = ostd->stack_top;
return true;
}
winnt.h:#define PAGE_READONLY 2
winnt.h:#define PAGE_READWRITE 4
winnt.h:#define PAGE_WRITECOPY 8
winnt.h:#define PAGE_EXECUTE 16
winnt.h:#define PAGE_EXECUTE_READ 32
winnt.h:#define PAGE_EXECUTE_READWRITE 64
winnt.h:#define PAGE_GUARD 256
winnt.h:#define PAGE_NOACCESS 1
winnt.h:#define PAGE_NOCACHE 512
winnt.h:#define PAGE_EXECUTE_WRITECOPY 128
*/
* from pc to pc+size-1 are readable and that reading from there won't
* generate an exception. this is a stronger check than
* !not_readable() below.
* FIXME : beware of multi-thread races, just because this returns true,
* doesn't mean another thread can't make the region unreadable between the
* check here and the actual read later. See d_r_safe_read() as an alt.
*/
static bool
query_is_readable_without_exception(byte *pc, size_t size)
{
MEMORY_BASIC_INFORMATION mbi;
byte *check_pc = (byte *)ALIGN_BACKWARD(pc, PAGE_SIZE);
size_t res;
if (size > (size_t)((byte *)POINTER_MAX - pc))
size = (byte *)POINTER_MAX - pc;
do {
res = query_virtual_memory(check_pc, &mbi, sizeof(mbi));
if (res != sizeof(mbi)) {
return false;
} else {
if (mbi.State != MEM_COMMIT || TEST(PAGE_GUARD, mbi.Protect) ||
!prot_is_readable(mbi.Protect))
return false;
}
check_pc += PAGE_SIZE;
} while (check_pc != 0 && check_pc < pc + size);
return true;
}
bool
is_readable_without_exception_query_os(byte *pc, size_t size)
{
return is_readable_without_exception(pc, size);
}
bool
is_readable_without_exception_query_os_noblock(byte *pc, size_t size)
{
return is_readable_without_exception_query_os(pc, size);
}
* to call even if the memory at base is unreadable, returns true if the
* read succeeded */
* by a read. We get the os to do the read for us via ReadVirtualMemory,
* however this is still much slower then a structured exception handling
* solution since we expect this to succeed most of the time. Ref PR 206278
* and 208562 on using the faster TRY/EXCEPT. */
static bool
safe_read_syscall(const void *base, size_t size, void *out_buf, size_t *bytes_read)
{
if (bytes_read != NULL)
*bytes_read = 0;
return nt_read_virtual_memory(NT_CURRENT_PROCESS, base, out_buf, size, bytes_read);
}
bool
safe_read_ex(const void *base, size_t size, void *out_buf, size_t *bytes_read)
{
STATS_INC(num_safe_reads);
* call layer, but safe_read_fast() requires fault handling to be set up.
* There are complications with moving windows fault handling earlier in
* the init process, so we just fall back to the syscall during init.
*/
if (!dynamo_initialized) {
return safe_read_syscall(base, size, out_buf, bytes_read);
} else {
return safe_read_fast(base, size, out_buf, bytes_read);
}
}
bool
d_r_safe_read(const void *base, size_t size, void *out_buf)
{
size_t bytes_read = 0;
return (safe_read_ex(base, size, out_buf, &bytes_read) && bytes_read == size);
}
* to call even if the memory at base is unreadable, returns true if the
* write succeeded. See safe_read_ex() on using more performant TRY/EXCEPT. */
bool
safe_write_ex(void *base, size_t size, const void *in_buf, size_t *bytes_written)
{
if (bytes_written != NULL)
*bytes_written = 0;
STATS_INC(num_safe_writes);
* bytes written and instead returns -1! Thus if the caller cares we fall
* back to a try-except version. This also means that callers who want to
* fail on partial writes should pass in NULL for bytes_written!
*/
if (get_os_version() >= WINDOWS_VERSION_10 && bytes_written != NULL)
return safe_write_try_except(base, size, in_buf, bytes_written);
return nt_write_virtual_memory(NT_CURRENT_PROCESS, base, in_buf, size, bytes_written);
}
bool
safe_write(void *base, size_t size, const void *in_buf)
{
return safe_write_ex(base, size, in_buf, NULL);
}
* protection info. Note errors or bad addresses are ignored and
* return PAGE_NOACCESS instead. If the difference between invalid
* address or PAGE_NOACCESS is essential users must use
* query_virtual_memory()
*/
uint
get_current_protection(byte *pc)
{
PBYTE pb = (PBYTE)pc;
MEMORY_BASIC_INFORMATION mbi;
size_t res = query_virtual_memory(pb, &mbi, sizeof(mbi));
ASSERT(res == sizeof(mbi));
ASSERT(mbi.State != MEM_FREE);
if (res != sizeof(mbi) || mbi.State == MEM_FREE) {
ASSERT_CURIOSITY(false && "in get_memory_osprot");
return PAGE_NOACCESS;
}
return mbi.Protect;
}
* returns true if any byte with address from pc to pc+size-1 is not_readable
* FIXME: reverse the logic to make this is_readable
* Also CHECK that we actually need this routine
*/
bool
not_readable(byte *pc, size_t size)
{
MEMORY_BASIC_INFORMATION mbi;
byte *check_pc = (byte *)ALIGN_BACKWARD(pc, PAGE_SIZE);
size_t res;
if (size > (size_t)((byte *)POINTER_MAX - pc))
size = (byte *)POINTER_MAX - pc;
while (check_pc != 0 && check_pc < pc + size) {
res = query_virtual_memory(check_pc, &mbi, sizeof(mbi));
if (res != sizeof(mbi) || mbi.State == MEM_FREE)
return true;
else if (!prot_is_readable(mbi.Protect))
return true;
check_pc += PAGE_SIZE;
}
return false;
}
void
mark_page_as_guard(byte *pc)
{
uint old_prot;
int res;
* after the guard triggers
*/
uint flags = PAGE_READWRITE | PAGE_GUARD;
ASSERT(ALIGNED(pc, PAGE_SIZE));
res = protect_virtual_memory((void *)pc, PAGE_SIZE, flags, &old_prot);
ASSERT(res);
}
bool
unmark_page_as_guard(byte *pc, uint prot)
{
uint old_prot;
int res;
byte *start_page = (byte *)ALIGN_BACKWARD(pc, PAGE_SIZE);
uint flags = memprot_to_osprot(prot & ~MEMPROT_GUARD);
res = protect_virtual_memory(start_page, PAGE_SIZE, flags, &old_prot);
ASSERT(res);
* while we wanted to remove this protection. The returned value
* can be checked for such a case.
*/
return TEST(PAGE_GUARD, old_prot);
}
* If set is false, makes [un]writable depending on add_writable argument,
* preserving other flags; else, sets protection to new_prot.
* If cow is true and set is false and writable is true, sets to
* be not only writable but copy-on-write.
* Requires pc and size are multiples of the
* PAGE_SIZE.
*
* Returns true if all protection requests succeeded, false if
* protection on any subregion fails: all callers that make memory
* writable should be able to handle the unrecoverable yet failure on
* out of commit memory.
* changed_protection is set to true if changes were necessary, or
* false if protection already meets requirements. Note that any
* reserved yet not committed subregion will be skipped (and change
* protection is not needed).
*/
static bool
internal_change_protection(byte *start, size_t requested_size, bool set, bool writable,
bool cow, uint new_prot, bool *changed_protection )
{
byte *pc = start;
size_t remaining_size = requested_size;
bool changed_permissions = false;
bool subregions_failed = false;
* stats incs into one prior to the actual protection change.
* Though note that code movement was not sufficient for i#936.
* Fortunately here it's only debug-build stats and our debug build
* shouldn't hit that high-optimization: but if we make these RSTATS
* we should be careful.
*/
volatile bool writable_volatile = writable;
* it is supposed to be in error, a MEM_FREE block would terminate it */
DEBUG_DECLARE(app_pc first_allocation_base = NULL;)
ASSERT(requested_size > 0);
ASSERT(ALIGNED(start, PAGE_SIZE) && ALIGNED(requested_size, PAGE_SIZE));
* the same attributes, we have to split the requested region into
* multiple proper subregions
*/
do {
MEMORY_BASIC_INFORMATION mbi;
uint old_prot;
size_t res;
uint flags, new_flags;
size_t allow_size;
size_t subregion_size;
ASSERT(remaining_size > 0);
* know the desired flags can do without the
* query_virtual_memory() calls and only needs to process the
* results of protect_virtual_memory() to decide whether needs
* more iterations.
*/
res = query_virtual_memory((PBYTE)pc, &mbi, sizeof(mbi));
if (res != sizeof(mbi)) {
goto finish;
}
ASSERT(res == sizeof(mbi));
ASSERT(mbi.State != MEM_FREE);
ASSERT(mbi.State == MEM_COMMIT || mbi.State == MEM_RESERVE);
ASSERT(ALIGNED(pc, PAGE_SIZE) && ALIGNED(remaining_size, PAGE_SIZE));
ASSERT(first_allocation_base == NULL ||
first_allocation_base == mbi.AllocationBase);
DODEBUG({ first_allocation_base = mbi.AllocationBase; });
ASSERT(pc == mbi.BaseAddress);
allow_size = mbi.RegionSize - (pc - (byte *)mbi.BaseAddress);
* we have to do each os region separately */
if (remaining_size > allow_size) {
LOG(THREAD_GET, LOG_VMAREAS, 2,
"WARNING: make_%swritable " PFX ": param size " PIFX " vs. "
"mbi size " PIFX " base " PFX "\n",
writable ? "" : "un", pc, remaining_size, mbi.RegionSize,
mbi.BaseAddress);
* that we made read-only and we go to restore their permissions.
* we can see it for the same region many times in a row
* (e.g., on javac in SPECJVM98),
*/
LOG(GLOBAL, LOG_VMAREAS, pc == start ? 1U : 2U,
"make_%swritable called with size " PFX "> region size " PFX " at pc " PFX
"\n",
writable ? "" : "un", remaining_size, allow_size, pc);
* region or when MEM_RESERVE subregion is processed,
* for the time being adding a curiosity on any other use */
subregion_size = allow_size;
} else {
subregion_size = remaining_size;
}
ASSERT(subregion_size <= allow_size);
LOG(THREAD_GET, LOG_VMAREAS, 3,
"make_%swritable: pc " PFX "-" PFX ", currently %s %s\n",
writable ? "" : "un", pc, pc + subregion_size, prot_string(mbi.Protect),
mbi.State == MEM_COMMIT ? "committed" : "reserved");
* we use gratuitously in this LOG
*/
if (mbi.State == MEM_RESERVE) {
LOG(THREAD_GET, LOG_VMAREAS, 2,
"make_%swritable: WARNING skipping reserved region " PFX "-" PFX "\n", pc,
pc + subregion_size);
* Assume nobody will really reference this uncomitted
* memory, and in case it is caller error, that we'll find
* out on write.
*/
goto skip;
}
if (mbi.State == MEM_FREE) {
ASSERT_NOT_REACHED();
subregions_failed = true;
goto finish;
}
flags = mbi.Protect & ~PAGE_PROTECTION_QUALIFIERS;
if (set) {
new_flags = new_prot;
} else if (writable) {
switch (flags) {
case PAGE_NOACCESS: new_flags = PAGE_READWRITE; break;
case PAGE_READONLY: new_flags = PAGE_READWRITE; break;
case PAGE_READWRITE: goto skip;
case PAGE_WRITECOPY: goto skip;
case PAGE_EXECUTE: new_flags = PAGE_EXECUTE_READWRITE; break;
case PAGE_EXECUTE_READ: new_flags = PAGE_EXECUTE_READWRITE; break;
case PAGE_EXECUTE_READWRITE: goto skip;
case PAGE_EXECUTE_WRITECOPY: goto skip;
default:
ASSERT_NOT_REACHED();
goto skip;
}
if (cow)
new_flags = osprot_add_writecopy(new_flags);
} else {
switch (flags) {
case PAGE_NOACCESS: goto skip;
case PAGE_READONLY: goto skip;
case PAGE_READWRITE: new_flags = PAGE_READONLY; break;
case PAGE_WRITECOPY: new_flags = PAGE_READONLY; break;
case PAGE_EXECUTE: goto skip;
case PAGE_EXECUTE_READ: goto skip;
case PAGE_EXECUTE_READWRITE: new_flags = PAGE_EXECUTE_READ; break;
case PAGE_EXECUTE_WRITECOPY: new_flags = PAGE_EXECUTE_READ; break;
default: ASSERT_NOT_REACHED(); goto skip;
}
}
new_flags = (mbi.Protect & ~flags) | new_flags;
DOSTATS({
if (!writable_volatile) {
STATS_INC(protection_change_calls);
STATS_ADD(protection_change_pages, subregion_size / PAGE_SIZE);
}
});
res = protect_virtual_memory((void *)pc, subregion_size, new_flags, &old_prot);
if (!res) {
* of commit memory, if we are marking this up as failure
* here
*/
subregions_failed = true;
* vmm_heap_commit to wait a little for someone else to
* free up memory, or free any of our own.
*/
}
ASSERT_CURIOSITY(res && "protect_virtual_memory failed");
DOSTATS({
if (writable_volatile) {
STATS_INC(protection_change_calls);
STATS_ADD(protection_change_pages, subregion_size / PAGE_SIZE);
}
});
changed_permissions = true;
skip:
pc += subregion_size;
remaining_size -= subregion_size;
} while (remaining_size > 0);
finish:
if (changed_protection != NULL)
*changed_protection = changed_permissions;
return !subregions_failed;
}
* this does not update the all memory area info
*/
bool
os_set_protection(byte *pc, size_t size, uint prot )
{
return set_protection(pc, size, prot);
}
* (padded to page boundaries).
* returns false on failure, e.g. out of commit memory
*/
bool
set_protection(byte *pc, size_t size, uint prot)
{
byte *start_page = (byte *)ALIGN_BACKWARD(pc, PAGE_SIZE);
size_t num_bytes = ALIGN_FORWARD(size + (pc - start_page), PAGE_SIZE);
return internal_change_protection(start_page, num_bytes, true ,
false , false ,
memprot_to_osprot(prot), NULL);
}
* (padded to page boundaries). This method is meant to be used on DR memory
* as part of protect from app and is safe with respect to stats and the data
* segment.
*/
bool
change_protection(byte *pc, size_t size, bool writable)
{
byte *start_page = (byte *)ALIGN_BACKWARD(pc, PAGE_SIZE);
size_t num_bytes = ALIGN_FORWARD(size + (pc - start_page), PAGE_SIZE);
return internal_change_protection(start_page, num_bytes, false , writable,
false , 0, NULL);
}
bool
make_hookable(byte *pc, size_t size, bool *changed_prot)
{
byte *start_pc = (byte *)ALIGN_BACKWARD(pc, PAGE_SIZE);
size_t num_bytes = ALIGN_FORWARD(size + (pc - start_pc), PAGE_SIZE);
return internal_change_protection(start_pc, num_bytes, false , true,
false , 0, changed_prot);
}
* other flags */
void
make_unhookable(byte *pc, size_t size, bool changed_prot)
{
if (changed_prot) {
byte *start_pc = (byte *)ALIGN_BACKWARD(pc, PAGE_SIZE);
size_t num_bytes = ALIGN_FORWARD(size + (pc - start_pc), PAGE_SIZE);
internal_change_protection(start_pc, num_bytes, false , false,
false , 0, NULL);
}
}
* and marks that memory writable, preserves other flags */
bool
make_writable(byte *pc, size_t size)
{
return internal_change_protection(pc, size, false , true,
false , 0, NULL);
}
* and marks that memory writable and copy-on-write, preserving other flags.
* note: only usable if allocated COW
*/
bool
make_copy_on_writable(byte *pc, size_t size)
{
return internal_change_protection(pc, size, false , true, true , 0,
NULL);
}
* and marks that memory NOT writable, preserves other flags */
void
make_unwritable(byte *pc, size_t size)
{
internal_change_protection(pc, size, false , false, false , 0,
NULL);
}
#endif
bool
convert_NT_to_Dos_path(OUT wchar_t *buf, IN const wchar_t *fname,
IN size_t buf_len )
{
HANDLE objdir;
UNICODE_STRING ustr;
wchar_t drive[3] = { L'x', L':', L'\0' };
PROCESS_DEVICEMAP_INFORMATION map;
uint i, len;
NTSTATUS res;
bool ans = false;
const wchar_t *lanman = L"\\Device\\LanmanRedirector\\";
LOG(THREAD_GET, LOG_NT, 3, "%s: converting %S\n", __FUNCTION__, fname);
if (wcsstr(fname, lanman) == fname) {
_snwprintf(buf, buf_len, L"\\\\%s", fname + wcslen(lanman));
buf[buf_len - 1] = L'\0';
LOG(THREAD_GET, LOG_NT, 3, "%s: result %S\n", __FUNCTION__, buf);
return true;
}
* 1) NtQueryInformationProcess ProcessDeviceMap => list of valid drive
* letter symlinks (emulating kernel32!GetLogicalDriveStrings)
* 2) loop through each drive symlink, calling NtOpenSymbolicLinkObject
* to get the target (emulating kernel32!QueryDosDevice)
* 3) when find a match, replace \Device\HarddiskVolumeX with drive letter
*/
IF_X64(map.Flags = 0);
res = nt_get_drive_map(NT_CURRENT_PROCESS, &map);
if (!NT_SUCCESS(res)) {
LOG(THREAD_GET, LOG_NT, 2, "%s: drive map error 0x%x\n", __FUNCTION__, res);
return false;
}
* FIXME: via NtSetInformationProcess ProcessDeviceMap, can the device
* dir be different from "\??"? How do we know?
*/
res = nt_open_object_directory(&objdir, L"\\??", false);
if (!NT_SUCCESS(res)) {
LOG(THREAD_GET, LOG_NT, 2, "%s: \\?? error 0x%x\n", __FUNCTION__, res);
return false;
}
LOG(THREAD_GET, LOG_NT, 2, "%s: DriveMap=%d\n", __FUNCTION__, map.Query.DriveMap);
ustr.Length = 0;
ustr.MaximumLength = (USHORT)buf_len * sizeof(wchar_t);
ustr.Buffer = buf;
for (i = 0; i < sizeof(map.Query.DriveType) / sizeof(UCHAR); i++) {
if (map.Query.DriveType[i] != DRIVE_UNKNOWN) {
drive[0] = L'A' + (wchar_t)i;
res = nt_get_symlink_target(objdir, drive, &ustr, &len);
if (NT_SUCCESS(res)) {
ustr.Buffer[MIN(ustr.Length / sizeof(ustr.Buffer[0]),
ustr.MaximumLength / sizeof(ustr.Buffer[0]) - 1)] = L'\0';
LOG(THREAD_GET, LOG_NT, 3, "%s: drive %d=%c: type=%d => %S\n",
__FUNCTION__, i, 'A' + (wchar_t)i, map.Query.DriveType[i],
ustr.Buffer);
} else {
LOG(THREAD_GET, LOG_NT, 3, "%s: failed to query symlink: 0x%x\n",
__FUNCTION__, res);
}
if (wcsstr(fname, ustr.Buffer) == fname) {
_snwprintf(buf, buf_len, L"%s%s", drive, fname + wcslen(ustr.Buffer));
buf[buf_len - 1] = L'\0';
LOG(THREAD_GET, LOG_NT, 3, "%s: result %S\n", __FUNCTION__, buf);
ans = true;
break;
}
}
}
close_handle(objdir);
return ans;
}
#ifndef NOT_DYNAMORIO_CORE_PROPER
* If the conversion won't fit in fixedbuf, allocates memory and
* returns that memory, along with its size in allocbuf_sz.
* In that case, the memory should be freed by calling
* convert_to_NT_file_path_wide_free();
* Always null-terminates when it returns non-NULL.
*/
wchar_t *
convert_to_NT_file_path_wide(OUT wchar_t *fixedbuf, IN const wchar_t *fname,
IN size_t fixedbuf_len ,
OUT size_t *allocbuf_sz )
{
* but a lot of the extra stuff there is curiosities for use within DR,
* while this routine is mainly used by drwinapi.
* If you change the logic here, change convert_to_NT_file_path().
*/
bool is_UNC = false;
bool is_device = false;
size_t relative_sz = 0;
const wchar_t *name = fname;
wchar_t *buf;
int i, size;
size_t wchars_needed, buf_len;
ASSERT(fixedbuf != NULL && fixedbuf_len != 0);
if (name[0] == L'\\') {
name += 1;
if (name[0] == L'\\') {
if (name[1] == L'.' && name[2] == L'\\') {
is_UNC = false;
is_device = true;
name += 3;
} else if (name[1] == L'?' && name[2] == L'\\') {
name += 3;
} else {
is_UNC = true;
}
} else {
if (name[0] != L'\0' && name[1] != L'\0' && name[2] != L'\0') {
name += 3;
} else {
return NULL;
}
}
if (!is_UNC && !is_device) {
if ((name[0] == L'U' || name[0] == L'u') &&
(name[1] == L'N' || name[1] == L'n') &&
(name[2] == L'C' || name[2] == L'c')) {
is_UNC = true;
name += 3;
}
}
} else if (name[1] == L':' && (name[2] == L'/' || name[2] == L'\\')) {
} else if (name[0] != '/' && name[0] != '\\') {
# ifndef NOT_DYNAMORIO_CORE_PROPER
* We don't support absolute for the current drive ("\foo.txt").
* We also don't support relative for other drives ("c:foo.txt").
*/
char *cwd_end = cwd + strlen(cwd) - 1;
relative_sz = strlen(cwd);
if (name[0] == L'.' && (name[1] == L'/' || name[1] == L'\\')) {
name += 2;
} else {
while (name[0] == L'.' && name[1] == L'.' &&
(name[2] == L'/' || name[2] == L'\\')) {
name += 3;
* has no trailing slash and is all backslashes (no forward slashes).
*/
while (relative_sz > 0 && *(cwd_end + 1) != '\\') {
cwd_end--;
relative_sz--;
}
if (relative_sz == 0)
return false;
}
}
# endif
}
wchars_needed =
(wcslen(name) + wcslen(L"\\??\\") + (is_UNC ? wcslen(L"UNC") : 0) + 1 );
if (fixedbuf_len >= wchars_needed) {
buf = fixedbuf;
buf_len = fixedbuf_len;
} else {
* larger-than-MAX_PATH paths (technically drwinapi only has to do
* that for "\\?\" paths).
*/
buf = (wchar_t *)global_heap_alloc(wchars_needed *
sizeof(wchar_t) HEAPACCT(ACCT_OTHER));
buf_len = wchars_needed;
*allocbuf_sz = wchars_needed * sizeof(wchar_t);
}
size = snwprintf(buf, buf_len, L"\\??\\%s%.*hs%s%s", is_UNC ? L"UNC" : L"",
# ifdef NOT_DYNAMORIO_CORE_PROPER
0, "", L"",
# else
relative_sz, cwd, (relative_sz > 0) ? L"\\" : L"",
# endif
name);
buf[buf_len - 1] = L'\0';
if (size < 0 || size == (int)buf_len) {
if (buf != fixedbuf)
global_heap_free(buf, *allocbuf_sz HEAPACCT(ACCT_OTHER));
return NULL;
}
for (i = 0; i < size; i++) {
if (buf[i] == L'/')
buf[i] = L'\\';
}
return buf;
}
void
convert_to_NT_file_path_wide_free(wchar_t *buf, size_t alloc_sz)
{
global_heap_free(buf, alloc_sz HEAPACCT(ACCT_OTHER));
}
#endif
*/
bool
convert_to_NT_file_path(OUT wchar_t *buf, IN const char *fname,
IN size_t buf_len )
{
bool is_UNC = false;
bool is_device = false;
size_t relative_sz = 0;
const char *name = fname;
int i, size;
ASSERT(buf != NULL && buf_len != 0);
* gives us a fullly qualified absolute path, no . .. relative etc.
* For UNC names(//server/name), the path should be \??\UNC\server\name. */
* so we should handle all possible file name prefixes, we've seen -
* c:\ \??\c:\ \\?\c:\ \\server \??\UNC\server \\?\UNC\server */
* reactos src. See DEVICE_PATH \\.\foo, UNC_DOT_PATH \\., etc.
* For i#499 we now convert \\.\foo to \??\foo.
*/
* if we would need to use that at this level instead of \??\ for long paths (not
* that it matters since our buffer in this routine limits us to MAX_PATH anyways).
/* FIXME - handle . and .. */
* translation for us, used by CreateDirectory CreateFile etc. but looking
* at the dissasembly it grabs the loader lock! why does it need
* to do that? is it to translate . or ..?, better just to do the
* translation here where we know what's going on */
* DynamoRIO_get_full_path.
*/
if (name[0] == '\\') {
name += 1;
if (name[0] == '\\') {
if (name[1] == '.' && name[2] == '\\') {
is_UNC = false;
is_device = true;
name += 3;
} else if (name[1] == '?') {
* chop off the \\?\ and we'll check for the UNC later */
ASSERT_CURIOSITY(CLIENT_OR_STANDALONE() ||
(name[2] == '\\' && "create file invalid name"));
if (name[2] != '\0') {
name += 3;
} else {
return false;
}
} else {
is_UNC = true;
}
} else {
* chop off the \??\ and we'll check for the UNC later */
ASSERT_CURIOSITY(CLIENT_OR_STANDALONE() ||
(name[0] == '?' && name[1] == '?' && name[2] == '\\' &&
"create file invalid name"));
if (name[0] != '\0' && name[1] != '\0' && name[2] != '\0') {
name += 3;
} else {
return false;
}
}
if (!is_UNC && !is_device) {
if ((name[0] == 'U' || name[0] == 'u') &&
(name[1] == 'N' || name[1] == 'n') &&
(name[2] == 'C' || name[2] == 'c')) {
* (we'll re-add below)
* NOTE '/' is not a legal separator for a \??\ or \\?\ path */
ASSERT_CURIOSITY(CLIENT_OR_STANDALONE() ||
(name[3] == '\\' && "create file invalid name"));
is_UNC = true;
name += 3;
} else {
* NOTE '/' is not a legal separator for a \??\ or \\?\ path */
ASSERT_CURIOSITY(
CLIENT_OR_STANDALONE() ||
(name[1] == ':' && name[2] == '\\' && "create file invalid name"));
}
}
} else if (name[1] == ':' && (name[2] == '/' || name[2] == '\\')) {
} else if (name[0] != '/' && name[0] != '\\') {
#ifndef NOT_DYNAMORIO_CORE_PROPER
* We don't support absolute for the current drive ("\foo.txt").
* We also don't support relative for other drives ("c:foo.txt").
*/
char *cwd_end = cwd + strlen(cwd) - 1;
relative_sz = strlen(cwd);
if (name[0] == '.' && (name[1] == '/' || name[1] == '\\')) {
name += 2;
} else {
while (name[0] == '.' && name[1] == '.' &&
(name[2] == '/' || name[2] == '\\')) {
name += 3;
* has no trailing slash and is all backslashes (no forward slashes).
*/
while (relative_sz > 0 && *(cwd_end + 1) != '\\') {
cwd_end--;
relative_sz--;
}
if (relative_sz == 0)
return false;
}
}
#endif
}
size = snwprintf(buf, buf_len, L"\\??\\%ls%.*hs%ls%hs", is_UNC ? L"UNC" : L"",
#ifdef NOT_DYNAMORIO_CORE_PROPER
0, "", L"",
#else
relative_sz, cwd, (relative_sz > 0) ? L"\\" : L"",
#endif
name);
buf[buf_len - 1] = L'\0';
if (size < 0 || size == (int)buf_len)
return false;
for (i = 0; i < size; i++) {
if (buf[i] == L'/')
buf[i] = L'\\';
if (i > 1 && buf[i] == L'\\' && buf[i - 1] == L'\\') {
int j;
for (j = i; j < size; j++)
buf[j] = buf[j + 1];
ASSERT(buf[j] == L'\0');
}
}
return true;
}
static file_t
os_internal_create_file(const char *fname, bool is_dir, ACCESS_MASK rights, uint sharing,
uint create_disposition)
{
wchar_t buf[MAX_PATH];
if (!convert_to_NT_file_path(buf, fname, BUFFER_SIZE_ELEMENTS(buf)))
return INVALID_FILE;
NULL_TERMINATE_BUFFER(buf);
return create_file(buf, is_dir, rights, sharing, create_disposition, true);
}
static bool
os_internal_create_file_test(const char *fname, bool is_dir, ACCESS_MASK rights,
uint sharing, uint create_disposition)
{
HANDLE file =
os_internal_create_file(fname, is_dir, rights, sharing, create_disposition);
if (INVALID_FILE == file) {
return false;
}
os_close(file);
return true;
}
bool
os_file_exists(const char *fname, bool is_dir)
{
return os_internal_create_file_test(fname, is_dir, 0,
* include write (drmem i#1025)
*/
FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN);
}
* Note: This size is different from the allocation size of the file, which can
* be larger or smaller (if file compression is turned on - case 8272).
*/
bool
os_get_file_size(const char *file, uint64 *size)
{
wchar_t filename[MAXIMUM_PATH + 1];
FILE_NETWORK_OPEN_INFORMATION file_info;
ASSERT(file != NULL && size != NULL);
if (file == NULL || size == NULL)
return false;
* directly.
*/
_snwprintf(filename, BUFFER_SIZE_ELEMENTS(filename), L"\\??\\%hs", file);
NULL_TERMINATE_BUFFER(filename);
if (query_full_attributes_file(filename, &file_info)) {
ASSERT(sizeof(*size) == sizeof(file_info.EndOfFile.QuadPart));
*size = file_info.EndOfFile.QuadPart;
return true;
}
return false;
}
bool
os_get_file_size_by_handle(IN HANDLE file_handle, uint64 *end_of_file )
{
FILE_STANDARD_INFORMATION standard_info;
NTSTATUS res = nt_query_file_info(file_handle, &standard_info, sizeof(standard_info),
FileStandardInformation);
ASSERT(NT_SUCCESS(res) && "bad file handle?");
if (!NT_SUCCESS(res)) {
return false;
}
*end_of_file = standard_info.EndOfFile.QuadPart;
return true;
}
bool
os_set_file_size(IN HANDLE file_handle, uint64 end_of_file)
{
NTSTATUS res;
FILE_END_OF_FILE_INFORMATION file_end_info;
ASSERT_CURIOSITY(end_of_file != 0);
file_end_info.EndOfFile.QuadPart = end_of_file;
res = nt_set_file_info(file_handle, &file_end_info, sizeof(file_end_info),
FileEndOfFileInformation);
ASSERT(NT_SUCCESS(res) && "can't set size: bad handle?");
return NT_SUCCESS(res);
}
* impersonated), as well as total available on the volume
* Note that any valid handle on the volume can be used.
*/
bool
os_get_disk_free_space(IN HANDLE file_handle, OUT uint64 *AvailableQuotaBytes OPTIONAL,
OUT uint64 *TotalQuotaBytes OPTIONAL,
OUT uint64 *TotalVolumeBytes OPTIONAL)
{
* bytes available on the volume, we may use just
* FILE_FS_SIZE_INFORMATION instead of FILE_FS_FULL_SIZE_INFORMATION
* case 9000: need to check if both are available on NT
*/
* FILE_FS_FULL_SIZE_INFORMATION
*
* "The size of the buffer passed ... must be at least sizeof
* (FILE_FS_FULL_SIZE_INFORMATION). This structure must be
* aligned on a LONGLONG (8-byte) boundary. "
*
* Although on XP SP2 this call succeeds even on a non-aligned
* value, to be sure we'll follow the recommendation.
*/
FILE_FS_FULL_SIZE_INFORMATION unaligned_fs_full_size[2];
FILE_FS_FULL_SIZE_INFORMATION *FileFsFullSize =
(FILE_FS_FULL_SIZE_INFORMATION *)ALIGN_FORWARD(unaligned_fs_full_size,
sizeof(LONGLONG));
uint64 BytesPerUnit;
NTSTATUS res;
ASSERT(sizeof(LONGLONG) < sizeof(FILE_FS_FULL_SIZE_INFORMATION));
ASSERT(ALIGNED(FileFsFullSize, sizeof(LONGLONG)));
res = nt_query_volume_info(file_handle, FileFsFullSize, sizeof(*FileFsFullSize),
FileFsFullSizeInformation);
if (!NT_SUCCESS(res))
return false;
BytesPerUnit =
FileFsFullSize->SectorsPerAllocationUnit * FileFsFullSize->BytesPerSector;
if (AvailableQuotaBytes != NULL) {
*AvailableQuotaBytes =
FileFsFullSize->CallerAvailableAllocationUnits.QuadPart * BytesPerUnit;
}
if (TotalQuotaBytes != NULL) {
*TotalQuotaBytes = FileFsFullSize->TotalAllocationUnits.QuadPart * BytesPerUnit;
}
if (TotalVolumeBytes != NULL) {
*TotalVolumeBytes =
FileFsFullSize->ActualAvailableAllocationUnits.QuadPart * BytesPerUnit;
}
return true;
}
* that if new_file is non-empty we are overwriting only the
* appropriate subregion. os_copy_file() can be used as a full file
* copy (with offset 0 in both files). With an offset os_copy_file()
* can be used to overwrite the portions of a file that are not mapped
* in memory or are suffixes not at all covered by the PE format.
*/
* to preserve OLE structured storage?
*
* NOTE: we do don't support NTFS alternate data streams,
* e.g. downloaded.dll:Zone.Identifier since we would expect that any
* checks by say Software Restriction Policies are done on the
* original file, not on what we really open.
*
* NOTE we don't preserve extended attributes, file attributes. If we
* care to have these should see see
* kernel32!CreateFile(,hTemplateFile) which supplies file attributes
* and extended attributes for the new file.
*
* Note we don't preserve security attributes - see
* shell32!SHFileOperation if we need this.
*
* We don't deal in any way with encrypted files - they are opened
* raw. FIXME: may want to at least make sure that encrypted files
* aren't shared.
*
* FIXME: testing: doublecheck compressed file offsets are properly
* used - test both encrypted and compressed folders.
*/
bool
os_copy_file(HANDLE new_file, HANDLE original_file, uint64 new_file_offset,
uint64 original_file_offset)
{
* current uses are rare enough. See p.64 and 02 FileCopy from
* Richter&Clark if a fast one is needed. */
* right away with os_set_file_size(), but that should be done
* only in case the current size is smaller (e.g. we shouldn't
* truncate if trying to overwrite a subsection ) */
ASSERT_NOT_IMPLEMENTED(false);
return false;
}
bool
os_create_dir(const char *fname, create_directory_flags_t create_dir_flags)
{
bool require_new = TEST(CREATE_DIR_REQUIRE_NEW, create_dir_flags);
bool force_owner = TEST(CREATE_DIR_FORCE_OWNER, create_dir_flags);
* always require_new,
* FIXME: not all current users do this properly
*/
return os_internal_create_file_test(
fname, true, 0, FILE_SHARE_READ,
(require_new ? FILE_CREATE : FILE_OPEN_IF) |
(force_owner ? FILE_DISPOSITION_SET_OWNER : 0));
}
file_t
os_open_directory(const char *fname, int os_open_flags)
{
uint sharing = FILE_SHARE_READ
* directory */
| FILE_SHARE_WRITE;
uint access = READ_CONTROL;
if (TEST(OS_OPEN_READ, os_open_flags))
access |= FILE_GENERIC_READ;
return os_internal_create_file(fname, true, access, sharing, FILE_OPEN);
}
* both seem to work as expected (and CreateFile uses the GENERIC_* while
* the ddk uses FILE_GENERIC_*) but they resolve differently, some confusion.
* ntddk.h has GENERIC_* as a single bit flag while FILE_GENERIC_* is
* a combination including FILE_{READ,WRITE}_DATA, so going with the latter.
*/
file_t
os_open(const char *fname, int os_open_flags)
{
uint access = 0;
uint sharing = FILE_SHARE_READ;
if (TEST(OS_EXECUTE, os_open_flags))
access |= FILE_GENERIC_EXECUTE;
if (TEST(OS_OPEN_READ, os_open_flags))
access |= FILE_GENERIC_READ;
if (TEST(OS_SHARE_DELETE, os_open_flags))
sharing |= FILE_SHARE_DELETE;
if (!TEST(OS_OPEN_WRITE, os_open_flags))
return os_internal_create_file(fname, false, access, sharing, FILE_OPEN);
ASSERT_CURIOSITY_ONCE((TEST(OS_OPEN_REQUIRE_NEW, os_open_flags) ||
standalone_library || CLIENTS_EXIST()) &&
"symlink risk PR 213492");
return os_internal_create_file(
fname, false,
access |
(TEST(OS_OPEN_APPEND, os_open_flags) ?
* FILE_WRITE_DATA, so we get auto-append
*/
(STANDARD_RIGHTS_WRITE | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES |
FILE_WRITE_EA)
: FILE_GENERIC_WRITE),
sharing,
(TEST(OS_OPEN_REQUIRE_NEW, os_open_flags)
? FILE_CREATE
: (TEST(OS_OPEN_APPEND, os_open_flags) ? FILE_OPEN_IF : FILE_OVERWRITE_IF)) |
(TEST(OS_OPEN_FORCE_OWNER, os_open_flags) ? FILE_DISPOSITION_SET_OWNER : 0));
}
void
os_close(file_t f)
{
close_handle(f);
}
file_t
os_open_protected(const char *fname, int os_open_flags)
{
return os_open(fname, os_open_flags);
}
void
os_close_protected(file_t f)
{
os_close(f);
}
bool
os_get_current_dir(char *buf, size_t bufsz)
{
int len = snprintf(buf, bufsz, "%S",
get_own_peb()->ProcessParameters->CurrentDirectoryPath.Buffer);
buf[bufsz - 1] = '\0';
if (len < 0 || (size_t)len == bufsz)
return false;
if (buf[len - 1] == '\\')
buf[len - 1] = '\0';
return true;
}
#ifndef NOT_DYNAMORIO_CORE_PROPER
* takes in a ULONG (==uint), though they return a ULONG_PTR (size_t)
*/
ssize_t
os_write(file_t f, const void *buf, size_t count)
{
size_t written = 0;
ssize_t out = -1;
bool ok;
if (f == INVALID_FILE)
return out;
IF_X64(ASSERT(CHECK_TRUNCATE_TYPE_uint(count)));
ok = write_file(f, buf, (uint)count, NULL, &written);
if (ok) {
ASSERT(written <= INT_MAX && written <= count);
out = (ssize_t)written;
} else {
ASSERT(written == 0);
}
return out;
}
#endif
* takes in a ULONG (==uint), though they return a ULONG_PTR (size_t)
*/
ssize_t
os_read(file_t f, void *buf, size_t count)
{
size_t read = 0;
ssize_t out = -1;
bool ok;
if (f == INVALID_FILE)
return out;
IF_X64(ASSERT(CHECK_TRUNCATE_TYPE_uint(count)));
ok = read_file(f, buf, (uint)count, NULL, &read);
if (ok) {
ASSERT(read <= INT_MAX && read <= count);
out = (ssize_t)read;
} else {
ASSERT(read == 0);
}
return out;
}
void
os_flush(file_t f)
{
nt_flush_file_buffers(f);
}
bool
os_seek(file_t f, int64 offset, int origin)
{
FILE_POSITION_INFORMATION info;
NTSTATUS res;
int64 abs_offset = offset;
switch (origin) {
case OS_SEEK_SET: break;
case OS_SEEK_CUR: {
int64 cur_pos = os_tell(f);
ASSERT(cur_pos != -1 && "bad file handle?");
abs_offset += cur_pos;
} break;
case OS_SEEK_END: {
uint64 file_size = 0;
bool size_res = os_get_file_size_by_handle(f, &file_size);
ASSERT(size_res && "bad file handle?");
abs_offset += file_size;
} break;
default: ASSERT(false && "os_seek: invalid origin"); return false;
}
info.CurrentByteOffset.QuadPart = abs_offset;
res = nt_set_file_info(f, &info, sizeof(info), FilePositionInformation);
return NT_SUCCESS(res);
}
int64
os_tell(file_t f)
{
FILE_POSITION_INFORMATION info;
NTSTATUS res = nt_query_file_info(f, &info, sizeof(info), FilePositionInformation);
ASSERT(NT_SUCCESS(res) && "bad file handle?");
if (!NT_SUCCESS(res)) {
return -1;
}
return info.CurrentByteOffset.QuadPart;
}
#ifndef NOT_DYNAMORIO_CORE_PROPER
* We use FILE_DELETE_ON_CLOSE, which works only on SEC_COMMIT, not on SEC_IMAGE.
* There is no known way to immediately delete a mapped-in SEC_IMAGE file.
* Xref case 9964.
*/
bool
os_delete_mapped_file(const char *filename)
{
NTSTATUS res;
HANDLE hf;
FILE_DISPOSITION_INFORMATION file_dispose_info;
bool deleted = false;
wchar_t wname[MAX_FILE_NAME_LENGTH];
if (!convert_to_NT_file_path(wname, filename, BUFFER_SIZE_ELEMENTS(wname)))
return false;
NULL_TERMINATE_BUFFER(wname);
res = nt_create_file(&hf, wname, NULL, 0, SYNCHRONIZE | DELETE, FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_DELETE |
FILE_SHARE_READ,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT |
FILE_DELETE_ON_CLOSE
* than its target, and avoid other reparse code.
* Otherwise the FILE_DELETE_ON_CLOSE would cause
* us to delete the target of a symlink!
* FIXME: fully test this: case 10067
*/
| FILE_OPEN_REPARSE_POINT);
if (!NT_SUCCESS(res)) {
LOG(GLOBAL, LOG_NT, 2,
"os_delete_mapped_file: unable to open handle to %s: " PFX "\n", filename,
res);
return false;
}
* with STATUS_CANNOT_DELETE 0xc0000121.
*/
file_dispose_info.DeleteFile = TRUE;
res = nt_set_file_info(hf, &file_dispose_info, sizeof(file_dispose_info),
FileDispositionInformation);
if (NT_SUCCESS(res))
deleted = true;
else {
LOG(GLOBAL, LOG_NT, 2,
"os_delete_mapped_file: unable to mark for deletion %s: " PFX "\n", filename,
res);
}
close_handle(hf);
if (!deleted) {
* open and assume nobody created a new file of the same name.
*/
res = nt_create_file(&hf, wname, NULL, 0, SYNCHRONIZE, FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_DELETE | FILE_SHARE_READ, FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_REPARSE_POINT);
LOG(GLOBAL, LOG_NT, 2, "os_delete_mapped_file: opening after close %s: " PFX "\n",
filename, res);
if (NT_SUCCESS(res))
close_handle(hf);
else if (res == STATUS_DELETE_PENDING || res == STATUS_OBJECT_NAME_NOT_FOUND) {
deleted = true;
} else
ASSERT_CURIOSITY(false && "unable to confirm close-on-delete");
}
return deleted;
}
bool
os_delete_file_w(const wchar_t *file_name, HANDLE directory_handle)
{
NTSTATUS res;
HANDLE hf;
FILE_DISPOSITION_INFORMATION file_dispose_info;
* in a base dir: need to examine all callers.
*/
res = nt_create_module_file(&hf, file_name, directory_handle, DELETE,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_DELETE |
FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_OPEN, 0);
* symbolic link (in Longhorn), while we want to act on the link
* itself
*/
ASSERT_CURIOSITY(NT_SUCCESS(res) && "can't open for deletion");
if (!NT_SUCCESS(res))
return false;
file_dispose_info.DeleteFile = TRUE;
res = nt_set_file_info(hf, &file_dispose_info, sizeof(file_dispose_info),
FileDispositionInformation);
close_handle(hf);
ASSERT_CURIOSITY(NT_SUCCESS(res) && "couldn't mark for deletion");
return NT_SUCCESS(res);
}
bool
os_delete_file(const char *name)
{
wchar_t wname[MAX_FILE_NAME_LENGTH];
if (!convert_to_NT_file_path(wname, name, BUFFER_SIZE_ELEMENTS(wname)))
return false;
NULL_TERMINATE_BUFFER(wname);
return os_delete_file_w(wname, NULL);
}
bool
os_delete_dir(const char *name)
{
wchar_t wname[MAX_FILE_NAME_LENGTH];
NTSTATUS res;
if (!convert_to_NT_file_path(wname, name, BUFFER_SIZE_ELEMENTS(wname)))
return false;
NULL_TERMINATE_BUFFER(wname);
res = nt_delete_file(wname);
return NT_SUCCESS(res);
}
* away the privileges required to rename a file when opening the handle.
* We also do not take in a rootdir handle to be parallel to the linux
* system call, so caller must specify full path.
* This will not rename a file across volumes.
*
* see os_rename_file_in_directory() for a Win32-specific interface
*/
bool
os_rename_file(const char *orig_name, const char *new_name, bool replace)
{
file_t fd = INVALID_FILE;
NTSTATUS res;
FILE_RENAME_INFORMATION info;
wchar_t worig[MAX_FILE_NAME_LENGTH];
if (!convert_to_NT_file_path(info.FileName, new_name,
BUFFER_SIZE_ELEMENTS(info.FileName)))
return false;
NULL_TERMINATE_BUFFER(info.FileName);
* but then we couldn't rename directories; ditto for create_file,
* so we directly call nt_create_file.
*/
if (!convert_to_NT_file_path(worig, orig_name, BUFFER_SIZE_ELEMENTS(worig)))
return false;
NULL_TERMINATE_BUFFER(worig);
res = nt_create_file(&fd, worig, NULL, 0, DELETE | SYNCHRONIZE, FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_DELETE,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT);
if (!NT_SUCCESS(res) || fd == INVALID_FILE) {
LOG(GLOBAL, LOG_NT, 2, "os_rename_file: unable to open handle to %s: " PFX "\n",
orig_name, res);
return false;
}
* 1) set FileRenameInformation: works on FAT, NTFS, all platforms
* 2) set FileNameInformation: not allowed; only for get
* 3) set FileShortNameInformation: I couldn't get this to work, but
* was probably missing some privilege; but, only available on NTFS XP+
*/
info.ReplaceIfExists = (BOOLEAN)replace;
info.RootDirectory = NULL;
IF_X64(ASSERT_TRUNCATE(info.FileNameLength, uint,
wcslen(info.FileName) * sizeof(wchar_t)));
info.FileNameLength = (uint)(wcslen(info.FileName) * sizeof(wchar_t));
res = nt_set_file_info(fd, &info, sizeof(info), FileRenameInformation);
if (!NT_SUCCESS(res)) {
LOG(GLOBAL, LOG_NT, 2, "os_rename_file: NtSetFileInformation error " PFX "\n",
res);
}
close_handle(fd);
return NT_SUCCESS(res);
}
* We take in orig_name instead of a file handle, so that we can abstract
* away the privileges required to rename a file when opening the handle.
* Note however, that any other handle must be closed before calling.
* Both names are relative to rootdir handle, since renaming files in
* same directory is our primary use.
*/
bool
os_rename_file_in_directory(IN HANDLE rootdir, const wchar_t *orig_name,
const wchar_t *new_name, bool replace)
{
file_t fd = INVALID_FILE;
NTSTATUS res;
FILE_RENAME_INFORMATION info;
res = nt_create_file(
&fd, orig_name, rootdir, 0, DELETE | SYNCHRONIZE, FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_DELETE, FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT);
if (!NT_SUCCESS(res) || fd == INVALID_FILE) {
LOG(GLOBAL, LOG_NT, 2, "os_rename_file: unable to open handle to %s: " PFX "\n",
orig_name, res);
return false;
}
info.ReplaceIfExists = (BOOLEAN)replace;
info.RootDirectory = rootdir;
wcsncpy(info.FileName, new_name, BUFFER_SIZE_ELEMENTS(info.FileName));
NULL_TERMINATE_BUFFER(info.FileName);
IF_X64(ASSERT_TRUNCATE(info.FileNameLength, uint,
wcslen(info.FileName) * sizeof(wchar_t)));
info.FileNameLength = (uint)(wcslen(info.FileName) * sizeof(wchar_t));
res = nt_set_file_info(fd, &info, sizeof(info), FileRenameInformation);
if (!NT_SUCCESS(res)) {
LOG(GLOBAL, LOG_NT, 2,
"os_rename_file_in_directory: NtSetFileInformation error " PFX "\n", res);
}
close_handle(fd);
return NT_SUCCESS(res);
}
byte *
os_map_file(file_t f, size_t *size INOUT, uint64 offs, app_pc addr, uint prot,
map_flags_t map_flags)
{
NTSTATUS res;
HANDLE section;
byte *map = addr;
uint osprot = memprot_to_osprot(prot);
# ifdef X64
bool loop = false;
byte *region_start = NULL, *region_end = NULL;
uint iters = 0;
# define MAX_MAP_LOOP_ITERS 100
# endif
LARGE_INTEGER li_offs;
li_offs.QuadPart = offs;
if (TEST(MAP_FILE_COPY_ON_WRITE, map_flags) && TEST(MEMPROT_WRITE, prot)) {
* need it for the view (except on win98, according to Richter p604)
*/
osprot = osprot_add_writecopy(osprot);
}
res = nt_create_section(§ion,
SECTION_ALL_ACCESS,
NULL,
osprot,
* it did in my test */
TEST(MAP_FILE_IMAGE, map_flags) ? SEC_IMAGE : SEC_COMMIT, f,
NULL , 0, NULL, NULL);
if (!NT_SUCCESS(res)) {
LOG(GLOBAL, LOG_NT, 2, "os_map_file: NtCreateSection error " PFX "\n", res);
return NULL;
}
# ifdef X64
if (TEST(MAP_FILE_REACHABLE, map_flags)) {
loop = true;
vmcode_get_reachable_region(®ion_start, ®ion_end);
}
while (!loop || (map != NULL && map >= region_start && map + *size <= region_end) ||
find_free_memory_in_region(region_start, region_end, *size, &map, NULL)) {
# endif
res = nt_raw_MapViewOfSection(section,
NT_CURRENT_PROCESS,
&map,
0,
0 ,
&li_offs,
(PSIZE_T)size,
ViewUnmap ,
0 ,
osprot);
# ifdef X64
if (!loop || NT_SUCCESS(res))
break;
if (++iters > MAX_MAP_LOOP_ITERS) {
ASSERT_NOT_REACHED();
break;
}
map = NULL;
}
if (NT_SUCCESS(res) && TEST(MAP_FILE_REACHABLE, map_flags))
ASSERT(map >= region_start && map + *size <= region_end);
# endif
close_handle(section);
if (!NT_SUCCESS(res)) {
LOG(GLOBAL, LOG_NT, 2, "os_map_file: NtMapViewOfSection error " PFX "\n", res);
return NULL;
}
return map;
}
bool
os_unmap_file(byte *map, size_t size )
{
int res = nt_raw_UnmapViewOfSection(NT_CURRENT_PROCESS, map);
return NT_SUCCESS(res);
}
file_t
os_create_memory_file(const char *name, size_t size)
{
ASSERT_NOT_IMPLEMENTED(false && "i#3556 NYI for Windows");
return INVALID_FILE;
}
void
os_delete_memory_file(const char *name, file_t fd)
{
ASSERT_NOT_IMPLEMENTED(false && "i#3556 NYI for Windows");
}
* Like any instance where a thread_record_t is used by a thread other than its
* owner, the caller must hold the thread_initexit_lock to ensure that it
* remains valid.
* Requires thread trec is at_safe_spot().
* We assume that the segments CS and SS have been set in the cxt properly.
*/
bool
translate_context(thread_record_t *trec, CONTEXT *cxt, bool restore_memory)
{
priv_mcontext_t mc;
bool res;
ASSERT(TESTALL(CONTEXT_CONTROL , cxt->ContextFlags));
ASSERT(TESTALL(CONTEXT_DR_STATE, cxt->ContextFlags));
context_to_mcontext(&mc, cxt);
res = translate_mcontext(trec, &mc, restore_memory, NULL);
if (res) {
mcontext_to_context(cxt, &mc, false );
}
return res;
}
* see SYSCALL_PARAM_OFFSET in win32/os.c.
*
* This routine is assumed to only be used for NtRaiseException, where changes
* to regs or even the stack will be unrolled or else the app will exit:
* i.e., there is no need to restore the changes ourselves.
*/
static void
set_mcontext_for_syscall(dcontext_t *dcontext, int sys_enum,
# ifdef X64
reg_t arg1, reg_t arg2, reg_t arg3
# else
reg_t sys_arg, size_t args_size
# endif
)
{
priv_mcontext_t *mc = get_mcontext(dcontext);
# ifdef X64
LOG(THREAD, LOG_SYSCALLS, 2,
"issue_last_system_call_from_app(0x%x, " PFX " " PFX " " PFX ")\n",
syscalls[sys_enum], arg1, arg2, arg3);
# else
LOG(THREAD, LOG_SYSCALLS, 2, "issue_last_system_call_from_app(0x%x, " PFX ")\n",
syscalls[sys_enum], sys_arg);
# endif
mc->xax = syscalls[sys_enum];
if (get_syscall_method() == SYSCALL_METHOD_WOW64 && syscall_uses_wow64_index()) {
mc->xcx = wow64_index[sys_enum];
}
# ifdef X64
mc->xcx = arg1;
mc->xdx = arg2;
mc->r8 = arg3;
# else
if (syscall_uses_edx_param_base())
mc->xdx = sys_arg;
else {
* so go ahead and push the args. See comment up top about not
* needing to restore the stack.
*/
mc->xsp -= args_size;
if (!safe_write((byte *)mc->xsp, args_size, (const void *)sys_arg)) {
SYSLOG_INTERNAL_WARNING("failed to store args for NtRaiseException");
}
}
# endif
}
void
os_raise_exception(dcontext_t *dcontext, EXCEPTION_RECORD *pexcrec, CONTEXT *pcontext)
{
# ifdef X64
set_mcontext_for_syscall(dcontext, SYS_RaiseException, (reg_t)pexcrec,
(reg_t)pcontext, (reg_t) true);
# else
struct _raise_exception_arguments_t {
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT Context;
DWORD SearchFrames;
} raise_exception_arguments = { pexcrec, pcontext, true };
* here so is ok if os clobbers them, though it won't since natively
* they hold return addresses */
reg_t arg_pointer =
(reg_t)((ptr_uint_t)&raise_exception_arguments) - SYSCALL_PARAM_OFFSET();
set_mcontext_for_syscall(dcontext, SYS_RaiseException, arg_pointer,
sizeof(raise_exception_arguments) + SYSCALL_PARAM_OFFSET()),
# endif
issue_last_system_call_from_app(dcontext);
ASSERT_NOT_REACHED();
}
* CORE DUMPS
*/
START_DATA_SECTION(NEVER_PROTECTED_SECTION, "w");
static char dump_core_buf[256] VAR_IN_SECTION(NEVER_PROTECTED_SECTION) = {
0,
};
static char dump_core_file_name[MAXIMUM_PATH] VAR_IN_SECTION(NEVER_PROTECTED_SECTION) = {
0,
};
static void
os_dump_core_segment_info(file_t file, HANDLE h, ULONG selector, const char *name)
{
NTSTATUS res;
DESCRIPTOR_TABLE_ENTRY entry = {
0,
};
entry.Selector = selector;
res = query_seg_descriptor(h, &entry);
* support for the underlying system call: we get STATUS_NOT_IMPLEMENTED.
*/
if (NT_SUCCESS(res)) {
snprintf(dump_core_buf, BUFFER_SIZE_ELEMENTS(dump_core_buf),
"%s=0x%04x (0x%08x 0x%08x)\n", name, entry.Selector,
*((PULONG)&entry.Descriptor), *(((PULONG)&entry.Descriptor) + 1));
} else {
snprintf(dump_core_buf, BUFFER_SIZE_ELEMENTS(dump_core_buf), "%s=0x%04x\n", name,
entry.Selector);
}
NULL_TERMINATE_BUFFER(dump_core_buf);
os_write(file, dump_core_buf, strlen(dump_core_buf));
}
static void
os_dump_core_dump_thread(file_t file, thread_id_t tid, TEB *teb, HANDLE h,
int handle_rights, CONTEXT *cxt, dcontext_t *dcontext)
{
app_pc win32_start_addr = 0;
snprintf(dump_core_buf, BUFFER_SIZE_ELEMENTS(dump_core_buf),
"Thread=" PFX "\nTEB=" PFX "\n"
"HandleRights=0x%08x\n"
"Eax=" PFX ", Ebx=" PFX ", Ecx=" PFX ", Edx=" PFX "\n"
"Esi=" PFX ", Edi=" PFX ", Esp=" PFX ", Ebp=" PFX "\n"
"EFlags=" PFX ", Eip=" PFX "\n",
tid, teb, handle_rights, cxt->CXT_XAX, cxt->CXT_XBX, cxt->CXT_XCX,
cxt->CXT_XDX, cxt->CXT_XSI, cxt->CXT_XDI, cxt->CXT_XSP, cxt->CXT_XBP,
cxt->CXT_XFLAGS, cxt->CXT_XIP);
NULL_TERMINATE_BUFFER(dump_core_buf);
os_write(file, dump_core_buf, strlen(dump_core_buf));
os_dump_core_segment_info(file, h, cxt->SegCs, "Cs");
os_dump_core_segment_info(file, h, cxt->SegSs, "Ss");
os_dump_core_segment_info(file, h, cxt->SegDs, "Ds");
os_dump_core_segment_info(file, h, cxt->SegEs, "Es");
os_dump_core_segment_info(file, h, cxt->SegFs, "Fs");
os_dump_core_segment_info(file, h, cxt->SegGs, "Gs");
* dcontext when the thread is created.
*/
if (dcontext != NULL) {
win32_start_addr = dcontext->win32_start_addr;
}
else {
NTSTATUS res = query_win32_start_addr(h, &win32_start_addr);
ASSERT(NT_SUCCESS(res) && "failed to obtain win32 start address");
}
snprintf(dump_core_buf, BUFFER_SIZE_ELEMENTS(dump_core_buf),
"Win32StartAddr=" PFX "\n", win32_start_addr);
NULL_TERMINATE_BUFFER(dump_core_buf);
os_write(file, dump_core_buf, strlen(dump_core_buf));
}
# pragma warning(push)
# pragma warning(disable : 4740)
static bool
os_dump_core_live_dump(const char *msg, char *path OUT, size_t path_sz)
{
* dumpcore_lock and are static to save stack space (CONTEXT is quite
* sizable) */
static file_t dmp_file VAR_IN_SECTION(NEVER_PROTECTED_SECTION) = INVALID_FILE;
static thread_record_t *tr VAR_IN_SECTION(NEVER_PROTECTED_SECTION) = NULL;
static thread_record_t *my_tr VAR_IN_SECTION(NEVER_PROTECTED_SECTION) = NULL;
static int i VAR_IN_SECTION(NEVER_PROTECTED_SECTION) = 0;
static thread_id_t my_id VAR_IN_SECTION(NEVER_PROTECTED_SECTION) = 0;
static bool have_all_threads_lock VAR_IN_SECTION(NEVER_PROTECTED_SECTION) = false;
static PBYTE pb VAR_IN_SECTION(NEVER_PROTECTED_SECTION) = NULL;
static MEMORY_BASIC_INFORMATION mbi VAR_IN_SECTION(NEVER_PROTECTED_SECTION) = {
0,
};
static CONTEXT cxt VAR_IN_SECTION(NEVER_PROTECTED_SECTION) = {
0,
};
DEBUG_DECLARE(static bool suspend_failures VAR_IN_SECTION(NEVER_PROTECTED_SECTION) =
false;)
pb = NULL;
have_all_threads_lock = false;
my_id = d_r_get_thread_id();
my_tr = NULL;
* (xref PR 264138) */
cxt.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS;
* deadlock, caller should have synchronized already anyways */
if (!get_unique_logfile(".ldmp", dump_core_file_name, sizeof(dump_core_file_name),
false, &dmp_file) ||
dmp_file == INVALID_FILE) {
SYSLOG_INTERNAL_NO_OPTION_SYNCH(SYSLOG_WARNING, "Unable to open core dump file");
return false;
}
if (msg != NULL) {
size_t length = strlen(msg);
snprintf(dump_core_buf, BUFFER_SIZE_ELEMENTS(dump_core_buf), PFX "\n",
length + 1 );
NULL_TERMINATE_BUFFER(dump_core_buf);
os_write(dmp_file, dump_core_buf, strlen(dump_core_buf));
os_write(dmp_file, msg, length);
os_write(dmp_file, "\n", 1);
}
* both of which might be dangerous on this path, instead walk table
* by hand (we try to grab the necessary locks, but we will go ahead
* and walk the table if we can't FIXME)
* FIXME : share with dynamo.c */
* NOTE os_dump_core, already turned off deadlock_avoidance for us */
# ifdef DEADLOCK_AVOIDANCE
* we already own, even if its only a trylock and even if the option is
* turned off! We hack around it here */
if (all_threads_lock.owner == d_r_get_thread_id()) {
LOG(GLOBAL, LOG_ALL, 1,
"WARNING : live dump, faulting thread already owns the all_threads lock, "
"let's hope things are consistent\n");
} else {
# endif
for (i = 0; i < 100 ; i++) {
if (d_r_mutex_trylock(&all_threads_lock)) {
have_all_threads_lock = true;
break;
} else {
os_thread_yield();
}
}
DODEBUG({
if (!have_all_threads_lock) {
LOG(GLOBAL, LOG_ALL, 1,
"WARNING : live dump unable to grab all_threads lock, "
"continuing without it\n");
}
});
# ifdef DEADLOCK_AVOIDANCE
}
# endif
snprintf(dump_core_buf, BUFFER_SIZE_ELEMENTS(dump_core_buf), "PEB=" PFX "\n",
get_own_peb());
NULL_TERMINATE_BUFFER(dump_core_buf);
os_write(dmp_file, dump_core_buf, strlen(dump_core_buf));
snprintf(dump_core_buf, BUFFER_SIZE_ELEMENTS(dump_core_buf),
"dynamorio.dll=" PFX "\n", get_dynamorio_dll_start());
NULL_TERMINATE_BUFFER(dump_core_buf);
os_write(dmp_file, dump_core_buf, strlen(dump_core_buf));
if (all_threads != NULL) {
for (i = 0; i < HASHTABLE_SIZE(ALL_THREADS_HASH_BITS); i++) {
for (tr = all_threads[i]; tr != NULL; tr = tr->next) {
if (tr->id == my_id)
my_tr = tr;
}
}
}
GET_OWN_CONTEXT(&cxt);
os_dump_core_dump_thread(dmp_file, my_id, get_own_teb(), NT_CURRENT_THREAD,
my_tr != NULL ? nt_get_handle_access_rights(my_tr->handle)
: 0,
&cxt, my_tr != NULL ? my_tr->dcontext : NULL);
if (all_threads != NULL) {
for (i = 0; i < HASHTABLE_SIZE(ALL_THREADS_HASH_BITS); i++) {
for (tr = all_threads[i]; tr != NULL; tr = tr->next) {
if (tr->id != my_id) {
ACCESS_MASK handle_rights = nt_get_handle_access_rights(tr->handle);
TEB *teb_addr = get_teb(tr->handle);
DEBUG_DECLARE(bool res =)
os_thread_suspend(tr);
DODEBUG({ suspend_failures = suspend_failures || !res; });
if (thread_get_context(tr, &cxt)) {
os_dump_core_dump_thread(dmp_file, tr->id, teb_addr, tr->handle,
handle_rights, &cxt, tr->dcontext);
} else {
snprintf(dump_core_buf, BUFFER_SIZE_ELEMENTS(dump_core_buf),
"Thread=0x%08x\nTEB=" PFX "\n"
"HandleRights=0x%08x\n"
"<error state not available>\n\n",
tr->id, teb_addr, handle_rights);
NULL_TERMINATE_BUFFER(dump_core_buf);
os_write(dmp_file, dump_core_buf, strlen(dump_core_buf));
}
}
}
}
} else {
const char *error_msg = "<error all threads list is already freed>";
os_write(dmp_file, error_msg, strlen(error_msg));
* walking the memory below could be racy, what if another thread
* frees some chunk of memory while we are copying it! Just live with
* the race for now. */
}
while (query_virtual_memory(pb, &mbi, sizeof(mbi)) == sizeof(mbi)) {
snprintf(dump_core_buf, BUFFER_SIZE_ELEMENTS(dump_core_buf),
"\n"
"BaseAddress=" PFX "\n"
"AllocationBase=" PFX "\n"
"AllocationProtect=0x%08x %s\n"
"RegionSize=0x%08x\n"
"State=0x%08x %s\n"
"Protect=0x%08x %s\n"
"Type=0x%08x %s\n",
mbi.BaseAddress, mbi.AllocationBase, mbi.AllocationProtect,
prot_string(mbi.AllocationProtect), mbi.RegionSize, mbi.State,
mem_state_string(mbi.State), mbi.Protect, prot_string(mbi.Protect),
mbi.Type, mem_type_string(mbi.Type));
NULL_TERMINATE_BUFFER(dump_core_buf);
os_write(dmp_file, dump_core_buf, strlen(dump_core_buf));
if (mbi.State == MEM_COMMIT && !TEST(PAGE_GUARD, mbi.Protect) &&
prot_is_readable(mbi.Protect)) {
os_write(dmp_file, mbi.BaseAddress, mbi.RegionSize);
}
if (POINTER_OVERFLOW_ON_ADD(pb, mbi.RegionSize))
break;
pb += mbi.RegionSize;
}
{
* in the general case one methodolgy requires the debug privilege
* the other requires that a global flag is set at boot time
* FIXME */
}
if (all_threads != NULL) {
for (i = 0; i < HASHTABLE_SIZE(ALL_THREADS_HASH_BITS); i++) {
for (tr = all_threads[i]; tr != NULL; tr = tr->next) {
if (tr->id != my_id) {
* resume will also fail -- o/w we could end up resuming
* a thread that a caller suspended!
*/
DEBUG_DECLARE(bool res =)
os_thread_resume(tr);
DODEBUG({ suspend_failures = suspend_failures || !res; });
}
}
}
}
if (have_all_threads_lock)
d_r_mutex_unlock(&all_threads_lock);
close_file(dmp_file);
SYSLOG_NO_OPTION_SYNCH(SYSLOG_INFORMATION, LDMP, 3, get_application_name(),
get_application_pid(), dump_core_file_name);
if (path != NULL) {
strncpy(path, dump_core_file_name, path_sz);
path[path_sz - 1] = '\0';
}
DODEBUG({
if (suspend_failures) {
SYSLOG_INTERNAL_NO_OPTION_SYNCH(
SYSLOG_ERROR, "suspend/resume failures during ldmp creation");
}
});
return true;
}
# pragma warning(pop)
# ifdef INTERNAL
static void
os_dump_core_external_dump()
{
* dumpcore_lock from os_dump_core() */
static char oncrash_var[MAXIMUM_PATH] VAR_IN_SECTION(NEVER_PROTECTED_SECTION) = {
0,
};
static wchar_t oncrash_cmdline[MAXIMUM_PATH] VAR_IN_SECTION(
NEVER_PROTECTED_SECTION) = {
0,
};
static wchar_t oncrash_exe[MAXIMUM_PATH] VAR_IN_SECTION(NEVER_PROTECTED_SECTION) = {
0,
};
int retval = d_r_get_parameter(PARAM_STR(DYNAMORIO_VAR_ONCRASH), oncrash_var,
sizeof(oncrash_var));
if (IS_GET_PARAMETER_SUCCESS(retval)) {
HANDLE child;
* specify a name for this key, everyone else will use tools
*/
char *c = strchr(oncrash_var, ' ');
if (c == NULL)
c = oncrash_var + strlen(oncrash_var);
ASSERT(c - oncrash_var < sizeof(oncrash_exe) / sizeof(wchar_t));
snwprintf(oncrash_exe, c - oncrash_var, L"%hs", oncrash_var);
oncrash_exe[c - oncrash_var] = L'\0';
snwprintf(oncrash_cmdline, sizeof(oncrash_cmdline) / sizeof(wchar_t), L"%hs %hs",
oncrash_var, get_application_pid());
NULL_TERMINATE_BUFFER(oncrash_cmdline);
SYSLOG_INTERNAL_INFO("Thread %d dumping core via \"%ls\"", d_r_get_thread_id(),
oncrash_cmdline);
child = create_process(oncrash_exe, oncrash_cmdline);
if (child != INVALID_HANDLE_VALUE) {
* FIXME: this makes ntsd have to do a 30-second wait to break in!
* plus it causes drwtsn32 to hang, then timeout and kill us
* w/o producing a dump file -- and only the header on the log file
* BUT, if we don't do this, we only get dumps for -kill_thread!
*/
nt_wait_event_with_timeout(child, INFINITE_WAIT);
close_handle(child);
} else
SYSLOG_INTERNAL_WARNING("Unable to dump core via \"%ls\"", oncrash_cmdline);
} else {
SYSLOG_INTERNAL_WARNING("Unable to dump core due to missing parameter");
}
}
# endif
static bool
os_dump_core_internal(const char *msg, bool live_only, char *path OUT, size_t path_sz)
{
static thread_id_t current_dumping_thread_id VAR_IN_SECTION(NEVER_PROTECTED_SECTION) =
0;
bool res = true;
thread_id_t current_id = d_r_get_thread_id();
# ifdef DEADLOCK_AVOIDANCE
dcontext_t *dcontext = get_thread_private_dcontext();
thread_locks_t *old_thread_owned_locks = NULL;
# endif
if (current_id == current_dumping_thread_id)
return false;
* dump_core_lock could lead to an infinite recursion, also a failure while
* holding the eventlog_lock would lead to a deadlock at the syslog in
* livedump (but we would likely deadlock later anyways), all other
* recursion/deadlock cases should be handled by the above check */
# ifdef DEADLOCK_AVOIDANCE
* to try to grab all_threads and thread_initexit locks) */
if (dcontext != NULL) {
old_thread_owned_locks = dcontext->thread_owned_locks;
dcontext->thread_owned_locks = NULL;
}
# endif
* buffers and current_dumping_thread_id */
d_r_mutex_lock(&dump_core_lock);
current_dumping_thread_id = current_id;
if (live_only || DYNAMO_OPTION(live_dump)) {
res = os_dump_core_live_dump(msg, path, path_sz);
}
# ifdef INTERNAL
if (!live_only && DYNAMO_OPTION(external_dump)) {
os_dump_core_external_dump();
}
# endif
current_dumping_thread_id = 0;
d_r_mutex_unlock(&dump_core_lock);
# ifdef DEADLOCK_AVOIDANCE
if (dcontext != NULL) {
dcontext->thread_owned_locks = old_thread_owned_locks;
}
# endif
return res;
}
void
os_dump_core(const char *msg)
{
os_dump_core_internal(msg, false, NULL, 0);
}
bool
os_dump_core_live(const char *msg, char *path OUT, size_t path_sz)
{
return os_dump_core_internal(msg, true , path, path_sz);
}
END_DATA_SECTION()
static bool internal_detach = false;
*
* For sysenter system calls the kernel callback return returns to a known fixed
* location that does a ret. To regain control we have overwritten the return
* address on the stack to point back to the after syscall location and need to restore
* the original target here.
*
* For all other types of system calls the kernel will return the instruction after
* the system call which is in our generated code. We allocate a piece of thread
* shared code here followed by an array of thread private detach_callback_stack_ts and
* an array of the callback return addresses. We redirect all after syscall locations
* to that shared code which then dispatches on thread_id to find the proper
* detach_callback_stack_t, get the right return address from it and then jmp to it.
*
* Returns true if there are outstanding non-sysenter callbacks.
*/
* that can be left behind to reduce the memory hit?
*/
bool
detach_handle_callbacks(int num_threads, thread_record_t **threads,
bool *cleanup_tpc )
{
int i, num_threads_with_callbacks = 0, num_stacked_callbacks = 0;
* of stacked callbacks (and also fixes the stack for sysenter system calls) so we
* now how much memory to allocate for non-sysenter system calls. */
for (i = 0; i < num_threads; i++) {
dcontext_t *dcontext = threads[i]->dcontext;
cleanup_tpc[i] = true;
if (dcontext->prev_unused != NULL && dcontext->prev_unused->valid) {
dcontext_t *tmp_dc = dcontext->prev_unused;
int count = 0;
LOG(GLOBAL, LOG_ALL, 1, "Detach : thread " TIDFMT " has stacked callbacks\n",
threads[i]->id);
do {
count++;
LOG(GLOBAL, LOG_ALL, 1, "callback %d has ret pc " PFX "\n", count,
POST_SYSCALL_PC(tmp_dc));
ASSERT(POST_SYSCALL_PC(tmp_dc) != NULL &&
!is_dynamo_address(POST_SYSCALL_PC(tmp_dc)));
if (get_syscall_method() == SYSCALL_METHOD_SYSENTER &&
INTERNAL_OPTION(detach_fix_sysenter_on_stack)) {
* fixed location this is all we need to do to restore app state.
* Note that shared syscall saves xsp for us, so xsp should be
* correct. */
ASSERT(*((app_pc *)get_mcontext(tmp_dc)->xsp) ==
after_do_syscall_code(dcontext) ||
*((app_pc *)get_mcontext(tmp_dc)->xsp) ==
after_shared_syscall_code(dcontext));
LOG(GLOBAL, LOG_ALL, 1,
"callback %d patching stack address " PFX " from " PFX " to " PFX
"\n",
get_mcontext(tmp_dc)->xsp, *((app_pc *)get_mcontext(tmp_dc)->xsp),
POST_SYSCALL_PC(tmp_dc));
*((app_pc *)get_mcontext(tmp_dc)->xsp) = POST_SYSCALL_PC(tmp_dc);
if (DYNAMO_OPTION(sygate_sysenter)) {
*((app_pc *)(get_mcontext(tmp_dc)->xsp + XSP_SZ)) =
dcontext->sysenter_storage;
}
}
tmp_dc = tmp_dc->prev_unused;
} while (tmp_dc != NULL && tmp_dc->valid);
num_threads_with_callbacks++;
num_stacked_callbacks += count;
* will return to there */
cleanup_tpc[i] = (get_syscall_method() == SYSCALL_METHOD_SYSENTER &&
INTERNAL_OPTION(detach_fix_sysenter_on_stack));
LOG(GLOBAL, LOG_ALL, 1,
"Detach : thread " TIDFMT " had %d stacked callbacks\n", threads[i]->id,
count);
} else {
LOG(GLOBAL, LOG_ALL, 1,
"Detach : thread " TIDFMT " has no stacked callbacks\n", threads[i]->id);
}
}
* the callback dispatch code and data structures. */
if (num_stacked_callbacks > 0 &&
(get_syscall_method() != SYSCALL_METHOD_SYSENTER ||
!INTERNAL_OPTION(detach_fix_sysenter_on_stack))) {
* {
* byte dispatch_code[DETACH_CALLBACK_CODE_SIZE];
* detach_callback_stack_t per_thread[num_threads_with_callbacks]
* app_pc callback_addrs[num_stacked_callbacks]
* }
* Not a real struct since variable size arrays. Note that nothing requires the
* above elements to be in that order (or even in the same allocation). We
* allocate them together to save memory since we must leak this. FIXME - find
* a way to free the allocation once we are finished with it. */
int callback_buf_size = DETACH_CALLBACK_CODE_SIZE +
num_threads_with_callbacks * sizeof(detach_callback_stack_t) +
num_stacked_callbacks * sizeof(app_pc);
* allocated outside of our vmmheap so that we can free the vmmheap reservation
* on detach. */
byte *callback_buf = (byte *)heap_mmap(
callback_buf_size, MEMPROT_EXEC | MEMPROT_READ | MEMPROT_WRITE,
VMM_SPECIAL_MMAP);
detach_callback_stack_t *per_thread =
(detach_callback_stack_t *)(callback_buf + DETACH_CALLBACK_CODE_SIZE);
app_pc *callback_addrs = (app_pc *)(&per_thread[num_threads_with_callbacks]);
int j = 0;
emit_detach_callback_code(GLOBAL_DCONTEXT, callback_buf, per_thread);
# ifdef X64
arch_patch_syscall(GLOBAL_DCONTEXT, callback_buf);
# endif
for (i = 0; i < num_threads; i++) {
dcontext_t *dcontext = threads[i]->dcontext;
if (dcontext->prev_unused != NULL && dcontext->prev_unused->valid) {
dcontext_t *tmp_dc = dcontext->prev_unused;
arch_patch_syscall(dcontext, callback_buf);
emit_detach_callback_final_jmp(dcontext, &(per_thread[j]));
per_thread[j].callback_addrs = callback_addrs;
per_thread[j].tid = dcontext->owning_thread;
per_thread[j].count = 0;
* (see callback.c, the last dcontext is considered the top of the
* stack). This is ok since our emitted code expects this. */
do {
*callback_addrs++ = POST_SYSCALL_PC(tmp_dc);
ASSERT((byte *)callback_addrs - (byte *)per_thread <=
callback_buf_size);
per_thread[j].count++;
tmp_dc = tmp_dc->prev_unused;
} while (tmp_dc != NULL && tmp_dc->valid);
j++;
}
}
ASSERT(j == num_threads_with_callbacks);
return true;
}
return false;
}
void
detach_remove_image_entry_hook(int num_threads, thread_record_t **threads)
{
* we do so now. We can tell from the callback hack: look for a thread with
* LOST_CONTROL_AT_CALLBACK in the under_dynamo_control bool.
*/
bool did_unhook = false;
int i;
for (i = 0; i < num_threads; i++) {
if (IS_UNDER_DYN_HACK(threads[i]->under_dynamo_control)) {
LOG(GLOBAL, LOG_ALL, 1,
"Detach : unpatching image entry point (from thread " TIDFMT ")\n",
threads[i]->id);
ASSERT(!did_unhook);
did_unhook = true;
remove_image_entry_trampoline();
}
}
if (!did_unhook) {
if (dr_injected_secondary_thread && !dr_late_injected_primary_thread) {
LOG(GLOBAL, LOG_ALL, 1,
"Detach : unpatching image entry point (from primary)\n");
did_unhook = true;
remove_image_entry_trampoline();
}
}
}
bool
detach_do_not_translate(thread_record_t *tr)
{
if (IS_UNDER_DYN_HACK(tr->under_dynamo_control)) {
LOG(GLOBAL, LOG_ALL, 1,
"Detach : thread " TIDFMT " running natively since lost control at callback "
"return and have not regained it, no need to translate context\n",
tr->id);
* even though native) since we should've re-taken over by then.
*/
DOCHECK(1, {
priv_mcontext_t mc;
bool res = thread_get_mcontext(tr, &mc);
ASSERT(res);
ASSERT(!is_at_do_syscall(tr->dcontext, (app_pc)mc.pc, (byte *)mc.xsp));
});
return true;
}
return false;
}
void
detach_finalize_translation(thread_record_t *tr, priv_mcontext_t *mc)
{
dcontext_t *dcontext = tr->dcontext;
* on the stack as part of the translation.
*/
if (get_syscall_method() == SYSCALL_METHOD_SYSENTER &&
mc->pc == (app_pc)vsyscall_after_syscall) {
ASSERT(get_os_version() >= WINDOWS_VERSION_XP);
* esp+4 (esp will point to sysenter_ret_address in ntdll)
*/
if (*(cache_pc *)(mc->xsp + (DYNAMO_OPTION(sygate_sysenter) ? XSP_SZ : 0)) ==
after_do_syscall_code(dcontext) ||
*(cache_pc *)(mc->xsp + (DYNAMO_OPTION(sygate_sysenter) ? XSP_SZ : 0)) ==
after_shared_syscall_code(dcontext)) {
LOG(GLOBAL, LOG_ALL, 1,
"Detach : thread " TIDFMT " suspended at vsysall with ret to after "
"shared syscall, fixing up by changing ret to " PFX "\n",
tr->id, POST_SYSCALL_PC(dcontext));
if (DYNAMO_OPTION(sygate_sysenter))
*(app_pc *)(mc->xsp + XSP_SZ) = dcontext->sysenter_storage;
*(app_pc *)mc->xsp = POST_SYSCALL_PC(dcontext);
} else {
LOG(GLOBAL, LOG_ALL, 1,
"Detach, thread " TIDFMT " suspended at vsyscall with ret to "
"unknown addr, must be running native!\n",
tr->id);
}
}
}
void
detach_finalize_cleanup(void)
{
# ifndef DEBUG
tls_cfree(true , (uint)tls_local_state_offs, TLS_NUM_SLOTS);
# endif
}
* will be different (and the number of threads if a non-app-API-triggered detach).
*/
void
detach_helper(int detach_type)
{
dcontext_t *my_dcontext = get_thread_private_dcontext();
* verified the nudge is valid.
*/
ASSERT(my_dcontext != NULL);
if (my_dcontext == NULL)
return;
ASSERT(detach_type < DETACH_NORMAL_TYPE ||
((my_dcontext != NULL && my_dcontext->whereami == DR_WHERE_FCACHE) ||
(RUNNING_WITHOUT_CODE_CACHE() && my_dcontext->whereami == DR_WHERE_APP)));
detach_on_permanent_stack(internal_detach, detach_type != DETACH_BAD_STATE_NO_CLEANUP,
NULL);
if (detach_type == DETACH_BAD_STATE_NO_CLEANUP) {
SYSLOG_INTERNAL_WARNING("finished detaching, skipping cleanup");
eventlog_fast_exit();
* our dll for proper tools function. */
* cache, if we return we will return back into the cache! It would be
* cleaner for the thread to die by returning from its start function,
* but to avoid complications we just kill it here. */
* thread dies unlike earlier versions). With the fix for that case we
* should no longer be holding any application locks at this point. */
nt_terminate_thread(NT_CURRENT_THREAD, 0);
ASSERT_NOT_REACHED();
return;
}
* no longer work (even options have been reset to their default values).
*/
* cache, if we return we will return back into the cache! It would be
* cleaner for the thread to die by returning from its start function,
* but to avoid complications we just kill it here. */
* thread dies unlike earlier versions). With the fix for that case we
* should no longer be holding any application locks at this point. */
nt_terminate_thread(NT_CURRENT_THREAD, 0);
ASSERT_NOT_REACHED();
return;
}
* be notifed of its creation by dll_thread_attach, is a transparency issue. */
void
detach_internal()
{
SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT);
internal_detach = true;
SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT);
LOG(GLOBAL, LOG_ALL, 1, "Starting detach\n");
nudge_internal(get_process_id(), NUDGE_GENERIC(detach), 0, 0 , 0);
LOG(GLOBAL, LOG_ALL, 1, "Created detach thread\n");
}
* must not be holding any locks */
void
detach_internal_synch()
{
dcontext_t *dcontext = get_thread_private_dcontext();
detach_internal();
enter_threadexit(dcontext);
adjust_wait_at_safe_spot(dcontext, 1);
check_wait_at_safe_spot(dcontext, THREAD_SYNCH_VALID_MCONTEXT);
}
bool
is_thread_currently_native(thread_record_t *tr)
{
return (!tr->under_dynamo_control ||
(tr->dcontext != NULL && tr->dcontext->currently_stopped) ||
IS_UNDER_DYN_HACK(tr->under_dynamo_control));
}
bool
ksynch_var_initialized(HANDLE *event)
{
return (*event != NULL);
}
static contention_event_t
mutex_get_contended_event(contention_event_t *contended_event, EVENT_TYPE event_type)
{
contention_event_t ret = *contended_event;
if (ret == NULL) {
contention_event_t new_event;
bool not_yet_created;
new_event = nt_create_event(event_type);
not_yet_created = atomic_compare_exchange_ptr(
(ptr_uint_t *)contended_event, (ptr_uint_t)NULL, (ptr_uint_t)new_event);
if (not_yet_created) {
ret = new_event;
} else {
ret = *contended_event;
close_handle(new_event);
}
}
ASSERT(ksynch_var_initialized(&ret));
return ret;
}
void
mutex_free_contended_event(mutex_t *lock)
{
os_close(lock->contended_event);
}
* timeout, true on signalled.
*
* A 0 timeout_ms means to wait forever.
* A non-NULL mc will mark this thread safe to suspend and transfer; setting mc
* requires a non-NULL dcontext to be passed.
*/
static bool
os_wait_event(event_t e, int timeout_ms, bool set_safe_for_synch, dcontext_t *dcontext,
priv_mcontext_t *mc)
{
wait_status_t res;
bool reported_timeout = false;
LARGE_INTEGER timeout;
if (mc != NULL) {
ASSERT(dcontext != NULL);
*get_mcontext(dcontext) = *mc;
}
KSTART(wait_event);
if (timeout_ms == 0 && DYNAMO_OPTION(deadlock_timeout) > 0) {
timeout.QuadPart =
-((int)DYNAMO_OPTION(deadlock_timeout)) * TIMER_UNITS_PER_MILLISECOND;
ASSERT(!set_safe_for_synch || dcontext != NULL);
if (set_safe_for_synch)
dcontext->client_data->client_thread_safe_for_synch = true;
if (mc != NULL)
set_synch_state(dcontext, THREAD_SYNCH_VALID_MCONTEXT);
res = nt_wait_event_with_timeout(e, &timeout );
if (set_safe_for_synch)
dcontext->client_data->client_thread_safe_for_synch = false;
if (mc != NULL)
set_synch_state(dcontext, THREAD_SYNCH_NONE);
if (res == WAIT_SIGNALED) {
KSTOP(wait_event);
return true;
}
ASSERT(res == WAIT_TIMEDOUT);
* there was a debugger, but we can't just ignore this. It's better
* to explicitly overwrite the hidden DO_ONCE variable from a debugging
* session if this is getting in the way.
*/
* we can access easily from the debugger. */
DO_ONCE({
reported_timeout = true;
report_dynamorio_problem(NULL, DUMPCORE_TIMEOUT, NULL, NULL,
"Timeout expired - 1st wait, possible deadlock "
"(or you were debugging)");
res = nt_wait_event_with_timeout(e, &timeout );
if (res == WAIT_SIGNALED) {
* Syslog a warning to ignore the first ldmp and continue. */
* such so is more useful in release builds? */
SYSLOG_INTERNAL_WARNING("WARNING - 2nd wait after deadlock timeout "
"expired succeeded! Not really deadlocked.");
KSTOP(wait_event);
return true;
}
ASSERT(res == WAIT_TIMEDOUT);
report_dynamorio_problem(NULL, DUMPCORE_TIMEOUT, NULL, NULL,
"Timeout expired - 2nd wait, possible deadlock "
"(or you were debugging)");
});
}
if (set_safe_for_synch)
dcontext->client_data->client_thread_safe_for_synch = true;
if (mc != NULL)
set_synch_state(dcontext, THREAD_SYNCH_VALID_MCONTEXT);
if (timeout_ms > 0)
timeout.QuadPart = -timeout_ms * TIMER_UNITS_PER_MILLISECOND;
res = nt_wait_event_with_timeout(e, timeout_ms > 0 ? &timeout : INFINITE_WAIT);
if (set_safe_for_synch)
dcontext->client_data->client_thread_safe_for_synch = false;
if (mc != NULL)
set_synch_state(dcontext, THREAD_SYNCH_NONE);
if (reported_timeout) {
* warning to that effect. */
* such so is more useful in release builds? */
SYSLOG_INTERNAL_WARNING("WARNING - Final wait after reporting deadlock timeout "
"expired succeeded! Not really deadlocked.");
}
KSTOP(wait_event);
return (res == WAIT_SIGNALED);
}
#endif
wait_status_t
os_wait_handle(HANDLE h, uint64 timeout_ms)
{
LARGE_INTEGER li;
LARGE_INTEGER *timeout;
if (timeout_ms == INFINITE)
timeout = INFINITE_WAIT;
else {
li.QuadPart = -(int64)timeout_ms * TIMER_UNITS_PER_MILLISECOND;
timeout = &li;
}
return nt_wait_event_with_timeout(h, timeout);
}
#ifndef NOT_DYNAMORIO_CORE_PROPER
void
mutex_wait_contended_lock(mutex_t *lock, priv_mcontext_t *mc)
{
contention_event_t event =
mutex_get_contended_event(&lock->contended_event, SynchronizationEvent);
dcontext_t *dcontext = get_thread_private_dcontext();
bool set_safe_for_sync =
(dcontext != NULL && IS_CLIENT_THREAD(dcontext) &&
(mutex_t *)dcontext->client_data->client_grab_mutex == lock);
ASSERT(!set_safe_for_sync || dcontext != NULL);
an mcontext to return into: nothing would be able to reset the
client_thread_safe_for_sync flag.
*/
ASSERT(!(set_safe_for_sync && mc != NULL));
os_wait_event(event, 0, set_safe_for_sync, dcontext, mc);
the auto-reset event is again nonsignaled for all other threads to wait on
*/
}
void
mutex_notify_released_lock(mutex_t *lock)
{
contention_event_t event =
mutex_get_contended_event(&lock->contended_event, SynchronizationEvent);
nt_set_event(event);
}
void
rwlock_wait_contended_writer(read_write_lock_t *rwlock)
{
contention_event_t event =
mutex_get_contended_event(&rwlock->writer_waiting_readers, SynchronizationEvent);
os_wait_event(event, 0, false, NULL, NULL);
the auto-reset event is again nonsignaled for all other threads to wait on
*/
}
void
rwlock_notify_writer(read_write_lock_t *rwlock)
{
contention_event_t event =
mutex_get_contended_event(&rwlock->writer_waiting_readers, SynchronizationEvent);
nt_set_event(event);
}
single reader. We then expect each of them to wake up any other ones
by properly counting.
*/
void
rwlock_wait_contended_reader(read_write_lock_t *rwlock)
{
contention_event_t notify_readers =
mutex_get_contended_event(&rwlock->readers_waiting_writer, SynchronizationEvent);
os_wait_event(notify_readers, 0, false, NULL, NULL);
* this event are released, if this was indeed the last reader
*/
}
void
rwlock_notify_readers(read_write_lock_t *rwlock)
{
contention_event_t notify_readers =
mutex_get_contended_event(&rwlock->readers_waiting_writer, SynchronizationEvent);
nt_set_event(notify_readers);
}
event_t
create_event(void)
{
return nt_create_event(SynchronizationEvent);
}
event_t
create_broadcast_event(void)
{
return nt_create_event(NotificationEvent);
}
void
destroy_event(event_t e)
{
nt_close_event(e);
}
void
signal_event(event_t e)
{
nt_set_event(e);
}
void
reset_event(event_t e)
{
nt_clear_event(e);
}
bool
wait_for_event(event_t e, int timeout_ms)
{
return os_wait_event(e, timeout_ms, false, NULL, NULL);
}
timestamp_t
get_timer_frequency()
{
LARGE_INTEGER ignore_tsc;
LARGE_INTEGER freq;
timestamp_t processor_speed;
nt_query_performance_counter(&ignore_tsc,
&freq);
DOLOG(2, LOG_ALL, {
timestamp_t tsc;
RDTSC_LL(tsc);
LOG(GLOBAL, LOG_ALL, 2,
"Starting RDTSC: " UINT64_FORMAT_STRING
" nt_query_performance_counter: " UINT64_FORMAT_STRING
" freq:" UINT64_FORMAT_STRING "\n",
tsc, ignore_tsc.QuadPart, freq.QuadPart);
});
processor_speed = freq.QuadPart / 1000;
if (processor_speed < 500 * 1000 ) {
processor_speed = 2937 * 1000;
LOG(GLOBAL, LOG_ALL, 1,
"get_timer_frequency: OS is using RTC! Reported speed is bogus.\n");
}
return processor_speed;
}
uint
os_random_seed()
{
LARGE_INTEGER tsc_or_rtc;
uint seed = (uint)d_r_get_thread_id();
seed ^= (uint)query_time_millis();
* if TSC is not available, either one is good enough for randomness
*/
nt_query_performance_counter(&tsc_or_rtc, NULL);
seed ^= tsc_or_rtc.LowPart;
seed ^= tsc_or_rtc.HighPart;
LOG(GLOBAL, LOG_ALL, 1, "os_random_seed: %d\n", seed);
return seed;
}
void
early_inject_init()
{
dcontext_t *dcontext = get_thread_private_dcontext();
module_handle_t mod;
bool under_dr_save;
dr_where_am_i_t whereami_save;
wchar_t buf[MAX_PATH];
int os_version_number = get_os_version();
GET_NTDLL(LdrLoadDll,
(IN PCWSTR PathToFile OPTIONAL, IN PULONG Flags OPTIONAL,
IN PUNICODE_STRING ModuleFileName, OUT PHANDLE ModuleHandle));
ASSERT(dcontext != NULL);
early_inject_location = DYNAMO_OPTION(early_inject_location);
if (DYNAMO_OPTION(early_inject_location) == INJECT_LOCATION_LdrCustom) {
early_inject_address = (app_pc)DYNAMO_OPTION(early_inject_address);
ASSERT(early_inject_address != NULL);
LOG(GLOBAL, LOG_TOP, 1,
"early_inject using option provided address " PFX " at location %d\n",
early_inject_address, early_inject_location);
return;
}
if (!INJECT_LOCATION_IS_LDR(early_inject_location)) {
LOG(GLOBAL, LOG_TOP, 1,
"early_inject is using location %d, no need to find address\n",
early_inject_location);
return;
}
* LdrpLoadImportModule check in options.c */
if (DYNAMO_OPTION(early_inject_location) == INJECT_LOCATION_LdrDefault) {
LOG(GLOBAL, LOG_TOP, 2,
"early_inject using default ldr location for this os_ver\n");
switch (os_version_number) {
case WINDOWS_VERSION_NT:
* automatically since one of the stack frames we need to walk
* for it doesn't use frame ptrs (we can get LdrpLoadDll though),
* LdrpLoadDll seems to work fairly well, but won't get us in til
* after some of the static dlls are loaded. */
* the presumption they're providing LdrpLoadImportModule for us.
*/
if (DYNAMO_OPTION(early_inject_address != 0)) {
early_inject_address = (app_pc)DYNAMO_OPTION(early_inject_address);
LOG(GLOBAL, LOG_TOP, 1,
"early_inject using option provided address " PFX " at location %d\n",
early_inject_address, early_inject_location);
return;
}
* while on others it doesn't. Just turn off early injection
* on NT for now (LdrpLoadDll wasn't giving very good aslr support
* anyways and isn't a desktop target). FIXME - we could just
* hardcode a table of LdrpLoadImportModule addresses for NT since
* we don't expect Microsoft to release any more patches for it.
*/
options_make_writable();
dynamo_options.early_inject = false;
options_restore_readonly();
return;
case WINDOWS_VERSION_2000:
* get us in til after most of the static dlls are loaded */
early_inject_location = INJECT_LOCATION_LdrpLoadImportModule;
;
break;
case WINDOWS_VERSION_XP:
* misses the load of kernel32 */
early_inject_location = INJECT_LOCATION_LdrpLoadDll;
break;
case WINDOWS_VERSION_2003:
case WINDOWS_VERSION_VISTA:
case WINDOWS_VERSION_7:
case WINDOWS_VERSION_8:
case WINDOWS_VERSION_8_1:
case WINDOWS_VERSION_10:
case WINDOWS_VERSION_10_1511:
case WINDOWS_VERSION_10_1607:
case WINDOWS_VERSION_10_1703:
case WINDOWS_VERSION_10_1709:
case WINDOWS_VERSION_10_1803:
* (XXX: would it be better just to use that so matches XP?),
* LdrpLoadImportModule also works but it misses the load of
* kernel32.
*/
early_inject_location = INJECT_LOCATION_LdrLoadDll;
break;
default:
* most likely to work
*/
early_inject_location = INJECT_LOCATION_LdrLoadDll;
ASSERT(os_version_number > WINDOWS_VERSION_10);
}
}
ASSERT(early_inject_location != INJECT_LOCATION_LdrDefault);
LOG(GLOBAL, LOG_TOP, 1, "early_inject is using location %d, finding address\n",
early_inject_location);
if (dr_early_injected && INJECT_LOCATION_IS_LDR_NON_DEFAULT(early_inject_location) &&
early_inject_location == dr_early_injected_location
&& !(INTERNAL_OPTION(early_inject_stress_helpers) &&
early_inject_location == INJECT_LOCATION_LdrpLoadImportModule)) {
early_inject_address = parent_early_inject_address;
ASSERT(early_inject_address != NULL);
ASSERT(early_inject_location != INJECT_LOCATION_LdrLoadDll ||
early_inject_address == (app_pc)LdrLoadDll);
LOG(GLOBAL, LOG_TOP, 1, "early_inject using parent supplied address " PFX "\n",
early_inject_address);
return;
}
switch (early_inject_location) {
case INJECT_LOCATION_LdrLoadDll: early_inject_address = (app_pc)LdrLoadDll; break;
case INJECT_LOCATION_LdrpLoadDll:
* from parent as our DllMain stack walk will have gotten the wrong
* locations (during process init the Ldr delays calling DllMains
* until all static dlls are loaded unless GetProcAddress is called
* on the dll first, in that case its DllMain is called from there
* not LdrpLoadDll as we expect). */
* when early_injected for the same reason dr's DllMain walk doesn't.
* Maybe there's some flag we can pass to the Ldr to tell it to call
* the DllMain right away (could then use it when trampoline loads
* dr dll). Other option is we could wait and use the helper dll
* once the Ldr is in a state where it will do what we expect
* (the image entry point would qualify, though we could prob. find
* somewhere earlier then that, say when we see the execution of the
* DllMain of one of the non ntdll system dlls or something). That
* said in the product I expect any given platform (let alone machine)
* to always use the same inject location. */
ASSERT_NOT_IMPLEMENTED(!dr_early_injected &&
"process early injected"
"at non LdrpLoadDll location is configured to"
"use LdrpLoadDll location which is NYI");
if (os_version_number == WINDOWS_VERSION_NT)
early_inject_address = ldrpLoadDll_address_NT;
else
early_inject_address = ldrpLoadDll_address_not_NT;
break;
case INJECT_LOCATION_LdrpLoadImportModule:
* to be a native_exec thread and load drearlyhelper1.dll which
* statically links to drearlyhelper2.dll. We watch for the
* NtMapViewOfSection call that loads drearlyhelper2.dll in
* syscall_while_native. At that point we expect the stack to look
* like this:
* (in NtMapViewOfSection)
* ntdll!LdrpMapDll
* ntdll!LdrpLoadImportModule (what we want)
* After that don't really care (is one of the Ldrp*ImportDescriptor*
* routines. So we walk the stack back and get the desired address.
*/
ASSERT(DYNAMO_OPTION(native_exec_syscalls));
LOG(GLOBAL, LOG_ALL, 1,
"early_inject using helper dlls to find LdrpLoadImportModule\n");
* since we're still pre dynamo_initialized no other threads can be
* running in dr code (so we're ok with the synch routines which could
* otherwise be a problem since we're still on the appstack at this
* point so could pass at_safe_spot while we were native). Hotpatch
* nudge dll loading does the same trick. This does assume that,
* like hotpatch nudge, we aren't running on the dstack as that
* will be clobbered. Alt. we could remove the KSTATS issue and
* the stack restriction by special casing this thread in
* syscall_while_native (just let all system calls run natively except
* MapViewOfSection which we do there so we can check the result). */
ASSERT(!is_currently_on_dstack(dcontext));
under_dr_save = dcontext->thread_record->under_dynamo_control;
dcontext->thread_record->under_dynamo_control = false;
whereami_save = dcontext->whereami;
* with d_r_dispatch for processing the native exec syscalls we'll hit
* while loading the helper dll (hotpatch has a similar issue but
* lucks out with having a compatible stack). Shouldn't mess things
* up too much though. We do have to use non-matching stops so not
* sure how accurate these times will be (should be tiny anyways)
* should poke around d_r_dispatch sometime and figure out some way to
* do this nicer. */
KSTART(dispatch_num_exits);
KSTART(dispatch_num_exits);
string_option_read_lock();
snwprintf(buf, BUFFER_SIZE_ELEMENTS(buf), L"%hs",
DYNAMO_OPTION(early_inject_helper_dll));
NULL_TERMINATE_BUFFER(buf);
string_option_read_unlock();
* ldrpLoadImportModule_address for us */
early_inject_load_helper_dcontext = dcontext;
* of the helper dlls don't exist/can't be found the Ldr treats
* that as a process init failure and aborts the process. Wonder if
* there's a flag we can pass to the Ldr to tell it not to do that.
* Anyways, in normal usage we expect to use the parent's address when
* early_injected (would only fail to do so if the parent was using
* a different inject_location which would be unexpected in a product
* configuration). */
EXITING_DR();
* load_library() & free_library() routines themselves don't write to
* any self protected regions, if changes we may need special versions
* here. */
mod = load_library(buf);
if (mod != NULL) {
free_library(mod);
}
ENTERING_DR();
dcontext->whereami = whereami_save;
early_inject_load_helper_dcontext = NULL;
dcontext->thread_record->under_dynamo_control = under_dr_save;
KSTOP_NOT_MATCHING_NOT_PROPAGATED(dispatch_num_exits);
KSTOP_NOT_PROPAGATED(dispatch_num_exits);
ASSERT(mod != NULL && ldrpLoadImportModule_address != NULL &&
"check that drearlyhelp*.dlls are installed");
* reason (most likely would be the helper dlls didn't exist/couldn't
* be found)? Could choose to fall back to another os version
* appropriate location. As is, in release build we'd just fail to
* follow children when we couldn't find the address (see FIXME in
* inject_into_process()). I expect QA is going to run into this
* occasionally (esp. till nodemgr etc. handle the helper dlls), so
* can we do anything to make things easier/more apparent for them? */
early_inject_address = ldrpLoadImportModule_address;
break;
default: ASSERT_NOT_REACHED();
}
* injected, we could fall back to parent's address. */
ASSERT(early_inject_address != NULL);
* early_inject_address is in ntdll */
ASSERT(get_allocation_base(early_inject_address) == get_ntdll_base());
LOG(GLOBAL, LOG_TOP, 1, "early_inject found address " PFX " to use\n",
early_inject_address);
}
* DR is not initialized at all so be careful what you call here.
*/
bool
earliest_inject_init(byte *arg_ptr)
{
earliest_args_t *args = (earliest_args_t *)arg_ptr;
if (!privload_bootstrap_dynamorio_imports((byte *)(ptr_int_t)args->dr_base,
(byte *)(ptr_int_t)args->ntdll_base)) {
* should we just silently go native?
*/
} else {
uint old_prot;
if (!bootstrap_protect_virtual_memory((byte *)(ptr_int_t)args->hook_location,
EARLY_INJECT_HOOK_SIZE, args->hook_prot,
&old_prot)) {
}
}
set_ntdll_base((byte *)(ptr_int_t)args->ntdll_base);
* it's initialized so we pass it in from parent.
* Imports are set up so we can call strncpy now.
*/
strncpy(dynamorio_library_path, args->dynamorio_lib_path,
BUFFER_SIZE_ELEMENTS(dynamorio_library_path));
NULL_TERMINATE_BUFFER(dynamorio_library_path);
* "for apc early hook, need special handling in callback.c to replace
* the early hook and then touch up the hook code to handle any queued
* up threads (and be finally early remote thread safe)."
* which implies the hook should have 1st thread invoke DR and the others
* spin in some fashion: for now not handling super-early threads
*/
return !args->late_injection;
}
void
earliest_inject_cleanup(byte *arg_ptr)
{
earliest_args_t *args = (earliest_args_t *)arg_ptr;
byte *tofree = (byte *)(ptr_int_t)args->tofree_base;
NTSTATUS res;
* We could free this in earliest_inject_init() via adding
* bootstrap_free_virtual_memory() but in case we need to add
* more cleanup later, going this route.
*/
LOG(GLOBAL, LOG_ALL, 1, "freeing early inject args @" PFX "\n", tofree);
res = nt_remote_free_virtual_memory(NT_CURRENT_PROCESS, tofree);
ASSERT(NT_SUCCESS(res));
}
# define SECURITY_MAX_SID_STRING_SIZE \
(2 + MAX_DWORD_STRING_LENGTH + 1 + MAX_DWORD_STRING_LENGTH + \
(MAX_DWORD_STRING_LENGTH * SID_MAX_SUB_AUTHORITIES) + 1)
static const char *
get_process_SID_string()
{
static char process_SID[SECURITY_MAX_SID_STRING_SIZE];
if (!process_SID[0]) {
wchar_t sid_string[SECURITY_MAX_SID_STRING_SIZE];
* assume that this function is called early enough before any
* impersonation could have taken place and NtOpenThreadToken
*/
get_current_user_SID(sid_string, sizeof(sid_string));
snprintf(process_SID, BUFFER_SIZE_ELEMENTS(process_SID), "%ls", sid_string);
NULL_TERMINATE_BUFFER(process_SID);
}
return process_SID;
}
static const PSID
get_Everyone_SID()
{
static PSID everyone_SID = NULL;
static UCHAR everyone_buf[LengthRequiredSID(1)];
if (everyone_SID == NULL) {
SID_IDENTIFIER_AUTHORITY world = SECURITY_WORLD_SID_AUTHORITY;
everyone_SID = (PSID)everyone_buf;
initialize_known_SID(&world, SECURITY_WORLD_RID, everyone_SID);
}
return everyone_SID;
}
static const PSID
get_process_owner_SID()
{
static PSID owner_SID = NULL;
static UCHAR owner_buf[SECURITY_MAX_SID_SIZE + sizeof(TOKEN_OWNER)];
if (owner_SID == NULL) {
PTOKEN_OWNER powner = (PTOKEN_OWNER)owner_buf;
NTSTATUS res;
ASSERT(!dynamo_initialized);
res = get_primary_owner_token(powner, sizeof(owner_buf));
ASSERT(NT_SUCCESS(res));
if (!NT_SUCCESS(res)) {
* all the time, otherwise we'll crash later when writing
* to owner_buf */
return NULL;
}
owner_SID = powner->Owner;
}
return owner_SID;
}
static bool
os_validate_owner_equals(HANDLE file_or_directory_handle, PSID expected_owner)
{
*
* Note that we only allow files used by initial process, so we
* must memoize initial SID.
*/
* a directory or file to pretend to be created by the victim
* - we can only ask a trusted component to create a directory
* writable only by the corresponding user. On Windows,
* however, chown() requries restore or TCB privileges.
* therefore it doesn't present a privilege escalation route.
*/
* then we'd have to verify it wasn't created by somebody else -
* after we open a file we should validate that we are its
* rightful owner (and we'll assume we have maintained the correct
* ACLs) to maintain that nobody else could have had write access
* to the file.
*/
* created files' owner will be the current user (in addition to
* being readable by the current user). We also assume that the
* cache\ directory is on the local system.
* FIXME: case 10884 we can't assume that, we have to create our files explicitly
*
* (FIXME: unclear whether Machine account will be available for
* us on the network for services)
*/
* concatenating strings would allow us to do the check only on
* the directory, and not on the files. We only need to make sure
* there are no TOCTOU races: no symbolic links allowed, and
* that directories cannot be renamed or deleted.
*/
UCHAR sd_buf[SECURITY_MAX_SID_SIZE + sizeof(SECURITY_DESCRIPTOR)];
PSECURITY_DESCRIPTOR sd = (PSECURITY_DESCRIPTOR)sd_buf;
PSID owner;
DWORD actual_sd_length;
NTSTATUS res;
ASSERT(ALIGNED(sd, sizeof(DWORD)));
* for a complete SD. We need to ensure that only one SID would
* be returned to us here.
*/
res = nt_query_security_object(file_or_directory_handle, OWNER_SECURITY_INFORMATION,
sd, sizeof(sd_buf), &actual_sd_length);
if (!NT_SUCCESS(res)) {
if (res == STATUS_ACCESS_DENIED) {
ASSERT_CURIOSITY(false && "verify handle allows READ_CONTROL");
}
return false;
}
ASSERT(actual_sd_length < sizeof(sd_buf));
if (get_owner_sd(sd, &owner)) {
* obtaining the SID that we can use for creating files */
if (!equal_sid(owner, expected_owner)) {
LOG(GLOBAL, LOG_TOP, 1,
"os_validate_owner_equals: owner not matching expected_owner\n");
return false;
}
return true;
}
ASSERT_NOT_REACHED();
return false;
}
* guaranteed to not be writable.
*/
bool
os_filesystem_supports_ownership(HANDLE file_or_directory_handle)
{
* Everyone - which certainly should only happen on FAT32.
*/
* ID/reference, since creation by file reference is only
* supported on NTFS
*/
if (os_validate_owner_equals(file_or_directory_handle, get_Everyone_SID())) {
* 0:000> !sid poi(owner)
* SID is: S-1-1-0 Everyone
*
* We assume that a malicious user cannot set the SID to
* Everyone. Although Everyone is not the same as Anonymous
* Logon S-1-5-7, just in case malware can run as Everyone and
* creates a file we cannot decide we're on FAT32 just based
* on this for files that.
*/
SYSLOG_INTERNAL_WARNING_ONCE("cache root directory is on FAT32, no security\n");
return false;
} else {
return true;
}
return false;
}
HANDLE
open_trusted_cache_root_directory(void)
{
char base_directory[MAXIMUM_PATH];
wchar_t wbuf[MAXIMUM_PATH];
HANDLE directory_handle;
bool param_ok = false;
if (DYNAMO_OPTION(aslr) != 0 || DYNAMO_OPTION(aslr_cache) != 0) {
int retval =
d_r_get_parameter(PARAM_STR(DYNAMORIO_VAR_CACHE_ROOT), base_directory,
BUFFER_SIZE_ELEMENTS(base_directory));
param_ok = !IS_GET_PARAMETER_FAILURE(retval);
} else {
ASSERT(strcmp(DYNAMORIO_VAR_CACHE_ROOT, DYNAMORIO_VAR_PERSCACHE_ROOT) == 0);
param_ok =
perscache_dirname(base_directory, BUFFER_SIZE_ELEMENTS(base_directory));
}
if (!param_ok || double_strchr(base_directory, DIRSEP, ALT_DIRSEP) == NULL) {
# ifndef STATIC_LIBRARY
SYSLOG_INTERNAL_WARNING("%s not correctly set!", DYNAMORIO_VAR_CACHE_ROOT);
# endif
return INVALID_HANDLE_VALUE;
}
NULL_TERMINATE_BUFFER(base_directory);
if (!convert_to_NT_file_path(wbuf, base_directory, BUFFER_SIZE_ELEMENTS(wbuf)))
return INVALID_HANDLE_VALUE;
* and owned by Administrators, and the directory ACL should not
* allow changes. We should not create one if it doesn't exist,
* even if we did we wouldn't have the correct ACLs for its
* children.
*/
directory_handle =
create_file(wbuf, true , READ_CONTROL ,
FILE_SHARE_READ
* in same directory */
| FILE_SHARE_WRITE,
FILE_OPEN, true);
if (directory_handle == INVALID_HANDLE_VALUE) {
SYSLOG_INTERNAL_ERROR("%s=%s is invalid!", DYNAMORIO_VAR_CACHE_ROOT,
base_directory);
}
return directory_handle;
}
bool
os_user_directory_supports_ownership()
{
static int user_directory_has_ownership = -1;
if (user_directory_has_ownership < 0) {
if (DYNAMO_OPTION(validate_owner_dir) || DYNAMO_OPTION(validate_owner_file)) {
HANDLE root_handle = open_trusted_cache_root_directory();
* don't care about user_directory_has_ownership, it is
* undefined. Since all users that verify ownership
* construct paths based on this directory, they should
* all fail and we don't really care. We assume that this
* directory is created with correct privileges, so if
* anyone controls the registry key or can create the
* directory we have lost already. (Interestingly,
* nt_query_security_object() returns current user for
* owner of -1, and so os_filesystem_supports_ownership()
* does return true instead.)
*/
if (os_filesystem_supports_ownership(root_handle))
user_directory_has_ownership = 1;
else
user_directory_has_ownership = 0;
close_handle(root_handle);
} else {
user_directory_has_ownership = 0;
}
}
return (user_directory_has_ownership == 1);
}
* DYNAMORIO_VAR_CACHE_ROOT.
* Handle needs to have READ_CONTROL access (FILE_GENERIC_READ provides that).
*/
bool
os_validate_user_owned(HANDLE file_or_directory_handle)
{
* that we'll be creating new files with current token as owner
*/
PSID process_SID = get_process_primary_SID();
* we also need ACLs allowing us to open other files created by
* the primary token */
if (os_validate_owner_equals(file_or_directory_handle, process_SID)) {
return true;
}
if (!os_user_directory_supports_ownership()) {
* get as owner Everyone. Since file ACLs are unsupported by
* file system on the system drive (where we install), we can
* assume that privilege escalation is irrelevant for this
* host.
*/
ASSERT(os_validate_owner_equals(file_or_directory_handle, get_Everyone_SID()));
return true;
}
ASSERT_CURIOSITY(false && "unauthorized user tried to forge our files");
return false;
}
* and optionally create a new one if possible
*
* Note 'current' is actually the primary process token: we currently
* allow only read-only access for impersonated threads.
*/
bool
os_current_user_directory(char *directory_prefix , uint directory_len,
bool create)
{
char *directory = directory_prefix;
char *dirend = directory_prefix + strlen(directory_prefix);
snprintf(dirend, directory_len - (dirend - directory_prefix), "%c%s", DIRSEP,
get_process_SID_string());
directory_prefix[directory_len - 1] = '\0';
directory = directory_prefix;
LOG(GLOBAL, LOG_CACHE, 2, "\tper-user dir is %s\n", directory);
DODEBUG({
if (!equal_sid(get_process_owner_SID(), get_process_primary_SID())) {
LOG(GLOBAL, LOG_CACHE, 1,
"Default owner is not current user, we must be an Administrator?\n");
}
});
* single application will be spread across different users secure
* storage locations. This may be a vulnerability - if a secure
* server loads a DLL while impersonated we may be erroneously
* using (without validation) a DLL controlled by lower privilege.
* Delay-loaded DLLs may provide such unexpected DLL loads.
*
* ACLs: We may want to leave files readable by Everyone - allows
* any impersonated threads to read files in the directory of the
* original process token. (Note that Anonymous token belongs to
* Everyone). World readable files also allow us to share files
* produced by TCB services. Yet, for stronger security against
* local privilege exploits, there is some value in not allowing
* anyone else to read our persistent files - the layout may be
* useful to attackers; and general need to know principle:
* normally other process don't need to read these.
*/
* impersonation at all, but we should try to detect it here if we
* fail to open a directory due to impersonated thread.
*/
if (!os_file_exists(directory, true ) && create) {
* the default owner doesn't have to be the current user, if
* member of Administrators. Therefore we specify our own
* SecurityDescriptor.Owner when creating a file so that we
* don't use SE_OWNER_DEFAULTED, but we still want a default
* DACL and we don't care about group.
*/
if (!os_create_dir(directory, CREATE_DIR_REQUIRE_NEW | CREATE_DIR_FORCE_OWNER)) {
LOG(GLOBAL, LOG_CACHE, 2, "\terror creating per-user dir %s\n", directory);
* run correctly without persistence. */
SYSLOG_INTERNAL_ERROR_ONCE("Persistent cache per-user needed.\n"
"mkdir \"%s\"\n"
"cacls \"%s\" /E /G username:F",
* name, while subinacl does take SIDs */
directory, directory);
return false;
} else {
LOG(GLOBAL, LOG_CACHE, 2, "\tcreated per-user dir %s\n", directory);
}
}
* setup accordingly we should be able to automatically create a
* our own per-user folder, without dealing with forging ACLs
* here, and without asking a trusted components to create it for
* us.
* currently each user MUST call os_validate_user_owned()
* before trusting a file, or if a directory handle is
* guaranteed to be open at all times such that renaming is
* disallowed, then only the directory needs to be validated
*/
return true;
}
* modified the value of any options to make them compatible
*/
bool
os_check_option_compatibility(void)
{
bool changed_options = false;
bool os_has_aslr = get_os_version() >= (int)INTERNAL_OPTION(os_aslr_version);
* WINDOWS_VERSION_VISTA */
if (!os_has_aslr)
return false;
if (TEST(OS_ASLR_DISABLE_PCACHE_ALL, DYNAMO_OPTION(os_aslr))) {
if (DYNAMO_OPTION(coarse_enable_freeze)) {
dynamo_options.coarse_enable_freeze = false;
changed_options = true;
}
if (DYNAMO_OPTION(coarse_freeze_at_unload)) {
dynamo_options.coarse_freeze_at_unload = false;
changed_options = true;
}
if (DYNAMO_OPTION(use_persisted)) {
dynamo_options.use_persisted = false;
changed_options = true;
}
if (changed_options)
SYSLOG_INTERNAL_WARNING_ONCE("pcache completely disabled, Vista+");
}
* using our own -aslr_dr */
if (TEST(OS_ASLR_DISABLE_PCACHE_ALL, DYNAMO_OPTION(os_aslr))) {
if (DYNAMO_OPTION(aslr) != 0) {
dynamo_options.aslr = 0;
changed_options = true;
SYSLOG_INTERNAL_WARNING_ONCE("ASLR completely disabled, Vista+");
}
if (DYNAMO_OPTION(aslr_cache) != 0) {
dynamo_options.aslr_cache = 0;
changed_options = true;
}
}
ASSERT(os_has_aslr);
return changed_options;
}
#endif
size_t
os_page_size(void)
{
return 4096;
}