blob: e8ab897d92b0d0ddd4410a4933f06c21acfc1b80 [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2011-2022 Google, Inc. All rights reserved.
* Copyright (c) 2008-2010 VMware, Inc. All rights reserved.
* **********************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of VMware, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
#include "configure.h"
#include "globals.h"
#include "nudge.h"
#ifdef WINDOWS
# include "ntdll.h" /* for our_create_thread(), nt_free_virtual_memory() */
# include "os_exports.h" /* for detach_helper(), get_stack_bounds() */
# include "drmarker.h"
#else
#endif /* WINDOWS */
#ifdef LINUX
# include "synch.h"
#endif
#ifdef HOT_PATCHING_INTERFACE
# include "hotpatch.h" /* for hotp_nudge_update() */
#endif
#ifdef PROCESS_CONTROL
# include "moduledb.h" /* for process_control() */
#endif
#include "fragment.h" /* needed for perscache.h */
#include "perscache.h" /* for coarse_units_freeze_all() */
#include "instrument.h" /* for instrement_nudge() */
#include "fcache.h" /* for reset routines */
#ifdef WINDOWS
static void
handle_nudge(dcontext_t *dcontext, nudge_arg_t *arg); /* forward decl */
static void
nudge_terminate_on_dstack(dcontext_t *dcontext)
{
ASSERT(dcontext == get_thread_private_dcontext());
if (dcontext->nudge_terminate_process) {
os_terminate_with_code(dcontext, TERMINATE_PROCESS | TERMINATE_CLEANUP,
dcontext->nudge_exit_code);
} else
os_terminate(dcontext, TERMINATE_THREAD | TERMINATE_CLEANUP);
ASSERT_NOT_REACHED();
}
/* This is the target for all nudge threads.
* CAUTION: generic_nudge_target is added to global_rct_ind_targets table. If this
* function is renamed or cloned, update rct_known_targets_init accordingly.
*/
void
generic_nudge_target(nudge_arg_t *arg)
{
/* Fix for case 5130; volatile forces a 'call' instruction to be generated
* rather than 'jmp' during optimization. FIXME: need a standardized &
* better way of stopping core from emulating itself.
*/
volatile bool nudge_result;
/* needed to make sure dr has a specific target to lookup and avoid
* interpreting when taking over new threads; see leave_call_native().
*/
nudge_result = generic_nudge_handler(arg);
/* Should never return. */
ASSERT_NOT_REACHED();
os_terminate(NULL, TERMINATE_THREAD); /* just in case */
}
/* exit_process is only honored if dcontext != NULL, and exit_code is only honored
* if exit_process is true
*/
bool
nudge_thread_cleanup(dcontext_t *dcontext, bool exit_process, uint exit_code)
{
/* Note - for supporting detach with clients and nudge threads we need that
* no lock grabbing or other actions that would interfere with the detaching process
* occur in the cleanup path here. */
/* Case 8901: this routine is currently called from the code cache, which may have
* been reset underneath us, so we can't just blindly return. This also gives us
* consistent behavior for handling stack freeing. */
/* Case 9020: no EXITING_DR() as os_terminate will do that for us */
/* FIXME - these nudge threads do hit dll mains for thread attach so app may have
* allocated some TLS memory which won't end up being freed since this won't go
* through dll main thread detach. The app may also object to unbalanced attach to
* detach ratio though we haven't seen that in practice. Long term we should take
* over and redirect the thread at the init apc so it doesn't go through the
* DllMains to start with. */
/* We have a general problem on how to free the application stack for nudges.
* Currently the app/os will never free a nudge thread's app stack:
* On NT and 2k ExitThread would normally free the app stack, but we always
* terminate nudge threads instead of allowing them to return and exit normally.
* On XP and 2k3 none of our nudge creation routines inform csrss of the new thread
* (which is who typically frees the stacks).
* On Vista and Win7 we don't use NtCreateThreadEx to create the nudge threads so
* the kernel doesn't free the stack.
* As such we are left with two options: free the app stack here (nudgee free) or
* have the nudge thread creator free the app stack (nudger free). Going with
* nudgee free means we leak exit race nudge stacks whereas if we go with nudger free
* for external nudges then we'll leak timed out nudge stacks (for internal nudges
* we pretty much have to do nudgee free). A nudge_arg_t flag is used to specify
* which model we use, but currently we always nudgee free.
* On Win8+ we do use NtCreateThreadEx to create the nudge threads so the kernel
* does free the stack. We could use this on Vista and Win7 too -- should we?
* It requires someone to free the argument buffer (NUDGE_FREE_ARG).
*
* dynamo_thread_exit_common() is where the app stack is actually freed, not here.
*/
if (dynamo_exited || !dynamo_initialized || dcontext == NULL) {
/* FIXME - no cleanup so we'll leak any memory allocated for this thread
* including the application's stack and arg if we were supposed to free them.
* We only expect to get here in rare races where the nudge thread was created
* before dr exited (i.e. before drmarker was freed) but didn't end up getting
* scheduled till after dr exited. */
ASSERT(!exit_process); /* shouldn't happen */
# ifdef WINDOWS
if (dcontext != NULL)
swap_peb_pointer(dcontext, false /*to app*/);
# endif
os_terminate(dcontext, TERMINATE_THREAD);
} else {
/* Nudge threads should exit without holding any locks. */
ASSERT_OWN_NO_LOCKS();
# ifdef WINDOWS
/* We want to remain private during exit (esp client exit and loader_thread_exit
* calling privlib entries). Thus we do *not* call swap_peb_pointer().
* For exit_process, os_loader_exit will swap to app.
* XXX: For thread exit: somebody should swap to app later: but
* os_thread_not_under_dynamo() doesn't seem to (unlike UNIX) (and if we
* change that we should call it *after* loader_thread_exit()!).
* It's not that important I guess: the thread is exiting.
*/
# endif
/* if freeing the app stack we must be on the dstack when we cleanup */
if (dcontext->free_app_stack && !is_currently_on_dstack(dcontext)) {
if (exit_process) {
/* XXX: wasteful to use two dcontext fields just for this.
* Extend call_switch_stack to support extra args or sthg?
*/
dcontext->nudge_terminate_process = true;
dcontext->nudge_exit_code = exit_code;
}
call_switch_stack(dcontext, dcontext->dstack,
(void (*)(void *))nudge_terminate_on_dstack,
NULL /* not on d_r_initstack */, false /* don't return */);
} else {
/* Already on dstack or nudge creator will free app stack. */
if (exit_process) {
os_terminate_with_code(dcontext, TERMINATE_PROCESS | TERMINATE_CLEANUP,
exit_code);
} else {
os_terminate(dcontext, TERMINATE_THREAD | TERMINATE_CLEANUP);
}
}
}
ASSERT_NOT_REACHED(); /* we should never return */
return true;
}
/* This is the actual nudge handler
* Notes: This function returns a boolean mainly to fix case 5130; it is not
* really necessary.
*/
bool
generic_nudge_handler(nudge_arg_t *arg_dont_use)
{
dcontext_t *dcontext = get_thread_private_dcontext();
nudge_arg_t safe_arg = { 0 };
uint nudge_action_mask = 0;
# ifdef WINDOWS
/* this routine is run natively via leave_call_native() so there's no
* cxt switch that swapped for us
*/
if (dcontext != NULL)
swap_peb_pointer(dcontext, true /*to priv*/);
# endif
/* To be extra safe we use d_r_safe_read() to access the nudge argument, though once
* we get past the checks below we are trusting its content. */
ASSERT(arg_dont_use != NULL && "invalid nudge argument");
if (!d_r_safe_read(arg_dont_use, sizeof(nudge_arg_t), &safe_arg)) {
ASSERT(false && "invalid nudge argument");
goto nudge_finished;
}
nudge_action_mask = safe_arg.nudge_action_mask;
/* if needed tell thread exit to free the application stack */
if (!TEST(NUDGE_NUDGER_FREE_STACK, safe_arg.flags)) {
dcontext->free_app_stack = true;
}
/* FIXME - would be nice to inform nudge creator if we need to nop the nudge. */
/* Fix for case 5702. If a nudge thread comes in during process exit,
* don't process it, i.e., nop it. FIXME - this leaks the app stack and nudge arg
* if the nudge was supposed to free them. */
if (dynamo_exited)
goto nudge_finished;
/* Node manager will not be able to nudge before reading the drmarker and
* the dr_marker isn't available before callback_interception_init().
* Since after callback_interception_init() new threads won't be allowed
* to progress till dynamo_initialized is set, by the time a nudge thread
* reaches here dynamo_initialized should be set. */
ASSERT(dynamo_initialized);
if (!dynamo_initialized)
goto nudge_finished;
/* We should always have a dcontext. */
ASSERT(dcontext != NULL);
if (dcontext == NULL)
goto nudge_finished;
ENTERING_DR();
/* Xref case 552, the nudge_target value provides a reasonable measure
* of security against an attacker leveraging this routine. */
if (dcontext->nudge_target !=
(void *)generic_nudge_target
/* Allow a syscall for our test in debug build. */
IF_DEBUG(&&!check_filter("win32.tls.exe",
get_short_name(get_application_name())))) {
/* FIXME - should we report this likely attempt to attack us? need
* a unit test for this (though will then have to tone this down). */
ASSERT(false && "unauthorized thread tried to nudge");
/* If we really are under attack we should terminate immediately and
* proceed no further. Note we are leaking the app stack and nudge arg if we
* were supposed to free them. */
os_terminate(dcontext, TERMINATE_THREAD);
ASSERT_NOT_REACHED();
}
/* Free the arg if requested. */
if (TEST(NUDGE_FREE_ARG, safe_arg.flags)) {
nt_free_virtual_memory(arg_dont_use);
}
handle_nudge(dcontext, &safe_arg);
nudge_finished:
return nudge_thread_cleanup(dcontext, false /*just thread*/, 0 /*unused*/);
}
#endif /* WINDOWS */
/* This routine may not return */
IF_WINDOWS(static)
void
handle_nudge(dcontext_t *dcontext, nudge_arg_t *arg)
{
uint nudge_action_mask = arg->nudge_action_mask;
/* Future version checks would go here. */
ASSERT_CURIOSITY(arg->version == NUDGE_ARG_CURRENT_VERSION);
/* Nudge shouldn't start with any locks held. Do this assert after the
* dynamo_exited check, other wise the locks may be deleted. */
ASSERT_OWN_NO_LOCKS();
STATS_INC(num_nudges);
#ifdef WINDOWS
/* Linux does this in signal.c */
SYSLOG_INTERNAL_INFO("received nudge mask=0x%x id=0x%08x arg=0x" ZHEX64_FORMAT_STRING,
arg->nudge_action_mask, arg->client_id, arg->client_arg);
#endif
if (nudge_action_mask == 0) {
ASSERT_CURIOSITY(false && "Nudge: no action specified");
return;
} else if (nudge_action_mask >= NUDGE_GENERIC(PARAMETRIZED_END)) {
ASSERT(false && "Nudge: unknown nudge action");
return;
}
/* In -thin_client mode only detach and process_control nudges are allowed;
* case 8888. */
#define VALID_THIN_CLIENT_NUDGES (NUDGE_GENERIC(process_control) | NUDGE_GENERIC(detach))
if (DYNAMO_OPTION(thin_client)) {
if (TEST(VALID_THIN_CLIENT_NUDGES, nudge_action_mask)) {
/* If it is a valid thin client nudge, then disable all others. */
nudge_action_mask &= VALID_THIN_CLIENT_NUDGES;
} else {
return; /* invalid nudge for thin_client, so mute it */
}
}
/* FIXME: NYI action handlers. As implemented move to desired order. */
if (TEST(NUDGE_GENERIC(upgrade), nudge_action_mask)) {
/* FIXME: watch out for flushed clean-call fragment */
nudge_action_mask &= ~NUDGE_GENERIC(upgrade);
ASSERT_NOT_IMPLEMENTED(false && "case 4179");
}
if (TEST(NUDGE_GENERIC(kstats), nudge_action_mask)) {
nudge_action_mask &= ~NUDGE_GENERIC(kstats);
ASSERT_NOT_IMPLEMENTED(false);
}
#ifdef INTERNAL
if (TEST(NUDGE_GENERIC(stats), nudge_action_mask)) {
nudge_action_mask &= ~NUDGE_GENERIC(stats);
ASSERT_NOT_IMPLEMENTED(false);
}
if (TEST(NUDGE_GENERIC(invalidate), nudge_action_mask)) {
/* FIXME: watch out for flushed clean-call fragment */
nudge_action_mask &= ~NUDGE_GENERIC(invalidate);
ASSERT_NOT_IMPLEMENTED(false);
}
if (TEST(NUDGE_GENERIC(recreate_pc), nudge_action_mask)) {
nudge_action_mask &= ~NUDGE_GENERIC(recreate_pc);
ASSERT_NOT_IMPLEMENTED(false);
}
if (TEST(NUDGE_GENERIC(recreate_state), nudge_action_mask)) {
nudge_action_mask &= ~NUDGE_GENERIC(recreate_state);
ASSERT_NOT_IMPLEMENTED(false);
}
if (TEST(NUDGE_GENERIC(reattach), nudge_action_mask)) {
/* FIXME: watch out for flushed clean-call fragment */
nudge_action_mask &= ~NUDGE_GENERIC(reattach);
ASSERT_NOT_IMPLEMENTED(false);
}
#endif /* INTERNAL */
if (TEST(NUDGE_GENERIC(diagnose), nudge_action_mask)) {
nudge_action_mask &= ~NUDGE_GENERIC(diagnose);
ASSERT_NOT_IMPLEMENTED(false);
}
/* Implemented action handlers */
if (TEST(NUDGE_GENERIC(opt), nudge_action_mask)) {
nudge_action_mask &= ~NUDGE_GENERIC(opt);
synchronize_dynamic_options();
}
if (TEST(NUDGE_GENERIC(ldmp), nudge_action_mask)) {
nudge_action_mask &= ~NUDGE_GENERIC(ldmp);
os_dump_core("Nudge triggered ldmp.");
}
if (TEST(NUDGE_GENERIC(freeze), nudge_action_mask)) {
nudge_action_mask &= ~NUDGE_GENERIC(freeze);
coarse_units_freeze_all(true /*in-place: FIXME: separate nudge for non?*/);
}
if (TEST(NUDGE_GENERIC(persist), nudge_action_mask)) {
nudge_action_mask &= ~NUDGE_GENERIC(persist);
coarse_units_freeze_all(false /*!in-place==persist*/);
}
if (TEST(NUDGE_GENERIC(client), nudge_action_mask)) {
nudge_action_mask &= ~NUDGE_GENERIC(client);
instrument_nudge(dcontext, arg->client_id, arg->client_arg);
}
#ifdef PROCESS_CONTROL
if (TEST(NUDGE_GENERIC(process_control), nudge_action_mask)) { /* Case 8594 */
nudge_action_mask &= ~NUDGE_GENERIC(process_control);
/* Need to synchronize because process control can be switched between
* on (allow or block list) & off. FIXME - the nudge mask should specify this,
* but doesn't hurt to do it again. */
synchronize_dynamic_options();
if (IS_PROCESS_CONTROL_ON())
process_control();
/* If process control is enforced then control won't come back. If
* either -detect_mode is on or if there was nothing to enforce, control
* comes back in which case it is safe to let remaining nudges be
* processed because no core state would have been changed. */
}
#endif
#ifdef HOTPATCHING
if (DYNAMO_OPTION(hot_patching) && DYNAMO_OPTION(liveshields) &&
TEST_ANY(NUDGE_GENERIC(policy) | NUDGE_GENERIC(mode) | NUDGE_GENERIC(lstats),
nudge_action_mask)) {
hotp_nudge_update(
nudge_action_mask &
(NUDGE_GENERIC(policy) | NUDGE_GENERIC(mode) | NUDGE_GENERIC(lstats)));
nudge_action_mask &=
~(NUDGE_GENERIC(policy) | NUDGE_GENERIC(mode) | NUDGE_GENERIC(lstats));
}
#endif
#ifdef PROGRAM_SHEPHERDING
if (TEST(NUDGE_GENERIC(violation), nudge_action_mask)) {
nudge_action_mask &= ~NUDGE_GENERIC(violation);
/* Use nudge mechanism to trigger a security violation at an
* arbitrary time. Note - is only useful for testing kill process attack
* handling as this is not an app thread (we injected it). */
/* see bug 652 for planned improvements */
security_violation(dcontext, dcontext->next_tag, ATTACK_SIM_NUDGE_VIOLATION,
OPTION_BLOCK | OPTION_REPORT);
}
#endif
if (TEST(NUDGE_GENERIC(reset), nudge_action_mask)) {
nudge_action_mask &= ~NUDGE_GENERIC(reset);
if (DYNAMO_OPTION(enable_reset)) {
d_r_mutex_lock(&reset_pending_lock);
/* fcache_reset_all_caches_proactively() will unlock */
fcache_reset_all_caches_proactively(RESET_ALL);
/* NOTE - reset is safe since we won't return to the code cache below (we
* will in fact not return at all). */
} else {
SYSLOG_INTERNAL_WARNING("nudge reset ignored since resets are disabled");
}
}
#if defined(WINDOWS) || defined(LINUX)
/* The detach handler is last since in the common case it doesn't return. */
if (TEST(NUDGE_GENERIC(detach), nudge_action_mask)) {
# ifdef WINDOWS
dcontext->free_app_stack = false;
nudge_action_mask &= ~NUDGE_GENERIC(detach);
detach_helper(DETACH_NORMAL_TYPE);
# else
nudge_action_mask &= ~NUDGE_GENERIC(detach);
/* This is not using stack_alloc() because we can't have this being cleaned up
* via normal cleanup paths. */
heap_error_code_t error_code_reserve, error_code_commit;
void *d_r_detachstack =
os_heap_reserve(NULL, DYNAMORIO_STACK_SIZE, &error_code_reserve, false);
/* XXX: This memory is not freed. */
if (!os_heap_commit(d_r_detachstack, DYNAMORIO_STACK_SIZE,
MEMPROT_READ | MEMPROT_WRITE, &error_code_commit)) {
ASSERT_NOT_REACHED();
}
call_switch_stack(dcontext,
(byte *)((ptr_uint_t)d_r_detachstack + DYNAMORIO_STACK_SIZE),
(void (*)(void *))detach_externally_on_new_stack, NULL, true);
ASSERT_NOT_REACHED();
# endif
}
#endif
}
#ifdef UNIX
/* Only touches thread-private data and acquires no lock */
void
nudge_add_pending(dcontext_t *dcontext, nudge_arg_t *nudge_arg)
{
pending_nudge_t *pending =
(pending_nudge_t *)heap_alloc(dcontext, sizeof(*pending) HEAPACCT(ACCT_OTHER));
pending_nudge_t *prev;
pending->arg = *nudge_arg;
pending->next = NULL;
/* Simpler to prepend, but we want FIFO. Should be rare to have multiple
* so not worth storing an end pointer.
*/
DOSTATS({
if (dcontext->nudge_pending != NULL)
STATS_INC(num_pending_nudges);
});
for (prev = dcontext->nudge_pending; prev != NULL && prev->next != NULL;
prev = prev->next)
;
if (prev == NULL) {
dcontext->nudge_pending = pending;
} else {
prev->next = pending;
}
}
#endif
/* send a nudge from DR to the current process or another process */
dr_config_status_t
nudge_internal(process_id_t pid, uint nudge_action_mask, uint64 client_arg,
client_id_t client_id, uint timeout_ms)
{
bool internal = (pid == get_process_id());
#ifdef WINDOWS
HANDLE hthread, hproc;
bool res;
void *nudge_target;
dr_config_status_t status;
wait_status_t wait;
#else
dcontext_t *dcontext = get_thread_private_dcontext();
#endif
nudge_arg_t nudge_arg;
memset(&nudge_arg, 0, sizeof(nudge_arg));
nudge_arg.version = NUDGE_ARG_CURRENT_VERSION;
nudge_arg.nudge_action_mask = nudge_action_mask;
/* We do not set NUDGE_NUDGER_FREE_STACK so the stack will be freed
* in the target process, for <=win7.
*/
nudge_arg.flags = (internal ? NUDGE_IS_INTERNAL : 0);
#ifdef WINDOWS
if (get_os_version() >= WINDOWS_VERSION_8) {
/* The kernel owns and frees the stack. */
nudge_arg.flags |= NUDGE_NUDGER_FREE_STACK;
/* The arg was placed in a new kernel alloc. */
nudge_arg.flags |= NUDGE_FREE_ARG;
}
#endif
nudge_arg.client_arg = client_arg;
nudge_arg.client_id = client_id;
LOG(GLOBAL, LOG_ALL, 1, "Creating internal nudge with action_mask 0x%08x\n",
nudge_action_mask);
#ifdef WINDOWS
if (internal) {
hproc = NT_CURRENT_PROCESS;
nudge_target = (void *)generic_nudge_target;
} else {
dr_marker_t marker;
hproc = process_handle_from_id(pid);
if (hproc == NULL)
return DR_NUDGE_PID_NOT_FOUND;
if (read_and_verify_dr_marker(hproc, &marker) != DR_MARKER_FOUND) {
/* if target process is not under DR (or any error getting
* marker), don't nudge.
*/
return DR_NUDGE_PID_NOT_INJECTED;
}
nudge_target = marker.dr_generic_nudge_target;
}
hthread = our_create_thread(hproc, IF_X64_ELSE(true, false), nudge_target, NULL,
&nudge_arg, sizeof(nudge_arg_t), 15 * (uint)PAGE_SIZE,
12 * (uint)PAGE_SIZE, false, NULL);
ASSERT(hthread != INVALID_HANDLE_VALUE);
if (hthread == INVALID_HANDLE_VALUE)
return DR_FAILURE;
wait = os_wait_handle(hthread, timeout_ms);
if (wait == WAIT_SIGNALED || (wait == WAIT_TIMEDOUT && timeout_ms == 0))
status = DR_SUCCESS;
else if (wait == WAIT_TIMEDOUT)
status = DR_NUDGE_TIMEOUT;
else
status = DR_FAILURE;
res = close_handle(hthread);
ASSERT(res);
LOG(GLOBAL, LOG_ALL, 1, "Finished creating internal nudge thread\n");
if (internal)
close_handle(hproc);
if (hthread != INVALID_HANDLE_VALUE)
return DR_SUCCESS;
else
return DR_FAILURE;
#else
if (internal) {
/* We could send a signal, but that doesn't help matters since the
* interruption point will be here and not be any potential fragment
* underneath this clean call if a client invoked this (unless the signal
* ends up going to another thread: can't control that w/ sigqueue). So we
* just document that it's up to the client to bound delivery time.
*/
if (dcontext == NULL)
return DR_FAILURE;
nudge_add_pending(dcontext, &nudge_arg);
return DR_SUCCESS;
} else {
if (send_nudge_signal(pid, nudge_action_mask, client_id, client_arg))
return DR_SUCCESS;
else
return DR_FAILURE;
}
#endif /* WINDOWS -> UNIX */
}