blob: 77f46fd6c21be2bccde5c2dd6623f4cded34889f [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2008 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.
*/
/* Usage :
* Should be run in DR_MODE_MEMORY_FIREWALL mode.
*
* Expects a single -client_ops option: the full absolute path to a configuration file
* (for example '-client_ops "C:\MF_moduledb_VIPA\MF_moduledb-sample.config"').
*
* The configuration file is specified as a series of table_value_t structures defined
* below with no line breaks or extra padding. Each table_value_t structure consists
* of a module_name padded with spaces to MAXIMUM_PATH in length followed by three
* 'y' or 'n' letters specifying the allow_to_stack, allow_to_heap, and allow_to_here
* modes.
*
* If allow_to_stack is 'y', execution is allowed to go from the module specified to
* a violating location on the stack. If allow_to_heap is 'y', execution is allowed to
* go from the module specified to a violating location on the heap. If allow_to_here is
* 'y', violating transfers targeting the module are allowed.
*
* A sample config file is included: MF_moduledb-sample.config that is set up to work
* with VIPA_test.exe (also in the sample folder). The VIPA_test.exe program has two
* buttons, one to generate a stack overflow attack and one to generate a heap
* overflow attack. The sample config file MF_moduledb-sample.config is set up to
* allow the heap attack, but not the stack attack. To demonstrate :
*
* Use drdeploy.exe to configure VIPA_test.exe to run under security_api mode with
* the appropriate options.
* drdeploy.exe -reg VIPA_test.exe -root <root path> -mode security_api -client <path
* to MF_moduledb.dll> -ops "-client_ops <path to MF_moduledb-sample.config>"
*
* Then run VIPA_test.exe. Clicking on the heap attack button should produce messages
* that a potential security violation is being allowed. Clicking on the stack attack
* button should produce a message that a potential security violation is being blocked
* by killing the process.
*
* Use 'drdeploy.exe -nudge VIPA_text.exe' to nudge the process to re-read the
* configuration file.
*
* The #define VERBOSE and #define VVERBOSE can be adjusted below to generate more
* verbose logging.
*/
#include "dr_api.h"
#define VERBOSE 0
#define VVERBOSE 0
#define USE_MESSAGEBOX 1
#define NAME "MF_moduledb"
#ifdef SHOW_RESULTS
# if defined(WINDOWS) && USE_MESSAGEBOX
# define DISPLAY_FUNC dr_messagebox
# else /* no messageboxes, use dr_printf() instead */
# define DISPLAY_FUNC dr_printf
# endif
#else
# define DISPLAY_FUNC dummy_func
#endif
#if VERBOSE
# define VDISPLAY_FUNC DISPLAY_FUNC
#else
# define VDISPLAY_FUNC dummy_func
#endif
#if VVERBOSE
# define VVDISPLAY_FUNC DISPLAY_FUNC
#else
# define VVDISPLAY_FUNC dummy_func
#endif
/* used to avoid displaying a string */
static void
dummy_func(char *fmt, ...)
{
}
typedef struct {
char module_name[MAXIMUM_PATH];
/* exempt transfers to violating stack regions from this module */
char allow_to_stack; /* either 'y' or 'n' */
/* exempt transfers to violating heap regions from this module */
char allow_to_heap; /* either 'y' or 'n' */
/* exempt violating transfers to this module */
char allow_to_here; /* either 'y' or 'n' */
/* Additional relaxation options such as allowing violating code origins regions
* within this module (some .data sections for ex.) could be added here. */
} table_value_t;
struct _table_entry_t;
typedef struct _table_entry_t table_entry_t;
struct _table_entry_t {
table_value_t value; /* the entry we read out of the config file */
table_entry_t *next; /* table is just a singly linked list */
}; /* table_entry_t */
static const char *table_def_file_name; /* name of configuration file */
static void *table_lock; /* for multithreaded access to the table */
static table_entry_t *table = NULL; /* table of relaxations */
static void
event_security_violation(void *drcontext, void *source_tag, app_pc source_pc,
app_pc target_pc, dr_security_violation_type_t violation,
dr_mcontext_t *mcontext, dr_security_violation_action_t *action);
static void
event_nudge(void *drcontext, uint64 argument);
static void
event_about_to_terminate_nudges(int total_non_app_threads, int live_non_app_threads);
static void
event_exit(void);
static table_entry_t *
get_entry_for_address(app_pc addr);
static void
read_table();
static void
free_table();
DR_EXPORT void
dr_client_main(client_id_t id, int argc, const char *argv[])
{
dr_set_client_name("DynamoRIO Sample Client 'MF_moduledb'",
"http://dynamorio.org/issues");
VDISPLAY_FUNC(NAME " initializing.");
/* register the events we wish to handle */
dr_register_security_event(event_security_violation);
dr_register_nudge_event(event_nudge, id);
dr_register_exit_event(event_exit);
/* read the client options */
table_def_file_name = dr_get_options(id);
if (table_def_file_name == NULL || table_def_file_name[0] == '\0') {
DISPLAY_FUNC(NAME " requires the table name as parameter\n");
dr_abort();
}
/* initialize structures */
table_lock = dr_mutex_create();
read_table();
}
static void
event_exit(void)
{
VDISPLAY_FUNC(NAME " exiting.");
/* free structures */
free_table();
dr_mutex_destroy(table_lock);
}
static void
event_nudge(void *drcontext, uint64 argument)
{
DISPLAY_FUNC(NAME " received nudge event; re-reading config file.");
/* An external process has nudged us with dr_nudge_process() telling us
* to re-read the configuration file. */
dr_mutex_lock(table_lock);
free_table();
read_table();
dr_mutex_unlock(table_lock);
}
#ifdef WINDOWS
/* Lookup the module containing addr and see if we have a table entry for it */
static table_entry_t *
get_entry_for_address(app_pc addr)
{
module_data_t *data = dr_lookup_module(addr);
table_entry_t *entry = NULL;
if (data != NULL) {
entry = table;
while (entry != NULL &&
_stricmp(dr_module_preferred_name(data), entry->value.module_name) != 0) {
entry = entry->next;
}
dr_free_module_data(data);
}
return entry;
}
#endif
static void
event_security_violation(void *drcontext, void *source_tag, app_pc source_pc,
app_pc target_pc, dr_security_violation_type_t violation,
dr_mcontext_t *mcontext, dr_security_violation_action_t *action)
{
/* A potential security violation was detected. */
char *violation_str = NULL; /* a name for this violation */
bool allow = false; /* should we let execution continue */
#ifdef WINDOWS
/* find the module we came from */
app_pc source = (source_pc == NULL ? source_tag : source_pc);
table_entry_t *entry;
/* check our source relaxations */
entry = get_entry_for_address(source);
if (entry != NULL) {
/* we have a match, check the relaxations */
if (violation == DR_RCO_STACK_VIOLATION &&
(entry->value.allow_to_stack == 'y' || entry->value.allow_to_stack == 'Y')) {
allow = true;
}
if (violation == DR_RCO_HEAP_VIOLATION &&
(entry->value.allow_to_heap == 'y' || entry->value.allow_to_heap == 'Y')) {
allow = true;
}
}
/* check our target relaxations */
entry = get_entry_for_address(target_pc);
if (entry != NULL) {
/* we have a match, check the relaxations */
if ((violation == DR_RCT_RETURN_VIOLATION ||
violation == DR_RCT_INDIRECT_CALL_VIOLATION ||
violation == DR_RCT_INDIRECT_JUMP_VIOLATION) &&
(entry->value.allow_to_here == 'y' || entry->value.allow_to_here == 'Y')) {
allow = true;
}
}
#endif
switch (violation) {
case DR_RCO_STACK_VIOLATION: violation_str = "stack execution violation"; break;
case DR_RCO_HEAP_VIOLATION: violation_str = "heap execution violation"; break;
case DR_RCT_RETURN_VIOLATION: violation_str = "return target violation"; break;
case DR_RCT_INDIRECT_CALL_VIOLATION: violation_str = "call transfer violation"; break;
case DR_RCT_INDIRECT_JUMP_VIOLATION: violation_str = "jump transfer violation"; break;
default: violation_str = "unknown"; break;
}
if (allow)
*action = DR_VIOLATION_ACTION_CONTINUE;
/* could use dr_write_forensics_report() here to log additional information */
DISPLAY_FUNC("WARNING - possible security violation \"%s\" detected, %s.",
violation_str, allow ? "allowing" : "blocking");
}
static void
read_table()
{
file_t file;
bool read_entry = true;
file = dr_open_file(table_def_file_name, DR_FILE_READ);
if (file == INVALID_FILE) {
DISPLAY_FUNC(NAME " error opening config file \"%s\"\n", table_def_file_name);
return;
}
VVDISPLAY_FUNC(NAME " reading config file: \"%s\"\n", table_def_file_name);
do {
table_entry_t *entry = (table_entry_t *)dr_global_alloc(sizeof(table_entry_t));
if (dr_read_file(file, &entry->value, sizeof(table_value_t)) !=
sizeof(table_value_t)) {
/* end of file */
read_entry = false;
dr_global_free(entry, sizeof(table_entry_t));
} else {
int i;
/* insert NULL termination for module name (including space padding) */
for (i = sizeof(entry->value.module_name) - 1;
i >= 0 && entry->value.module_name[i] == ' '; i--) {
entry->value.module_name[i] = '\0';
}
/* just in case */
entry->value.module_name[sizeof(entry->value.module_name) - 1] = '\0';
/* add to the table */
entry->next = table;
table = entry;
VVDISPLAY_FUNC(
NAME " read entry for module=\"%s\" to_stack=%s to_heap=%s "
"transfer_to_here=%s\n",
entry->value.module_name,
(entry->value.allow_to_stack == 'y' || entry->value.allow_to_stack == 'Y')
? "yes"
: "no",
(entry->value.allow_to_heap == 'y' || entry->value.allow_to_heap == 'Y')
? "yes"
: "no",
(entry->value.allow_to_here == 'y' || entry->value.allow_to_here == 'Y')
? "yes"
: "no");
}
} while (read_entry);
VVDISPLAY_FUNC(NAME " done reading config file.");
}
static void
free_table()
{
/* Free all table entries */
table_entry_t *next;
while (table != NULL) {
next = table->next;
dr_global_free(table, sizeof(table_entry_t));
table = next;
}
}