blob: cf8136a0caae776f8356b567e1faf8b8309e4b69 [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2010-2014 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.
*/
/* Copyright (c) 2003-2007 Determina Corp. */
/* Copyright (c) 2001-2003 Massachusetts Institute of Technology */
/* Copyright (c) 2000-2001 Hewlett-Packard Company */
/*
* 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" /* is_in_client_lib() */
#include <windows.h>
#include <stddef.h> /* for offsetof */
#include "events.h" /* event log messages */
#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) /* nothing */
# define ASSERT_NOT_IMPLEMENTED(x) /* nothing */
# define ASSERT_NOT_TESTED(x) /* nothing */
# define ASSERT_CURIOSITY_ONCE(x) /* nothing */
# undef LOG
# define LOG(x, ...) /* nothing */
#else /* !NOT_DYNAMORIO_CORE_PROPER: around most of file, to exclude preload */
#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));
/* PR 601533: cleanup_and_terminate must release the initstack_mutex
* 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 /* 2 args + retaddr of call to win8 wrapper */
/* 1 for call + 1 extra + setup */
#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});
/* We point it several stack slots in for win8 setup */
const byte *wow64_syscall_stack =
&wow64_syscall_stack_array[WOW64_SYSCALL_STACK_SIZE - WOW64_SYSCALL_SETUP_SIZE];
/* globals */
bool intercept_asynch = false;
bool intercept_callbacks = false;
/* we store here to enable TEB.ClientId.ProcessHandle as a spill slot */
process_id_t win32_pid = 0;
/* we store here to enable TEB.ProcessEnvironmentBlock as a spill slot */
void *peb_ptr;
static int os_version;
static uint os_service_pack_major;
static uint os_service_pack_minor;
static const char *os_name;
app_pc vsyscall_page_start = NULL;
/* pc kernel will claim app is at while in syscall */
app_pc vsyscall_after_syscall = NULL;
/* pc of the end of the syscall instr itself */
app_pc vsyscall_syscall_end_pc = NULL;
/* atomic variable to prevent multiple threads from trying to detach at the same time */
DECLARE_CXTSWPROT_VAR(static volatile int dynamo_detaching_flag, LOCK_FREE_STATE);
#ifdef PROFILE_RDTSC
uint kilo_hertz; /* cpu clock speed */
#endif
#define HEAP_INITIAL_SIZE 1024*1024
/* pc values delimiting dynamo dll image */
app_pc dynamo_dll_start = NULL;
app_pc dynamo_dll_end = NULL; /* open-ended */
/* needed for randomizing DR library location */
static app_pc dynamo_dll_preferred_base = NULL;
/* thread-local storage slots */
enum {TLS_UNINITIALIZED = (ushort) 0U};
static ushort tls_local_state_offs = TLS_UNINITIALIZED;
/* we keep this cached for easy asm access */
static ushort tls_dcontext_offs = TLS_UNINITIALIZED;
/* used for early inject */
app_pc parent_early_inject_address = NULL; /* dynamo.c fills in */
/* note that this is the early inject location we'll use for child processes
* 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];
/* forwards */
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);
/* Safely gets the target of the call (assumed to be direct) to the nth stack
* 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;
/* walk up the stack */
for (i = 0; i < num_frames; i++) {
if (!safe_read(cur_ebp, sizeof(next_frame), next_frame))
break;
cur_ebp = (reg_t *)next_frame[0];
}
if (i == num_frames) {
/* success walking frames, return address should be the after
* call address of the call that targeted this frame */
/* FIXME - would be nice to get this with decode_cti, but dr might
* not even be initialized yet and this is safer */
byte buf[5]; /* sizeof call rel32 */
if (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;
}
/* Should be called from NtMapViewOfSection interception with *base
* 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)) {
/* loaded a module, check name */
const char *name;
bool match = false;
name = get_dll_short_name(base); /* we only need pe name */
if (name != NULL) {
LOG(GLOBAL, LOG_TOP, 1,
"early_inject hit mapview of image %s\n", name);
string_option_read_lock();
/* we control both the pe_name and the option value so use
* strcmp (vs. strcasecmp), just to keep things tight */
match =
strcmp(DYNAMO_OPTION(early_inject_helper_name),
name) == 0;
string_option_read_unlock();
}
if (match) {
/* Found it. 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.
* 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
/* we have interp not inline calls to this routine */
void
DllMainThreadAttach()
{
if (INTERNAL_OPTION(noasynch) && dynamo_initialized && !dynamo_exited) {
/* we normally intercept thread creation in callback.c, but with
* 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", get_thread_id());
dynamo_thread_init(NULL, NULL _IF_CLIENT_INTERFACE(false));
}
}
#endif
/* Hand-made DO_ONCE since DllMain is executed prior to DR init */
DECLARE_FREQPROT_VAR(static bool do_once_DllMain, false);
/* DLL entry point
* N.B.: dynamo interprets this routine!
*/
/* get_nth_stack_frames_call_target() assumes that DllMain has a frame pointer
* so we cannot optimize it (i#566)
*/
START_DO_NOT_OPTIMIZE
bool WINAPI /* get link warning 4216 if export via APIENTRY */
DllMain(HANDLE hModule, DWORD reason_for_call, LPVOID Reserved)
{
switch (reason_for_call) {
case DLL_PROCESS_ATTACH:
/* case 8097: with -no_hide, DllMain will be called a second time
* 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);
/* Carefully walk stack to find address of LdrpLoadDll. */
/* Remember dr isn't initialized yet, no need to worry about
* protect from app etc., but also can't check options. */
GET_FRAME_PTR(cur_ebp);
/* For non early_inject (late follow children, preinject) expect
* 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
/* Since dr isn't initialized yet we can't call get_os_version()
* 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);
/* FIXME - would be nice to have extra verification here,
* 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
/* we don't care about DLL_PROCESS_DETACH or DLL_THREAD_DETACH */
}
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;
/* FIXME: we should just call get_allocation_size() */
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;
/* create the profile file */
/* if logging is on create in log directory, else use base directory */
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);
/* Case 7533: put basic run info in profile file. */
print_version_and_app_info(profile_file);
/* set the interval, don't assert success, on my desktop anything less than
* 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());
/* create profiles */
/* Default shift of 30 gives 4 buckets for the global profile,
* 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 profiles */
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);
/* we expect to be the last thread at this point.
FIXME: we can remove the mutex_lock/unlock then */
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);
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)
{
/* i#437: AVX is supported on Windows 7 SP1 and Windows Server 2008 R2 SP1
* win7sp1+ both should be 0x40.
*/
if (YMM_ENABLED()) {
/* YMM_ENABLED indicates both OS and processor support (i#1278)
* 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));
}
/* FIXME: Right now error reporting will work here, but once we have our
* 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 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()
{
PEB *peb = get_own_peb();
/* choose appropriate syscall array (the syscall numbers change from
* one version of windows to the next!
* they may even change at different patch levels)
*/
syscalls = NULL;
DOCHECK(1, { check_syscall_array_sizes(); });
/* In at least 2K, XP, XP64, Vista, and Win7, the service pack is
* stored in peb->OSCSDVersion, major in the top byte:
*/
os_service_pack_major = (peb->OSCSDVersion & 0xff00) >> 8;
os_service_pack_minor = (peb->OSCSDVersion & 0xff);
if (peb->OSPlatformId == VER_PLATFORM_WIN32_NT) {
/* WinNT or descendents */
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();
/* i#437: ymm/avx is supported after Win-7 SP1 */
if (os_service_pack_major >= 1) {
/* Sanity check on our SP ver retrieval */
ASSERT(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(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) {
/* Vista system call number differ between service packs, we use
* the existence of NtReplacePartitionUnit as a sanity check
* for sp1 - see PR 246402. They also differ for
* 32-bit vs 64-bit/wow64.
*/
ASSERT(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(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) {
/* Version 5.2 can mean 32- or 64-bit 2003, or 64-bit XP */
/* Assumption: get_ntll_base makes no system calls */
if (module_is_64bit(get_ntdll_base()) ||
is_wow64_process(NT_CURRENT_PROCESS)) {
/* We expect x64 2003 and x64 XP to have the same system call
* 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;
/* We don't yet have need to split the version enum */
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;
/* NT4 syscalls differ among service packs.
* 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 (get_proc_address(ntdllh, "NtW32Call") != NULL) {
/* < SP4 */
/* we don't know whether SP1 and SP2 fall in line w/ SP0 or w/ SP3,
* or possibly are different from both, but we don't support them
*/
if (get_proc_address(ntdllh, "NtReadFileScatter") != NULL) {
/* > SP0 */
syscalls = (int *) windows_NT_sp3_syscalls;
os_name = "Microsoft Windows NT SP3";
} else {
/* < SP3 */
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";
}
} else {
SYSLOG_INTERNAL_ERROR("Unknown Windows NT-family version: major=%d, minor=%d\n",
peb->OSMajorVersion, peb->OSMinorVersion);
if (standalone_library)
return false; /* let app handle it */
os_name = "Unrecognized Windows NT-family version";
FATAL_USAGE_ERROR(BAD_OS_VERSION, 4, get_application_name(),
get_application_pid(), PRODUCT_NAME, os_name);
}
} else if (peb->OSPlatformId == VER_PLATFORM_WIN32_WINDOWS) {
/* Win95 or Win98 */
uint ver_high = (peb->OSBuildNumber >> 8) & 0xff;
uint ver_low = peb->OSBuildNumber & 0xff;
if (standalone_library)
return false; /* let app handle it */
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; /* let app handle it */
os_name = "Win32s";
/* Win32S on Windows 3.1 */
FATAL_USAGE_ERROR(BAD_OS_VERSION, 4, get_application_name(),
get_application_pid(), PRODUCT_NAME, os_name);
}
return true;
}
/* Note that assigning a process to a Job is done only after it has
* 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);
/* 4GB for unlimited */
LOG(GLOBAL, LOG_TOP, 1, "\t Pagefile limit: %6d KB\n",
qlimits.PagefileLimit/1024);
/* TimeLimit not supported on Win2k, but WSRM (Windows System
* 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);
}
/* os-specific initializations */
void
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);
}
/* make sure we create the message box title string before we are
* 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
/* We no longer rely on peb64 being adjacent to peb for i#816 but
* let's print it nonetheless
*/
DOLOG(1, LOG_TOP, {
if (is_wow64_process(NT_CURRENT_PROCESS)) {
ptr_uint_t peb64 = (ptr_uint_t) get_own_x64_peb();
LOG(GLOBAL, LOG_TOP, 1, "x64 PEB: "PFX"\n", peb64);
}
});
#endif
/* match enums in os_exports.h with TEB definition from ntdll.h */
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));
/* windows_version_init should have already been called */
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);
/* i#437, i#1278: get the context_xstate after proc_init() sets proc_avx_enabled() */
context_xstate = get_context_xstate_flag();
ntdll_init();
callback_init();
eventlog_init(); /* os dependent and currently Windows specific */
if (os_version >= WINDOWS_VERSION_XP) {
/* FIXME: bootstrapping problem where we see 0x7ffe0300 before we see
* 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.
*/
/* on XP service pack 2 the syscall enter and exit stubs are Ki
* 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.)
*/
/* initializing so get_module_handle should be safe, FIXME */
module_handle_t ntdllh = get_ntdll_base();
app_pc return_point = (app_pc) get_proc_address(ntdllh,
"KiFastSystemCallRet");
if (return_point != NULL) {
app_pc syscall_pc = (app_pc) get_proc_address(ntdllh,
"KiFastSystemCall");
vsyscall_after_syscall = (app_pc) return_point;
/* we'll re-set this once we see the 1st syscall, but we set an
* 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; /* wait until 1st one */
} else {
/* FIXME : if INT syscalls are being used then this opens up a
* 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;
}
}
/* TLS alignment use either preferred on processor, or hardcoded option value */
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);
}
/* case 3701 about performance gains,
* and case 6670 about TLS conflict in SQL2005 */
/* FIXME: could control which entry should be cache aligned, but
* 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.
*/
/* allocate thread-private storage */
res = tls_calloc(false/*no synch required*/, &offs, TLS_NUM_SLOTS,
alignment);
DODEBUG({
/* FIXME: elevate failure here to a release-build syslog? */
if (!res)
SYSLOG_INTERNAL_ERROR("Cannot allocate %d tls slots at %d alignment", TLS_NUM_SLOTS,
alignment);
});
/* retry with no alignment on failure */
if (!res) {
alignment = 0;
ASSERT_NOT_TESTED();
/* allocate thread-private storage with no alignment */
res = tls_calloc(false/*no synch required*/, &offs, TLS_NUM_SLOTS,
alignment);
/* report even in release build that we really can't grab in TLS64 */
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();
/* just to preserve side effects. If not done yet in eventlog,
* path needs to be be preserved before hiding from module list.
*/
aslr_init();
/* ensure static cache buffers are primed, both for .data protection purposes and
* 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();
/* avoid later .data-unprotection calls */
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());
os_take_over_init();
/* i#298: cache cur dir at init time, when safer to read it.
* 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);
}
/* os-specific atexit cleanup
* 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)
{
/* make sure we never see an .exe that does all its work in
* 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() ||
RUNNING_WITHOUT_CODE_CACHE()
IF_APP_EXPORTS( || dr_api_entry)
/* Clients can go native. XXX: add var for whether client did? */
IF_CLIENT_INTERFACE
(|| !IS_INTERNAL_STRING_OPTION_EMPTY(client_lib)));
DOLOG(1, LOG_TOP, { print_mem_quota(); });
DOLOG(1, LOG_TOP, {
print_mem_stats();
});
#ifdef WINDOWS_PC_SAMPLE
if (dynamo_options.profile_pcs) {
exit_global_profiles();
/* check to see if we are using the fast exit path
* if so dump profiles that were skipped */
# ifndef DEBUG
if (dynamo_detaching_flag == LOCK_FREE_STATE) {
/* fast exit path, get remaining ungathered profile data */
if (dynamo_options.prof_pcs_gencode >= 2 &&
dynamo_options.prof_pcs_gencode <= 32) {
thread_record_t **threads;
int num, i;
/* get surviving threads */
arch_profile_exit();
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));
mutex_unlock(&thread_initexit_lock);
}
if (dynamo_options.prof_pcs_fcache >= 2 &&
dynamo_options.prof_pcs_fcache <= 32) {
/* note that fcache_exit() is called before os_fast_exit(),
* 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
/* case 10338: we don't free TLS on the fast path, in case there
* 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();
}
/* os-specific atexit cleanup since Detach wants to leave nothing from
* 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)
{
/* free and zero thread-private storage (case 10338: slow path only) */
DEBUG_DECLARE(int res = )
tls_cfree(true/*need to synch*/, (uint) tls_local_state_offs,
TLS_NUM_SLOTS);
ASSERT(res);
aslr_exit();
eventlog_slow_exit();
os_take_over_exit();
}
/* Win8 WOW64 does not point edx at the param base so we must
* 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.
*/
/* Pass INVALID_HANDLE_VALUE for process exit */
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) /* app may have passed bogus handle */
return (byte *) wow64_syscall_stack;
/* We use our scratch slots in the TEB. We need room for syscall
* 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
}
/* Only takes action when edx is not the param base */
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))); /* => atomic writes */
/* skip a slot (natively it's the retaddr from the call to the wrapper) */
*(((reg_t*)xsp)+1) = (reg_t) proc_or_thread_handle;
*(((reg_t*)xsp)+2) = (reg_t) exit_status;
}
#endif
}
/* FIXME: what are good values here? */
#define KILL_PROC_EXIT_STATUS -1
#define KILL_THREAD_EXIT_STATUS -1
/* custom_code only honored if exit_process == true */
static byte *
os_terminate_static_arguments(bool exit_process, bool custom_code, int exit_code)
{
byte *arguments;
/* arguments for NtTerminate{Process,Thread} */
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;
/* It is not safe to use app stack and hope application will work.
* 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.
*/
/* To facilitate detecting syscall failure for SYSENTER, we set a
* 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), /* 0 -> NULL for release */
{NT_CURRENT_THREAD, KILL_THREAD_EXIT_STATUS}
};
static const terminate_args_t term_proc_args = {
IF_DEBUG_ELSE_0((byte *)debug_infinite_loop), /* 0 -> NULL for release */
{NT_CURRENT_PROCESS, KILL_PROC_EXIT_STATUS}
};
/* special sygate froms (non-const) */
static terminate_args_t sygate_term_thread_args = {
0, /* will be set to sysenter_ret_address */
{NT_CURRENT_THREAD, KILL_THREAD_EXIT_STATUS}
};
static terminate_args_t sygate_term_proc_args = {
0, /* will be set to sysenter_ret_address */
{NT_CURRENT_PROCESS, KILL_PROC_EXIT_STATUS}
};
/* for variable exit code */
static terminate_args_t custom_term_proc_args = {
IF_DEBUG_ELSE_0((byte *)debug_infinite_loop), /* 0 -> NULL for release */
{NT_CURRENT_PROCESS, KILL_PROC_EXIT_STATUS}
};
/* for LOG statement just pick proc vs. thread here, will adjust for
* 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;
/* Note we overwrite every time we use this, but is ATOMIC and
* 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;
}
/* dcontext is not needed for TERMINATE_PROCESS, so can pass NULL in
*/
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) != /* xor */
TEST(TERMINATE_THREAD, terminate_type));
/* We could be holding the bb_building_lock at this point -- if we cleanup,
* 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 :)
*/
/* clean up may be dangerous - just terminate */
if (terminate_type == TERMINATE_PROCESS) {
/* skip synchronizing dynamic options, is risky and caller has almost
* certainly already done so for a syslog */
if (TESTANY(DETACH_ON_TERMINATE|DETACH_ON_TERMINATE_NO_CLEAN,
DYNAMO_OPTION(internal_detach_mask))) {
/* FIXME : if we run into stack problems we could reset the stack
* 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);
/* skip option synch, make this as safe as possible */
SYSLOG_INTERNAL_NO_OPTION_SYNCH(SYSLOG_WARNING,
"detach on terminate failed or already started by another thread, killing thread "TIDFMT"\n",
get_thread_id());
/* if we get here, either we recursed or someone is already trying
* to detach, just kill this thread so progress is made we don't
* have anything better to do with it */
/* skip cleanup, our state is likely messed up and we'd just like
* to get out alive, also avoids recursion problems, see caveat at
* remove_thread below */
terminate_type = TERMINATE_THREAD;
} else {
config_exit(); /* delete .1config file */
nt_terminate_process(currentThreadOrProcess, KILL_PROC_EXIT_STATUS);
ASSERT_NOT_REACHED();
}
}
/* CHECK: Can a process disallow PROCESS_TERMINATE or THREAD_TERMINATE
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);
/* Make sure debug loop pointer is in expected place since this makes
* 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);
/* we enter from several different places, so rewind until top-level kstat */
KSTOP_REWIND_UNTIL(thread_measured);
/* now we issue a syscall by number */
/* we can't use issue_system_call_for_app because it relies on
* dstack that we should release */
/* FIXME: what happens now if we get some callbacks that are still on
* their way? Shouldn't happen since Terminate* are believed to be
* non-alertable. */
/* FIXME: we only want the last part of cleanup_and_terminate */
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 /* no 2nd arg, just a filler */),
exit_process, 0, 0);
} else {
/* may have decided to terminate process */
if (exit_process) {
config_exit(); /* delete .1config file */
nt_terminate_process(currentThreadOrProcess, KILL_PROC_EXIT_STATUS);
ASSERT_NOT_REACHED();
} else {
/* FIXME: this is now very dangerous - we even leave our own state */
/* we should at least remove this thread from the all threads list
* 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, get_thread_id());
nt_terminate_thread(currentThreadOrProcess, KILL_THREAD_EXIT_STATUS);
ASSERT_NOT_REACHED();
}
/* CHECK: who is supposed to clean up the thread's stack?
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()
{
/* everything was done in os_init, even TEB TLS slots are initialized to 0 for us */
}
void
os_tls_exit(local_state_t *local_state, bool other_thread)
{
/* not needed for windows, everything is done is os_slow_exit including zeroing
* the freed TEB tls slots */
}
#ifdef CLIENT_INTERFACE
/* Allocates num_slots tls slots aligned with alignment align */
bool
os_tls_calloc(OUT uint *offset, uint num_slots, uint alignment)
{
bool need_synch = !dynamo_initialized;
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);
}
#endif
void
os_thread_init(dcontext_t *dcontext)
{
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;
/* init ostd fields here */
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);
/* case 8721: save the win32 start address and print it in the ldmp */
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_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
/* for non-debug we do fast exit path and don't free local heap */
/* clean up ostd fields here */
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);
/* see case 3768: a thread's stack is not de-allocated by this process,
* 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)) {
/* dstack is the only stack */
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, {
/* ASSERT that os region matches region stored in ostd */
byte *alloc_base;
size_t size = get_allocation_size(ostd->stack_base, &alloc_base);
/* Xref case 5877, this assert can fire if the exiting thread has already
* 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) ||
/* PR 252008: for WOW64 nudges we allocate an extra page. */
(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));
});
/* believe <= win2k frees the stack in process, would like to check
* 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 */
/* make sure we use our dcontext (dcontext could belong to another thread
* from other_thread_exit) since flushing will end up using this dcontext
* for synchronization purposes */
/* do not flush if at process exit since already cleaned up fragment
* info (for PR 536058)
*/
if (!dynamo_exited) {
app_memory_deallocation(get_thread_private_dcontext(), ostd->stack_base,
ostd->stack_top - ostd->stack_base,
true /* own thread_initexit_lock */,
false /* not image */);
}
if (TEST(ASLR_HEAP_FILL, DYNAMO_OPTION(aslr))) {
size_t stack_reserved_size = ostd->stack_top - ostd->stack_base;
/* verified above with get_allocation_size() this is not 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");
/* believe >= XP free the stack out of process */
ASSERT(ostd->stack_top == NULL);
ASSERT_CURIOSITY(get_os_version() <= WINDOWS_VERSION_2000);
}
}
void
os_thread_under_dynamo(dcontext_t *dcontext)
{
/* add cur thread to callback list */
ASSERT_MESSAGE(CHKLVL_ASSERTS+1/*expensive*/, "can only act on executing thread",
dcontext == get_thread_private_dcontext());
set_asynch_interception(get_thread_id(), true);
}
void
os_thread_not_under_dynamo(dcontext_t *dcontext)
{
/* remove cur thread from callback list */
ASSERT_MESSAGE(CHKLVL_ASSERTS+1/*expensive*/, "can only act on executing thread",
dcontext == get_thread_private_dcontext());
set_asynch_interception(get_thread_id(), false);
}
/***************************************************************************
* THREAD TAKEOVER
*/
/* Data passed to a thread for its own initialization */
typedef struct _takeover_data_t {
app_pc continuation_pc;
bool in_progress;
thread_id_t tid;
#ifndef X64
/* For WOW64 we sometimes need to modify the x64 state: */
HANDLE thread_handle;
CONTEXT_64 *cxt64;
byte *cxt64_alloc;
/* We assume these will never be 0 and use that as a sentinel */
ptr_uint_t memval_stack;
ptr_uint_t memval_r14;
#endif
} takeover_data_t;
/* List of threads */
typedef struct _thread_list_t {
HANDLE handle;
thread_id_t tid; /* may not be known, in which case INVALID_THREAD_ID */
void *user_data; /* set to NULL initially */
} thread_list_t;
/* Stored in thread_list_t.user_data */
enum {
TAKEOVER_NEW = 0, /* must match initial NULL */
TAKEOVER_TRIED,
TAKEOVER_SUCCESS,
};
/* Our set of a thread's context is not always visible until the thread is
* 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 /* should remain small */
#define INVALID_PAYLOAD ((void *)(ptr_int_t)-2) /* NULL and -1 are used by table */
static void
takeover_table_entry_free(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, MAX_CONTEXT_64_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 /* load factor: not perf-critical */,
HASHTABLE_SHARED | HASHTABLE_PERSISTENT,
takeover_table_entry_free
_IF_DEBUG("takeover table"));
}
/* Only called on slow exit */
static void
os_take_over_exit(void)
{
generic_hash_destroy(GLOBAL_DCONTEXT, takeover_table);
}
/* We need to distinguish a thread intercepted via APC hook but that is in ntdll
* 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);
}
/* Returns an array of num_threads_out thread_list_t entries allocated on the
* 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;
/* sanity checks (xref i#1220) */
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;
}
/* Removes the entry for the executing thread from the table and frees data */
static void
thread_attach_remove_from_table(takeover_data_t *data)
{
TABLE_RWLOCK(takeover_table, write, lock);
/* this will free data */
generic_hash_remove(GLOBAL_DCONTEXT, takeover_table, data->tid);
TABLE_RWLOCK(takeover_table, write, unlock);
}
/* Restores memory and the x64 context */
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(); /* translating a non-native thread! */
}
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)get_thread_id());
TABLE_RWLOCK(takeover_table, read, unlock);
if (data != NULL && data != INVALID_PAYLOAD) {
cxt->CXT_XIP = (ptr_uint_t) data->continuation_pc;
thread_attach_restore_full_state(data);
thread_attach_remove_from_table(data);
} else
ASSERT_NOT_REACHED(); /* translating a non-native thread! */
}
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);
}
#ifndef X64
/* i#1141: problems with NtGetContextThread and NtSetContextThread on WOW64
*
* 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 initstack: but these takeover points shouldn't be perf-critical.
* This really simplifies the wow64 entry/exit corner cases.
*/
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
/* Match the wow64 syscall call*:
* 7d8513eb 64ff15c0000000 call dword ptr fs:[000000c0]
*/
static const byte WOW64_SYSCALL_CALL[] = {0x64, 0xff, 0x15, 0xc0, 0x00, 0x00, 0x00};
# endif
/* The WOW64_CONTEXT.Eip won't be correct in two spots: right before it's
* 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].
*/
/* We include the subsequent instr for 12 to avoid matching elsewhere in wow64 code */
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};
if (!is_wow64_process(NT_CURRENT_PROCESS))
return;
/* WOW64 context setting is fragile: we need the raw x64 context as well.
* We can't easily use nt_initialize_context so we manually set the flags.
*/
buf = (byte *) global_heap_alloc(MAX_CONTEXT_64_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, MAX_CONTEXT_64_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) {
/* In x86 mode, so not inside the wow64 layer. Context setting should
* work fine.
*/
global_heap_free(buf, MAX_CONTEXT_64_SIZE HEAPACCT(ACCT_THREAD_MGT));
return;
}
/* Could be in ntdll or user32 or anywhere a syscall is made, so we don't
* assert is_in_ntdll, but we do check that it's the wow64 syscall call*:
*/
ASSERT_CURIOSITY(memcmp(data->continuation_pc - sizeof(WOW64_SYSCALL_CALL),
WOW64_SYSCALL_CALL, sizeof(WOW64_SYSCALL_CALL)) == 0);
/* Corner case #1: 1st instr on entry where retaddr is in [esp] */
if (memcmp((byte *)(ptr_uint_t)cxt64->Rip, WOW64_ENTER_INST12,
sizeof(WOW64_ENTER_INST12)) == 0) {
if (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();
}
}
/* Corner case #2: 2nd instr in entry where retaddr is in r8d */
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();
}
/* Restore so we can use cxt64 to revert if necessary */
cxt64->R8 = orig_r8;
}
/* Corner case #3: 2nd-to-last instr in exit where retaddr is in r9d */
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();
}
/* Restore so we can use cxt64 to revert if necessary */
cxt64->R9 = orig_r9;
}
/* Corner case #4: last instr in exit where we already copied retaddr to [r14] */
else if (memcmp((byte *)(ptr_uint_t)cxt64->Rip, WOW64_EXIT_INST2,
sizeof(WOW64_EXIT_INST2)) == 0) {
if (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();
}
}
if (changed_x64_cxt) {
/* We'll need the handle in case we have to revert/restore the x64 context.
* 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, MAX_CONTEXT_64_SIZE HEAPACCT(ACCT_THREAD_MGT));
}
}
#endif
/* On success, returns true and leaves thread suspended. */
static bool
os_take_over_thread(dcontext_t *dcontext, HANDLE hthread, thread_id_t tid, bool suspended)
{
bool success = true;
char buf[MAX_CONTEXT_SIZE];
CONTEXT *cxt = nt_initialize_context(buf, CONTEXT_DR_STATE);
ASSERT(tid == thread_id_from_handle(hthread));
if ((suspended || nt_thread_suspend(hthread, NULL)) &&
NT_SUCCESS(nt_get_context(hthread, cxt))) {
/* Rather than try to emulate clone handling by putting this
* 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;
/* i#1443: avoid self-interp on threads that are waiting at our hook
* 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);
return true; /* it's waiting for us to take it over */
}
/* Avoid double-takeover.
* 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)) {
/* Thread was never scheduled on last takeover, or has not
* 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) &&
/* Rule out thread initializing but currently in ntdll */
!((takeover_data_t *)already_taken_over)->in_progress &&
cxt->CXT_XIP != (ptr_uint_t) thread_attach_takeover) {
/* XXX: I see cases where my setcontext succeeds, immediate getcontext
* 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);
/* Now that i#1141 is fixed this shouldn't happen: we'd like to
* know if it does.
*/
ASSERT_CURIOSITY(false && "thread takeover context reverted!");
} else
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
/* See comments above: not safe to change any other regs here */
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);
/* leave thread suspended */
}
} else {
LOG(GLOBAL, LOG_THREADS, 1, "\tfailed to suspend/query thread "TIDFMT"\n", tid);
success = false;
}
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 the app voluntarily stopped, wait for it to ask to start again */
if (dcontext->currently_stopped)
return false;
/* In case of failure (xref all the issues with setting the context), we
* 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/*suspended*/);
}
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;
bool took_over_all = true, found_new_threads = true;
/* ensure user_data starts out how we think it does */
ASSERT(TAKEOVER_NEW == (ptr_uint_t) NULL);
mutex_lock(&thread_initexit_lock);
/* Need to iterate until no new threads, w/ an escape valve of max iters.
* 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) {
/* Copy user_data over. Yeah, nested loop: but hashtable seems overkill. */
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) { /* not already under our control */
/* cur thread is assumed to be under DR */
ASSERT(threads[i].tid != get_thread_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/*!suspended*/)) {
threads[i].user_data = (void *)(ptr_uint_t) TAKEOVER_SUCCESS;
} else {
took_over_all = false;
/* We want to know when this happens. We might expect
* 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);
}
}
/* Potential risk of a thread from an earlier list somehow not showing up on
* 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;
}
mutex_unlock(&thread_initexit_lock);
return !took_over_all;
}
/* Previously-unknown thread is redirected here to initialize itself. */
void
thread_attach_setup(priv_mcontext_t *mc)
{
dcontext_t *dcontext;
takeover_data_t *data;
int rc;
ENTERING_DR();
TABLE_RWLOCK(takeover_table, write, lock);
data = (takeover_data_t *)
generic_hash_lookup(GLOBAL_DCONTEXT, takeover_table, (ptr_uint_t)get_thread_id());
TABLE_RWLOCK(takeover_table, write, unlock);
if (data == NULL || data == INVALID_PAYLOAD) {
ASSERT_NOT_REACHED();
/* in release better to let thread run native than to crash */
EXITING_DR();
return;
}
/* Preclude double takeover if we become suspended while in ntdll */
data->in_progress = true;
rc = dynamo_thread_init(NULL, mc _IF_CLIENT_INTERFACE(false));
/* We don't assert that rc!=-1 b/c we are used to take over a
* native_exec thread, which is already initialized.
*/
dcontext = get_thread_private_dcontext();
ASSERT(dcontext != NULL);
dynamo_thread_under_dynamo(dcontext);
/* clear retakeover field, if we came from os_thread_take_over_suspended_native() */
dcontext->thread_record->retakeover = false;
/* A native_exec_syscalls hook on NtCallbackReturn could have left the
* 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", 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/*!full_DR_state*/);
ASSERT_NOT_REACHED();
}
/***************************************************************************
* CLIENT THREADS
*/
#ifdef CLIENT_SIDELINE /* PR 222812: tied to sideline usage */
/* i#41/PR 222812: 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
*/
/* FIXME PR 210591: transparency issues:
* 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)
{
/* Thread was initialized in intercept_new_thread() */
dcontext_t *dcontext = get_thread_private_dcontext();
/* We stored the func and args at base of dstack and param points at them */
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",
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",
get_thread_id());
os_terminate(dcontext, TERMINATE_THREAD|TERMINATE_CLEANUP);
}
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);
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();
/* We store the func and args at base of dstack for client_thread_target */
arg_buf[0] = (void *) func;
arg_buf[1] = arg;
/* FIXME PR 225714: does this work on Vista? */
hthread = 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;
}
/* FIXME: what about all of our check_sole_thread() checks? */
res = close_handle(hthread);
CLIENT_ASSERT(res, "error closing thread handle");
return res;
}
#endif CLIENT_SIDELINE /* PR 222812: tied to sideline usage */
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)
{
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;
}
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;
/* FIXME: consider just E to be unreadable?
* 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);
}
/* true when page hasn't been written to */
bool
prot_is_copyonwrite(uint prot)
{
prot &= ~PAGE_PROTECTION_QUALIFIERS;
/* although really providing an enumeration, the known PAGE_
* values use separate bit flags. We use TESTANY in case new
* PAGE_PROTECTION_QUALIFIERS show up.
*/
return TESTANY(PAGE_WRITECOPY|PAGE_EXECUTE_WRITECOPY, prot);
}
/* true when page is a guard page and hasn't been touched */
bool
prot_is_guard(uint prot)
{
return TEST(PAGE_GUARD, prot);
}
/* translate platform independent protection bits to native flags */
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;
}
/* translate native flags to platform independent protection bits */
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;
}
/* does not change prot if it doesn't already have read access */
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;
}
/* returns osprot flags preserving all native protection flags except
* 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);
/* preserve any old WRITECOPY 'flag' if page hasn't been touched */
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);
}
}
}
/* FIXME:
* 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)
{
/* make the common case of NT_CURRENT_PROCESS faster */
if (phandle == NT_CURRENT_PROCESS) {
return true;
} else {
/* we know of no way to detect whether two handles point to the same object,
* so we go to pid
*/
process_id_t pid = process_id_from_handle(phandle);
return is_pid_me(pid);
}
}
/* used only in get_dynamorio_library_path() but file level namespace
* so it is easily available to windbg scripts */
static char dynamorio_library_path[MAXIMUM_PATH];
/* get full path to our own library, (cached), used for forking and message file name */
char*
get_dynamorio_library_path()
{
/* This operation could be dangerous, so it is still better that we do it
* once at startup when there is a single thread only
*/
if (!dynamorio_library_path[0]) { /* not cached */
/* get_module_name can take any pc in the dll,
* 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;
/* here's where we set the library path */
ASSERT(!dr_earliest_injected); /* should be already set for earliest */
get_module_name(pb, dynamorio_library_path, MAXIMUM_PATH);
}
return dynamorio_library_path;
}
/* based on a process handle to a process that is not yet running,
* verify whether we should be taking control over it */
/* if target process should be injected into returns true, and
* inject_settings is set if non-NULL */
bool
should_inject_into_process(dcontext_t *dcontext, HANDLE process_handle,
int *rununder_mask, /* OPTIONAL OUT */
inject_setting_mask_t *inject_settings /* OPTIONAL OUT */)
{
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; /* -follow_children defaults to inject */
/* check if child should be excluded from running under dr */
if (TEST(INJECT_EXCLUDED, should_inject)) {
LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 1,
"\tchild is excluded, not injecting\n");
inject = false;
}
/* check if we should leave injection to preinjector */
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;
}
/* cxt may be NULL if -inject_at_create_process */
static int
inject_into_process(dcontext_t *dcontext, HANDLE process_handle, CONTEXT *cxt,
inject_setting_mask_t should_inject)
{
/* Here in fact we don't want to have the default argument override
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 *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));
/* If there is no app-specific subkey, then we should check in 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.
*/
switch (err) {
case GET_PARAMETER_SUCCESS:
break;
case GET_PARAMETER_NOAPPSPECIFIC:
/* We got the global key's library, use parent's library instead if the only
* 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();
}
LOG(THREAD, LOG_SYSCALLS|LOG_THREADS, 1, "\tinjecting %s into child process\n", library);
if (DYNAMO_OPTION(aslr_dr) &&
/* case 8749 - can't aslr dr for thin_clients */
process_handle != NULL && !is_child_in_thin_client(process_handle)) {
aslr_force_dynamorio_rebase(process_handle);
}
/* Can't early inject 32-bit DR into a wow64 process as there is no
* 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));
/* FIXME if early_inject_address == NULL then early_inject_init failed
* 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, 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));
/* FIXME i#49: this can happen for a 64-bit child of a 32-bit parent */
ASSERT_CURIOSITY(false && "injection into child failed: 32 to 64?");
return false; /* for compilation correctness and release builds */
}
return true;
}
bool
is_first_thread_in_new_process(HANDLE process_handle, CONTEXT *cxt)
{
/* ASSUMPTION: based on what I've seen, on win2k a new process has
* 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)
return true;
if (!is_pid_me(pid)) {
ptr_uint_t peb = (ptr_uint_t) get_peb(process_handle);
if (cxt->THREAD_START_ARG == peb)
return true;
else if (is_wow64_process(process_handle) &&
get_os_version() >= WINDOWS_VERSION_VISTA) {
/* i#816: for wow64 process PEB query will be x64 while thread addr
* 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 is
* a one-page alloc whose first 3 fields match the x64 PEB:
* boolean flags, Mutant, and ImageBaseAddress.
*/
int64 peb64[3];
int peb32[3];
byte *base = NULL;
size_t sz = get_allocation_size_ex
(process_handle, (byte *)cxt->THREAD_START_ARG, &base);
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(), cxt->THREAD_START_ARG, peb);
if (sz != PAGE_SIZE || base != (byte *)cxt->THREAD_START_ARG)
return false;
if (!nt_read_virtual_memory(process_handle, (const void *) peb,
peb64, sizeof(peb64), &sz) ||
sz != sizeof(peb64) ||
!nt_read_virtual_memory(process_handle,
(const void *) cxt->THREAD_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;
}
/* Depending on registry and options maybe inject into child process with
* 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). */
bool
maybe_inject_into_process(dcontext_t *dcontext, HANDLE process_handle,
CONTEXT *cxt)
{
/* if inject_at_create_process becomes dynamic, need to move this check below
* the synchronize dynamic options */
/* FIXME - can't read process parameters, at process create time is NULL
* value in peb field except in Vista. Could pass it in. */
/* Can't early inject 32-bit DR into a wow64 process as there is no
* 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;
/* Creating a new process & at potential inject point */
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)) {
injected = true; /* attempted, at least */
ASSERT(cxt != NULL || DYNAMO_OPTION(early_inject));
/* FIXME : if not -early_inject, we are going to read and write
* to cxt, which may be unsafe */
if (inject_into_process(dcontext, process_handle, cxt,
should_inject)) {
check_for_run_once(process_handle, rununder_mask);
}
}
}
return injected;
}
/* For case 8749: can't aslr dr for thin_client because cygwin apps will die. */
static bool
is_child_in_thin_client(HANDLE process_handle)
{
bool res;
const options_t *opts;
/* Shouldn't be using this for the current process. */
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);
/* In this case the option is used only for preventing aslr_dr, so be safe
* 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;
}
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);
/* 0x7ffeffff on 2GB:2GB default */
/* or 0xbffeffff with /3GB in boot.ini, */
/* /userva switch may also change the actual value seen */
static void