blob: 938352bdf0aafaf149a0564c1a5fc88ca3a1896e [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2011-2014 Google, Inc. All rights reserved.
* Copyright (c) 2005-2010 VMware, Inc. All rights reserved.
* **********************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of VMware, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
#ifndef ASM_CODE_ONLY /* C code */
/* Tests protection of DynamoRIO's stack
* This test when run natively will fail with an error message
*/
#include "tools.h"
#ifdef UNIX
# include <unistd.h>
# include <signal.h>
# include <ucontext.h>
# include <errno.h>
#endif
#include <setjmp.h>
/* asm routines */
void clear_eflags(void);
void evil_copy(void *start, int count, ptr_int_t value);
#define VERBOSE 0
#define EXPANDSTR(x) #x
#define STRINGIFY(x) EXPANDSTR(x)
/* bottom page is a guard page, so ignore it -- consider only top 8KB */
#define DSTACK_SIZE (8*1024)
/* N.B.: dependent on exact DR offsets here! */
#ifdef UNIX
/* this used to be 44 prior to 1/24/06 commit, and 20 prior to Mar 11 2006 */
# define DCONTEXT_TLS_OFFSET IF_X64_ELSE(32, 16)
# define DSTACK_OFFSET_IN_DCONTEXT IF_X64_ELSE(0x2e8,0x168)
# ifdef X64
# define GET_DCONTEXT(var) \
asm("mov %%gs:"STRINGIFY(DCONTEXT_TLS_OFFSET)", %%rax" : : : "rax"); \
asm("mov %%rax, %0" : "=m"((var)));
# else
# define GET_DCONTEXT(var) \
asm("movl %%fs:"STRINGIFY(DCONTEXT_TLS_OFFSET)", %%eax" : : : "eax"); \
asm("movl %%eax, %0" : "=m"((var)));
# endif
#else
unsigned int dcontext_tls_offset;
# define DSTACK_OFFSET_IN_DCONTEXT IF_X64_ELSE(0x1a8,0x164)
# define GET_DCONTEXT(var) \
var = (void *) IF_X64_ELSE(__readgsqword,__readfsdword)(dcontext_tls_offset);
/* 0:001> dt getdc owning_thread
* +0x05c owning_thread
*/
# define OWNING_THREAD_OFFSET_IN_DCONTEXT IF_X64_ELSE(0x218,0x19c)
/* offset varies based on release/debug build (# of slots we need)
* and cache line size (must be aligned) and the -ibl_table_in_tls
* option being set to true
*/
#endif
SIGJMP_BUF mark;
int where; /* 0 = normal, 1 = segfault SIGLONGJMP, 2 = evil takeover */
#ifdef UNIX
static void
signal_handler(int sig)
{
if (sig == SIGSEGV) {
#if VERBOSE
print("Got seg fault\n");
#endif
SIGLONGJMP(mark, 1);
}
exit(-1);
}
#else
/* sort of a hack to avoid the MessageBox of the unhandled exception spoiling
* our batch runs
*/
# include <windows.h>
/* top-level exception handler */
static LONG
our_top_handler(struct _EXCEPTION_POINTERS * pExceptionInfo)
{
if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
# if VERBOSE
/* DR gets the target addr wrong for far call/jmp so we don't print it */
print("\tPC "PFX" tried to %s address "PFX"\n",
pExceptionInfo->ExceptionRecord->ExceptionAddress,
(pExceptionInfo->ExceptionRecord->ExceptionInformation[0]==0)?"read":"write",
pExceptionInfo->ExceptionRecord->ExceptionInformation[1]);
# endif
SIGLONGJMP(mark, 1);
}
# if VERBOSE
print("Exception occurred, process about to die silently\n");
# endif
return EXCEPTION_EXECUTE_HANDLER; /* => global unwind and silent death */
}
#endif
/* global so that evil can use it */
int *dstack_base;
/* the goal is to get DR to jmp to here by clobbering the fcache_return ret addr
* on the dstack
*/
static void
evil()
{
/* popf of DR's eflags (in old DR design) w/ our clobbered value could
* set some funny flags -- clear them all here
*/
clear_eflags();
/* don't trusk stack -- hopefully enough there to call SIGLONGJMP
* certainly can't return from this function since not called
*/
SIGLONGJMP(mark, 2);
}
int
main()
{
int *pc;
int release_build = 0; /* 1 == release, 0 == debug */
void *dcontext;
int *dstack;
int tls_offs;
ptr_int_t owning_thread;
INIT();
#ifdef UNIX
intercept_signal(SIGSEGV, (handler_3_t) signal_handler, false);
#else
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER) our_top_handler);
#endif
#ifdef WINDOWS
/* brute force loop over all TLS entries,
* and see whether owning_thread is GetCurrentThreadId()
* 0:001> dt getdc owning_thread
* +0x05c owning_thread : 0xed8
*
* 0:001> dt _TEB TLS64
* +0xe10 TLS64 : [64] Ptr32 Void
*/
for (tls_offs = 63; tls_offs >=0; tls_offs--) {
enum {offsetof_TLS64_in_TEB = IF_X64_ELSE(0x1480, 0xe10)};
dcontext_tls_offset = offsetof_TLS64_in_TEB +
tls_offs*sizeof(void*);
GET_DCONTEXT(dcontext);
#if VERBOSE
print("%d idx, %x offs\n", tls_offs, dcontext_tls_offset);
#endif
where = SIGSETJMP(mark);
if (where == 0) {
owning_thread = *(ptr_int_t *)(((char *)dcontext) +
OWNING_THREAD_OFFSET_IN_DCONTEXT);
/* we didn't crash reading, is it really thread ID? */
#if VERBOSE
print(" %d thread %d vs %d\n", tls_offs, owning_thread, GetCurrentThreadId());
#endif
if (owning_thread == GetCurrentThreadId()) {
#if VERBOSE
print(" %d is dcontext!\n", tls_offs);
#endif
break;
}
} else {
#if VERBOSE
print(" %d crashed\n", tls_offs);
#endif
/* we crashed reading, try next offset */
}
}
if (tls_offs < 0) {
print("error obtaining dcontext: are you running natively?!?\n");
exit(1);
}
#endif
where = SIGSETJMP(mark);
if (where != 0) {
print("error obtaining dcontext: are you running natively?!?\n");
exit(1);
}
GET_DCONTEXT(dcontext)
#if VERBOSE
print("dcontext is "PFX"\n", dcontext);
#endif
dstack = *(int **)(((char *)dcontext) + DSTACK_OFFSET_IN_DCONTEXT);
if (dstack == NULL || !ALIGNED(dstack, PAGE_SIZE)) {
print("can't find dstack: old build, or new where dstack offset changed?\n");
while (1)
;
exit(-1);
}
dstack_base = (int *) (((char *)dstack) - DSTACK_SIZE);
#if VERBOSE
print("dstack is "PFX"-"PFX"\n", dstack_base, dstack);
#endif
print("dcontext->dstack successfully obtained\n");
where = SIGSETJMP(mark);
#if VERBOSE
print("SIGSETJMP returned %d\n", where);
#endif
if (where == 0) {
/* if we do the copy in a C loop, trace heads cause us to exit before
* we've hit the cxt switch return address, so we crash rather than taking
* control -- so we hand-code a copy that in C looks like this:
* for (pc = dstack_base; pc++; pc < dstack)
* *pc = (int) evil;
* we assume df is cleared
* FIXME: popf in old fcache_return can trigger a trap crash before
* get to ret that goes to evil!
* FIXME: I had this getting to evil w/o crashing first, but it's
* a little fragile, and on win32 I get issues later b/c we have
* trampolines, etc. and so don't completely lose control.
* But, in all cases we fail, so whether it's a nice shell code
* execution or a crash doesn't matter -- the test does what it's supposed
* to do!
*/
evil_copy(dstack_base, DSTACK_SIZE / sizeof(int), (ptr_int_t)evil);
print("wrote to entire dstack without incident!\n");
} else if (where == 1) {
print("error writing to "PFX" in expected dstack "PFX"-"PFX"\n",
pc, dstack_base, dstack);
} else if (where == 2) {
print("DR has been cracked! Malicious code is now runnning...\n");
}
}
#else /* asm code *************************************************************/
#include "asm_defines.asm"
START_FILE
#define FUNCNAME clear_eflags
DECLARE_FUNC(FUNCNAME)
GLOBAL_LABEL(FUNCNAME:)
/* We don't bother w/ SEH64 directives, though we're an illegal leaf routine! */
push 0
popf
ret
END_FUNC(FUNCNAME)
/* void evil_copy(void *start, int count, ptr_int_t value); */
#undef FUNCNAME
#define FUNCNAME evil_copy
DECLARE_FUNC_SEH(FUNCNAME)
GLOBAL_LABEL(FUNCNAME:)
/* Must use ARG macros before any pushes; but must preserve xdi */
mov REG_XAX, ARG3
mov REG_XCX, ARG1
mov REG_XDX, ARG2
PUSH_SEH(REG_XDI)
END_PROLOG
xchg REG_XDX, REG_XCX /* contortions b/c of x64 param regs */
mov REG_XDI, REG_XDX
rep stosd
add REG_XSP, 0 /* make a legal SEH64 epilog */
pop REG_XDI
ret
END_FUNC(FUNCNAME)
END_FILE
#endif