blob: 37f0494e057e326fe834d6c36cf04062bffe50ad [file]
/* **********************************************************
* Copyright (c) 2010-2025 Google, Inc. All rights reserved.
* Copyright (c) 2007-2010 VMware, Inc. All rights reserved.
* **********************************************************/
/* Dr. Memory: the memory debugger
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License, and no later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* Dr. Syscall top-level code */
#include "dr_api.h"
#include "drmgr.h"
#include "drsyscall.h"
#include "drsyscall_os.h"
#include "drmemory_framework.h"
#include "../framework/drmf.h"
#include "utils.h"
#include <string.h>
#include <stddef.h> /* for offsetof */
/* for handle_sockaddr()
* XXX: should we move it to a new drsyscall_shared.c to avoid having
* all these platform-specific includes in the main file?
*/
#ifdef UNIX
# include <sys/types.h>
# include <sys/socket.h>
# ifdef MACOS
# include <netinet/in.h>
# include <sys/un.h>
# else
# include <linux/in.h>
# include <linux/in6.h>
# include <linux/un.h>
# include <linux/netlink.h>
# endif
#else
# include "../wininc/afd_shared.h"
#endif
#ifdef SYSCALL_DRIVER
# include "syscall_driver.h"
#endif
/* Keep this in synch with drsys_param_type_t */
const char * const param_type_names[] = {
"<invalid>", /* DRSYS_TYPE_INVALID */
"<unknown>", /* DRSYS_TYPE_UNKNOWN */
"void", /* DRSYS_TYPE_VOID */
"bool", /* DRSYS_TYPE_BOOL */
"int", /* DRSYS_TYPE_INT */
"int", /* DRSYS_TYPE_SIGNED_INT */
"unsigned int", /* DRSYS_TYPE_UNSIGNED_INT */
"size_t", /* DRSYS_TYPE_SIZE_T */
"HANDLE", /* DRSYS_TYPE_HANDLE */
"NTSTATUS", /* DRSYS_TYPE_NTSTATUS */
"ATOM", /* DRSYS_TYPE_ATOM */
"LCID", /* DRSYS_TYPE_LCID */
"LPARAM", /* DRSYS_TYPE_LPARAM */
"HMODULE", /* DRSYS_TYPE_HMODULE */
"HFILE", /* DRSYS_TYPE_HFILE */
"void *", /* DRSYS_TYPE_POINTER */
"<struct>", /* DRSYS_TYPE_STRUCT */
"char *", /* DRSYS_TYPE_CSTRING */
"wchar_t *", /* DRSYS_TYPE_CWSTRING */
"char[]", /* DRSYS_TYPE_CARRAY */
"wchar_t[]", /* DRSYS_TYPE_CWARRAY */
"char **", /* DRSYS_TYPE_CSTRARRAY */
"UNICODE_STRING", /* DRSYS_TYPE_UNICODE_STRING */
"LARGE_STRING", /* DRSYS_TYPE_LARGE_STRING */
"OBJECT_ATTRIBUTES", /* DRSYS_TYPE_OBJECT_ATTRIBUTES */
"SECURITY_DESCRIPTOR", /* DRSYS_TYPE_SECURITY_DESCRIPTOR */
"SECURITY_QOS", /* DRSYS_TYPE_SECURITY_QOS */
"PORT_MESSAGE", /* DRSYS_TYPE_PORT_MESSAGE */
"CONTEXT", /* DRSYS_TYPE_CONTEXT */
"EXCEPTION_RECORD", /* DRSYS_TYPE_EXCEPTION_RECORD */
"DEVMODEW", /* DRSYS_TYPE_DEVMODEW */
"WNDCLASSEXW", /* DRSYS_TYPE_WNDCLASSEXW */
"CLSMENUNAME", /* DRSYS_TYPE_CLSMENUNAME */
"MENUITEMINFOW", /* DRSYS_TYPE_MENUITEMINFOW */
"ALPC_PORT_ATTRIBUTES", /* DRSYS_TYPE_ALPC_PORT_ATTRIBUTES */
"ALPC_SECURITY_ATTRIBUTES", /* DRSYS_TYPE_ALPC_SECURITY_ATTRIBUTES */
"LOGFONTW", /* DRSYS_TYPE_LOGFONTW */
"NONCLIENTMETRICSW", /* DRSYS_TYPE_NONCLIENTMETRICSW */
"ICONMETRICSW", /* DRSYS_TYPE_ICONMETRICSW */
"SERIALKEYSW", /* DRSYS_TYPE_SERIALKEYSW */
"struct sockaddr", /* DRSYS_TYPE_SOCKADDR */
"struct msghdr", /* DRSYS_TYPE_MSGHDR */
"struct msgbuf", /* DRSYS_TYPE_MSGBUF */
"LARGE_INTEGER", /* DRSYS_TYPE_LARGE_INTEGER */
"ULARGE_INTEGER", /* DRSYS_TYPE_ULARGE_INTEGER */
"IO_STATUS_BLOCK", /* DRSYS_TYPE_IO_STATUS_BLOCK */
"<function>", /* DRSYS_TYPE_FUNCTION */
"BITMAPINFO", /* DRSYS_TYPE_BITMAPINFO */
"ALPC_CONTEXT_ATTRIBUTES", /* DRSYS_TYPE_ALPC_CONTEXT_ATTRIBUTES */
"ALPC_MESSAGE_ATTRIBUTES", /* DRSYS_TYPE_ALPC_MESSAGE_ATTRIBUTES */
"T2_SET_PARAMETERS", /* DRSYS_TYPE_T2_SET_PARAMETERS */
};
#define NUM_PARAM_TYPE_NAMES \
(sizeof(param_type_names)/sizeof(param_type_names[0]))
int cls_idx_drsys = -1;
drsys_options_t drsys_ops;
static int drsys_init_count;
void *systable_lock;
static drsys_param_type_t
map_to_exported_type(uint sysarg_type, size_t *sz_out DR_PARAM_OUT);
/***************************************************************************
* SYSTEM CALLS
*/
static drsys_gateway_t syscall_gateway = DRSYS_GATEWAY_UNKNOWN;
DR_EXPORT
drmf_status_t
drsys_syscall_gateway(drsys_gateway_t *method)
{
if (method == NULL)
return DRMF_ERROR_INVALID_PARAMETER;
*method = syscall_gateway;
return DRMF_SUCCESS;
}
bool
is_using_sysenter(void)
{
return (syscall_gateway == DRSYS_GATEWAY_SYSENTER);
}
/* we assume 1st syscall reflects primary gateway */
bool
is_using_sysint(void)
{
return (syscall_gateway == DRSYS_GATEWAY_INT || syscall_gateway == DRSYS_GATEWAY_SVC);
}
#ifdef WINDOWS
bool
is_using_wow64(void)
{
return (syscall_gateway == DRSYS_GATEWAY_WOW64);
}
#endif
static void
check_syscall_gateway(instr_t *inst)
{
#ifdef X86
if (instr_get_opcode(inst) == OP_sysenter) {
if (syscall_gateway == DRSYS_GATEWAY_UNKNOWN
/* some syscalls use int, but consider sysenter the primary */
IF_UNIX(|| syscall_gateway == DRSYS_GATEWAY_INT))
syscall_gateway = DRSYS_GATEWAY_SYSENTER;
else {
ASSERT(syscall_gateway == DRSYS_GATEWAY_SYSENTER,
"multiple system call gateways not supported");
}
} else if (instr_get_opcode(inst) == OP_syscall) {
if (syscall_gateway == DRSYS_GATEWAY_UNKNOWN ||
syscall_gateway == DRSYS_GATEWAY_INT)
syscall_gateway = DRSYS_GATEWAY_SYSCALL;
else {
ASSERT(syscall_gateway == DRSYS_GATEWAY_SYSCALL
/* some syscalls use int */
IF_UNIX(|| syscall_gateway == DRSYS_GATEWAY_INT),
"multiple system call gateways not supported");
}
} else if (instr_get_opcode(inst) == OP_int) {
if (syscall_gateway == DRSYS_GATEWAY_UNKNOWN)
syscall_gateway = DRSYS_GATEWAY_INT;
else {
ASSERT(syscall_gateway == DRSYS_GATEWAY_INT
IF_UNIX(|| syscall_gateway == DRSYS_GATEWAY_SYSENTER
|| syscall_gateway == DRSYS_GATEWAY_SYSCALL),
"multiple system call gateways not supported");
}
#elif defined(ARM) || defined(AARCH64)
if (instr_get_opcode(inst) == OP_svc) {
if (syscall_gateway == DRSYS_GATEWAY_UNKNOWN)
syscall_gateway = DRSYS_GATEWAY_SVC;
else {
ASSERT(syscall_gateway == DRSYS_GATEWAY_SVC,
"multiple system call gateways not supported");
}
#endif
#ifdef WINDOWS
} else if (instr_is_wow64_syscall(inst)) {
if (syscall_gateway == DRSYS_GATEWAY_UNKNOWN)
syscall_gateway = DRSYS_GATEWAY_WOW64;
else {
ASSERT(syscall_gateway == DRSYS_GATEWAY_WOW64,
"multiple system call gateways not supported");
}
#endif
} else
ASSERT(false, "unknown system call gateway");
}
DR_EXPORT
drmf_status_t
drsys_number_to_syscall(drsys_sysnum_t sysnum, drsys_syscall_t **syscall DR_PARAM_OUT)
{
syscall_info_t *sysinfo = syscall_lookup(sysnum, true/*resolve 2ndary*/);
if (syscall == NULL)
return DRMF_ERROR_INVALID_PARAMETER;
/* All unknown-detail syscalls are now in the tables, so we only return
* NULL on error.
*/
if (sysinfo == NULL)
return DRMF_ERROR_NOT_FOUND;
*syscall = (drsys_syscall_t *) sysinfo;
return DRMF_SUCCESS;
}
DR_EXPORT
drmf_status_t
drsys_name_to_syscall(const char *name, drsys_syscall_t **syscall DR_PARAM_OUT)
{
drsys_sysnum_t sysnum;
syscall_info_t *sysinfo;
bool ok;
#ifdef DEBUG
uint offset = 0;
#endif
if (name == NULL || syscall == NULL)
return DRMF_ERROR_INVALID_PARAMETER;
ok = os_syscall_get_num(name, &sysnum);
if (!ok)
return DRMF_ERROR_NOT_FOUND;
/* We're looking for secondary table here b/c there are usercalls
* without primary name (e.g. ReleaseDC) which should be looked
* in secondary table.
*/
sysinfo = syscall_lookup(sysnum, true/*resolve 2ndary*/);
if (sysinfo == NULL) {
ASSERT(false, "name2num should return num in systable");
return DRMF_ERROR_NOT_FOUND;
}
#ifdef DEBUG
#ifdef WINDOWS
/* ignore possible Nt/Zw mismatch */
if (((sysinfo->name[0] == 'N' && sysinfo->name[1] == 't') ||
(sysinfo->name[0] == 'Z' && sysinfo->name[1] == 'w')) &&
((name[0] == 'N' && name[1] == 't') || (name[0] == 'Z' && name[1] == 'w')))
offset = 2;
#endif
ASSERT(stri_eq(sysinfo->name + offset, name + offset)
IF_WINDOWS(||
/* account for NtUser*, etc. prefix differences, but only on
* Windows b/c strcasestr's tolower is undef on Linux
*/
strcasestr(sysinfo->name + offset, name + offset) != NULL),
"name<->num mismatch");
#endif
*syscall = (drsys_syscall_t *) sysinfo;
return DRMF_SUCCESS;
}
/* to avoid heap-allocated data we use pointers to temporary drsys_sysnum_t */
uint
sysnum_hash(void *val)
{
drsys_sysnum_t *num = (drsys_sysnum_t *) val;
/* Most primaries are < 0x3fff and secondaries are < 0x1ff so we
* simply combine the most-likely-meaningful bits.
*/
return (num->secondary << 14) | num->number;
}
/* to avoid heap-allocated data we use pointers to temporary drsys_sysnum_t */
bool
sysnum_cmp(void *v1, void *v2)
{
drsys_sysnum_t *num1 = (drsys_sysnum_t *) v1;
drsys_sysnum_t *num2 = (drsys_sysnum_t *) v2;
return drsys_sysnums_equal(num1, num2);
}
syscall_info_t *
syscall_lookup(drsys_sysnum_t num, bool resolve_secondary)
{
/* The common case is lookup for syscalls without secondary component,
* which requires only one hashtable lookup. So we pay a cost of second
* lookup only if user queries it.
*/
syscall_info_t *res = NULL;
/* First we look for secondary table to avoid collision with primary table
* in case when user looks for secondary table for entry with .0 secondary num.
*/
dr_recurlock_lock(systable_lock);
if (resolve_secondary) {
res = (syscall_info_t *) hashtable_lookup(&secondary_systable, (void *) &num);
}
if (res == NULL) {
res = (syscall_info_t *) hashtable_lookup(&systable, (void *) &num);
}
dr_recurlock_unlock(systable_lock);
return res;
}
/***************************************************************************
* UNKNOWN SYSCALL HANDLING
*/
static const byte UNKNOWN_SYSVAL_SENTINEL = 0xab;
static const syscall_info_t unknown_info_template =
{{0,0},"<unknown>", 0/*UNKNOWN*/, DRSYS_TYPE_UNKNOWN, };
DR_EXPORT
drmf_status_t
drsys_syscall_is_known(drsys_syscall_t *syscall, bool *known DR_PARAM_OUT)
{
syscall_info_t *sysinfo = (syscall_info_t *) syscall;
if (syscall == NULL || known == NULL)
return DRMF_ERROR_INVALID_PARAMETER;
*known = TEST(SYSINFO_ALL_PARAMS_KNOWN, sysinfo->flags);
return DRMF_SUCCESS;
}
static bool
is_byte_addressable(byte *addr)
{
if (drsys_ops.is_byte_addressable == NULL)
return true; /* have to assume it is */
else
return (*drsys_ops.is_byte_addressable)(addr);
}
static bool
is_byte_defined(byte *addr)
{
if (drsys_ops.is_byte_defined == NULL)
return is_byte_addressable(addr); /* have to assume it is */
else
return (*drsys_ops.is_byte_defined)(addr);
}
static bool
is_byte_undefined(byte *addr)
{
if (drsys_ops.is_byte_undefined == NULL) {
if (drsys_ops.is_byte_defined != NULL &&
drsys_ops.is_byte_addressable != NULL) {
return ((*drsys_ops.is_byte_addressable)(addr) &&
!(*drsys_ops.is_byte_defined)(addr));
} else
return false; /* have to assume it's not */
} else
return (*drsys_ops.is_byte_undefined)(addr);
}
static bool
is_register_defined(reg_id_t reg)
{
if (drsys_ops.is_register_defined == NULL)
return true; /* have to assume it is */
else
return (*drsys_ops.is_register_defined)(reg);
}
/* For syscall we do not have specific parameter info for, we do a
* memory comparison to find what has been written.
* We will not catch passing undefined values in that are read, of course.
*/
static void
handle_pre_unknown_syscall(void *drcontext, cls_syscall_t *cpt,
sysarg_iter_info_t *ii)
{
app_pc start;
int i, j;
bool defined;
drsys_arg_t arg_loc = *ii->arg; /* set up mc, etc. */
IF_DEBUG(drsys_sysnum_t sysnum = ii->arg->sysnum;)
IF_DEBUG(syscall_info_t *sysinfo = cpt->sysinfo;)
if (!drsys_ops.analyze_unknown_syscalls)
return;
LOG(SYSCALL_VERBOSE, "unknown system call #"SYSNUM_FMT"."SYSNUM_FMT" %s\n",
sysnum.number, sysnum.secondary, sysinfo == NULL ? "" : sysinfo->name);
/* PR 484069: reduce global logfile size */
DO_ONCE(ELOGF(0, f_global, "WARNING: unhandled system calls found\n"));
for (i=0; i<SYSCALL_NUM_ARG_TRACK; i++) {
cpt->sysarg_ptr[i] = NULL;
drsyscall_os_get_sysparam_location(cpt, i, &arg_loc);
if (arg_loc.reg != DR_REG_NULL)
defined = is_register_defined(arg_loc.reg);
else
defined = is_byte_defined(arg_loc.start_addr);
if (defined) {
/* No need for a TRY/EXCEPT b/c this mem addr is defined */
start = (app_pc) dr_syscall_get_param(drcontext, i);
LOG(2, "pre-unknown-syscall #"SYSNUM_FMT"."SYSNUM_FMT": param %d == "PFX"\n",
sysnum.number, sysnum.secondary, i, start);
if (ALIGNED(start, 4) && is_byte_addressable(start)) {
/* This looks like a memory parameter. It might contain OUT
* values mixed with DR_PARAM_IN, so we do not stop at the first undefined
* byte: instead we stop at an unaddr or at the max size.
* We need two passes to know how far we can safely read,
* so we go ahead and use dynamically sized memory as well.
*/
byte *s_at = NULL;
int prev;
bool overlap = false;
for (j=0; j<SYSCALL_ARG_TRACK_MAX_SZ; j++) {
for (prev=0; prev<i; prev++) {
if (cpt->sysarg_ptr[prev] < start + j &&
cpt->sysarg_ptr[prev] + cpt->sysarg_sz[prev] > start) {
/* overlap w/ prior arg. while we could miss some
* data due to the max sz we just bail for simplicity.
*/
overlap = true;
break;
}
}
if (overlap || !is_byte_addressable(start + j))
break;
}
if (j > 0) {
LOG(SYSCALL_VERBOSE,
"pre-unknown-syscall #"PIFX": param %d == "PFX" %d bytes\n",
sysnum, i, start, j);
/* Make a copy of the arg values */
if (j > cpt->sysarg_val_bytes[i]) {
if (cpt->sysarg_val_bytes[i] > 0) {
thread_free(drcontext, cpt->sysarg_val[i],
cpt->sysarg_val_bytes[i], HEAPSTAT_MISC);
} else
ASSERT(cpt->sysarg_val[i] == NULL, "leak");
cpt->sysarg_val_bytes[i] = ALIGN_FORWARD(j, 64);
cpt->sysarg_val[i] =
thread_alloc(drcontext, cpt->sysarg_val_bytes[i],
HEAPSTAT_MISC);
}
if (safe_read(start, j, cpt->sysarg_val[i])) {
cpt->sysarg_ptr[i] = start;
cpt->sysarg_sz[i] = j;
} else {
LOG(SYSCALL_VERBOSE,
"WARNING: unable to read syscall arg "PFX"-"PFX"!\n",
start, start + j);
cpt->sysarg_sz[i] = 0;
}
}
if (drsys_ops.syscall_sentinels) {
for (j=0; j<cpt->sysarg_sz[i]; j++) {
if (is_byte_undefined(start + j)) {
/* Detect writes to data that happened to have the same
* value beforehand (happens often with 0) by writing
* a sentinel.
* XXX: want more-performant safe write on Windows:
* xref PR 605237
* XXX: another thread could read the data (after
* all we're not sure it's really syscall data) and
* unexpectedly read the sentinel value
*/
if (s_at == NULL)
s_at = start + j;
if (!dr_safe_write(start + j, 1,
&UNKNOWN_SYSVAL_SENTINEL, NULL)) {
/* if page is read-only then assume rest is not OUT */
LOG(1, "WARNING: unable to write sentinel value @"PFX"\n",
start + j);
break;
}
} else if (s_at != NULL) {
LOG(2, "writing sentinel value to "PFX"-"PFX" %d %d "PFX"\n",
s_at, start + j, i, j, cpt->sysarg_ptr[i]);
s_at = NULL;
}
}
if (s_at != NULL) {
LOG(2, "writing sentinel value to "PFX"-"PFX"\n", s_at, start + j);
s_at = NULL;
}
}
}
}
}
}
/* If ii is NULL, performs post-syscall final actions */
static void
handle_post_unknown_syscall(void *drcontext, cls_syscall_t *cpt,
sysarg_iter_info_t *ii)
{
int i, j;
byte *w_at = NULL;
byte post_val[SYSCALL_ARG_TRACK_MAX_SZ];
if (!drsys_ops.analyze_unknown_syscalls)
return;
/* we analyze params even if syscall failed, since in some cases
* some params are still written (xref i#486, i#358)
*/
for (i=0; i<SYSCALL_NUM_ARG_TRACK; i++) {
if (cpt->sysarg_ptr[i] != NULL) {
if (safe_read(cpt->sysarg_ptr[i], cpt->sysarg_sz[i], post_val)) {
for (j = 0; j < cpt->sysarg_sz[i]; j++) {
byte *pc = cpt->sysarg_ptr[i] + j;
if (is_byte_undefined(pc)) {
/* kernel could have written sentinel.
* XXX: we won't mark as defined if pre-syscall value
* matched sentinel and kernel wrote sentinel!
*/
LOG(4, "\targ %d "PFX" %d comparing %x to %x\n", i,
cpt->sysarg_ptr[i], j,
post_val[j], cpt->sysarg_val[i][j]);
if ((drsys_ops.syscall_sentinels &&
post_val[j] != UNKNOWN_SYSVAL_SENTINEL) ||
(!drsys_ops.syscall_sentinels &&
post_val[j] != cpt->sysarg_val[i][j])) {
if (w_at == NULL)
w_at = pc;
/* With no other threads this would still be undefined,
* modulo overlapping syscall args. But another thread
* could change it so we don't do a full ASSERT.
*/
DODEBUG({
if (!is_byte_addressable(pc)) {
WARN("WARNING: "PFX" undefined but !addressable", pc);
}
});
if (ii != NULL && drsys_ops.syscall_dword_granularity) {
/* w/o sentinels (which are dangerous) we often miss
* seemingly unchanged bytes (often zero) so mark
* the containing dword (i#477)
*/
report_memarg_type(ii, i, SYSARG_WRITE,
(byte *)ALIGN_BACKWARD(pc, 4), 4, NULL,
DRSYS_TYPE_UNKNOWN, NULL);
} else if (ii != NULL) {
report_memarg_type(ii, i, SYSARG_WRITE, pc, 1, NULL,
DRSYS_TYPE_UNKNOWN, NULL);
}
} else if (ii == NULL /* => restore */) {
if (post_val[j] == UNKNOWN_SYSVAL_SENTINEL &&
cpt->sysarg_val[i][j] != UNKNOWN_SYSVAL_SENTINEL) {
/* kernel didn't write so restore app value that
* we clobbered w/ our sentinel.
*/
LOG(4, "restoring app sysval @"PFX"\n", pc);
if (!dr_safe_write(pc, 1, &cpt->sysarg_val[i][j],
NULL)) {
LOG(1, "WARNING: unable to restore app sysval @"PFX"\n",
pc);
}
}
if (w_at != NULL) {
LOG(SYSCALL_VERBOSE, "unknown-syscall #"SYSNUM_FMT
": param %d written "PFX" %d bytes\n",
(ii == NULL) ? 0 : ii->arg->sysnum.number,
i, w_at, pc - w_at);
w_at = NULL;
}
}
} else {
LOG(4, "\targ %d "PFX" byte %d defined\n", i,
cpt->sysarg_ptr[i], j);
}
}
if (w_at != NULL) {
LOG(SYSCALL_VERBOSE, "unknown-syscall #"SYSNUM_FMT": param %d written "
PFX" %d bytes\n", (ii == NULL) ? 0 : ii->arg->sysnum.number,
i, w_at, (cpt->sysarg_ptr[i] + j) - w_at);
w_at = NULL;
}
} else {
/* If we can't read I assume we are also unable to write to undo
* sentinel writes: though should try since param could span pages
*/
LOG(1, "WARNING: unable to read app sysarg @"PFX"\n", cpt->sysarg_ptr[i]);
}
}
}
}
/***************************************************************************
* QUERY ROUTINES
*/
static drsys_syscall_t *
get_cur_syscall(cls_syscall_t *pt)
{
/* We can't return NULL b/c the caller will pass it to our query routines.
* So we pass a sentinel entry, which is per-thread so we can modify it.
* We only use this for dynamic queries where the caller shouldn't keep
* the pointer around.
*/
if (pt->sysinfo == NULL) {
/* We do need to fill in the syscall number */
memcpy(&pt->unknown_info, &unknown_info_template, sizeof(pt->unknown_info));
pt->unknown_info.num = pt->sysnum;
return (drsys_syscall_t *) &pt->unknown_info;
} else
return (drsys_syscall_t *) pt->sysinfo;
}
DR_EXPORT
drmf_status_t
drsys_cur_syscall(void *drcontext, drsys_syscall_t **syscall DR_PARAM_OUT)
{
cls_syscall_t *pt = (cls_syscall_t *) drmgr_get_cls_field(drcontext, cls_idx_drsys);
if (drcontext == NULL || syscall == NULL)
return DRMF_ERROR_INVALID_PARAMETER;
*syscall = get_cur_syscall(pt);
return DRMF_SUCCESS;
}
DR_EXPORT
drmf_status_t
drsys_syscall_succeeded(drsys_syscall_t *syscall, reg_t result, bool *success DR_PARAM_OUT)
{
#ifdef MACOS
/* XXX: we actually could return a value for Mach syscalls */
return DRMF_ERROR_FEATURE_NOT_AVAILABLE;
#else
syscall_info_t *sysinfo = (syscall_info_t *) syscall;
cls_syscall_t pt;
memset(&pt, 0, sizeof(pt));
if (syscall == NULL || success == NULL)
return DRMF_ERROR_INVALID_PARAMETER;
pt.mc.IF_X86_ELSE(xax,r0) = result; /*r0 for ARM and AARCH64*/
*success = os_syscall_succeeded(sysinfo->num, sysinfo, &pt);
return DRMF_SUCCESS;
#endif
}
static void
get_syscall_result(syscall_info_t *sysinfo, cls_syscall_t *pt,
DR_PARAM_OUT bool *success, DR_PARAM_OUT uint64 *value,
DR_PARAM_OUT uint *error_code)
{
bool res = os_syscall_succeeded(sysinfo->num, sysinfo, pt);
dr_mcontext_t *mc = &pt->mc;
if (success != NULL)
*success = res;
if (value != NULL) {
#ifdef X64
*value = mc->IF_X86_ELSE(rax,r0); /*r0 for AARCH64*/
#else
/* yes, reg_t is unsigned so we have no sign-extension here */
if (TEST(SYSINFO_RET_64BIT, sysinfo->flags))
*value = (uint64)mc->IF_ARM_ELSE(r0,eax) |
((uint64)mc->IF_ARM_ELSE(r1,edx) << 32);
else
*value = (uint64)mc->IF_ARM_ELSE(r0,eax);
#endif
}
if (error_code != NULL) {
if (res)
*error_code = 0;
else {
#ifdef LINUX
*error_code = (uint)-(int)mc->IF_X86_ELSE(xax,r0); /*r0 for ARM and AARCH64*/
#else
*error_code = (uint)mc->xax;
#endif
}
}
}
DR_EXPORT
drmf_status_t
drsys_cur_syscall_result(void *drcontext, DR_PARAM_OUT bool *success,
DR_PARAM_OUT uint64 *value, DR_PARAM_OUT uint *error_code)
{
cls_syscall_t *pt;
syscall_info_t *sysinfo;
if (drcontext == NULL)
return DRMF_ERROR_INVALID_PARAMETER;
pt = (cls_syscall_t *) drmgr_get_cls_field(drcontext, cls_idx_drsys);
sysinfo = (syscall_info_t *) get_cur_syscall(pt);
get_syscall_result(sysinfo, pt, success, value, error_code);
return DRMF_SUCCESS;
}
DR_EXPORT
drmf_status_t
drsys_pre_syscall_arg(void *drcontext, uint argnum, ptr_uint_t *value DR_PARAM_OUT)
{
cls_syscall_t *pt = (cls_syscall_t *) drmgr_get_cls_field(drcontext, cls_idx_drsys);
if (value == NULL || argnum >= SYSCALL_NUM_ARG_STORE)
return DRMF_ERROR_INVALID_PARAMETER;
*value = (ptr_uint_t) pt->sysarg[argnum];
return DRMF_SUCCESS;
}
DR_EXPORT
drmf_status_t
drsys_pre_syscall_arg64(void *drcontext, uint argnum, uint64 *value DR_PARAM_OUT)
{
cls_syscall_t *pt = (cls_syscall_t *) drmgr_get_cls_field(drcontext, cls_idx_drsys);
if (value == NULL || argnum >= SYSCALL_NUM_ARG_STORE)
return DRMF_ERROR_INVALID_PARAMETER;
*value = pt->sysarg[argnum];
return DRMF_SUCCESS;
}
DR_EXPORT
drmf_status_t
drsys_syscall_name(drsys_syscall_t *syscall, const char **name DR_PARAM_OUT)
{
syscall_info_t *sysinfo = (syscall_info_t *) syscall;
if (syscall == NULL || name == NULL)
return DRMF_ERROR_INVALID_PARAMETER;
*name = sysinfo->name;
return DRMF_SUCCESS;
}
DR_EXPORT
drmf_status_t
drsys_syscall_number(drsys_syscall_t *syscall, drsys_sysnum_t *sysnum DR_PARAM_OUT)
{
syscall_info_t *sysinfo = (syscall_info_t *) syscall;
if (syscall == NULL || sysnum == NULL)
return DRMF_ERROR_INVALID_PARAMETER;
*sysnum = sysinfo->num;
return DRMF_SUCCESS;
}
DR_EXPORT
drmf_status_t
drsys_get_mcontext(void *drcontext, dr_mcontext_t **mc DR_PARAM_OUT)
{
cls_syscall_t *pt = (cls_syscall_t *) drmgr_get_cls_field(drcontext, cls_idx_drsys);
if (mc == NULL)
return DRMF_ERROR_INVALID_PARAMETER;
*mc = &pt->mc;
return DRMF_SUCCESS;
}
DR_EXPORT
drmf_status_t
drsys_syscall_return_type(drsys_syscall_t *syscall, drsys_param_type_t *type DR_PARAM_OUT)
{
syscall_info_t *sysinfo = (syscall_info_t *) syscall;
if (syscall == NULL || type == NULL)
return DRMF_ERROR_INVALID_PARAMETER;
/* XXX: should we provide size too? They can iterate to get that. */
*type = map_to_exported_type(sysinfo->return_type, NULL);
return DRMF_SUCCESS;
}
/***************************************************************************
* REGULAR SYSCALL HANDLING
*/
static const char *
arg_type_name(drsys_param_type_t type, const char *type_name)
{
if (type_name == NULL && type != DRSYS_TYPE_UNKNOWN &&
type != DRSYS_TYPE_INVALID) {
ASSERT(type < NUM_PARAM_TYPE_NAMES, "invalid type enum val");
return param_type_names[type];
} else
return type_name;
}
/* Assumes that arg fields on the context (drcontext, sysnum, pre, and
* mc) have already been filled in.
*
* Fills in arg->valid with true.
* XXX: should we get rid of the valid field? For the all-args
* dynamic iterator we use the sysparam addr and don't do a deref; and
* for memargs, not reading usually means not knowing the bounds of a
* sub-field where there's no type or other info and so it's not worth
* invoking the callback.
*
* Sets ii->abort according to return value.
*/
bool
report_memarg_ex(sysarg_iter_info_t *ii,
int ordinal, drsys_param_mode_t mode,
app_pc ptr, size_t sz, const char *id,
drsys_param_type_t type, const char *type_name,
drsys_param_type_t containing_type)
{
drsys_arg_t *arg = ii->arg;
#ifdef UNIX
/* FIXME i#1171: this assertion fails on Windows. */
ASSERT(sz > 0, "drsyscall shouldn't report empty memargs");
#endif
/* Support making handler code simpler by allowing them to invoke us
* w/o conditionals on whether it's an IN param and this is post-syscall.
*/
if (!ii->pt->pre && !TEST(DRSYS_PARAM_OUT, mode))
return true;
arg->type = type;
arg->type_name = arg_type_name(type, type_name);
arg->containing_type = containing_type;
arg->arg_name = id;
arg->ordinal = ordinal;
arg->mode = mode;
arg->reg = DR_REG_NULL;
arg->start_addr = ptr;
arg->size = sz;
/* We can't short-circuit on first iter b/c we have too much code that
* stores extra info in pre for post that's after several reports.
* Thus we just suppress future callbacks on first iter.
*/
if (!ii->abort) {
if (!(*ii->cb_mem)(arg, ii->user_data))
ii->abort = true;
} else {
ASSERT(ii->pt->first_iter,
"other than 1st iter, shouldn't report after abort");
}
return ii->pt->first_iter || !ii->abort;
}
drsys_param_mode_t
mode_from_flags(uint arg_flags)
{
drsys_param_mode_t mode = 0;
if (TEST(SYSARG_WRITE, arg_flags))
mode |= DRSYS_PARAM_OUT;
if (TESTANY(SYSARG_READ|SYSARG_INLINED, arg_flags))
mode |= DRSYS_PARAM_IN;
if (TEST(SYSARG_INLINED, arg_flags))
mode |= DRSYS_PARAM_INLINED;
return mode;
}
static drsys_param_type_t
map_to_exported_type(uint sysarg_type, size_t *sz_out DR_PARAM_OUT)
{
size_t sz = 0;
drsys_param_type_t type = (drsys_param_type_t) sysarg_type;
/* map to exported types */
if (sysarg_type == SYSARG_TYPE_UNICODE_STRING_NOLEN) {
type = DRSYS_TYPE_UNICODE_STRING;
} else if (sysarg_type == SYSARG_TYPE_SINT32) {
type = DRSYS_TYPE_SIGNED_INT;
sz = 4;
} else if (sysarg_type == SYSARG_TYPE_UINT32) {
type = DRSYS_TYPE_UNSIGNED_INT;
sz = 4;
} else if (sysarg_type == SYSARG_TYPE_SINT16) {
type = DRSYS_TYPE_SIGNED_INT;
sz = 2;
} else if (sysarg_type == SYSARG_TYPE_UINT16) {
type = DRSYS_TYPE_UNSIGNED_INT;
sz = 2;
} else if (sysarg_type == SYSARG_TYPE_BOOL8) {
type = DRSYS_TYPE_BOOL;
sz = 1;
} else if (sysarg_type == SYSARG_TYPE_BOOL32) {
type = DRSYS_TYPE_BOOL;
sz = 4;
#ifdef WINDOWS
} else if (sysarg_type == DRSYS_TYPE_NTSTATUS) {
sz = sizeof(NTSTATUS);
#endif
}
ASSERT(type < NUM_PARAM_TYPE_NAMES, "invalid type enum val");
if (sz_out != NULL && sz > 0)
*sz_out = sz;
return type;
}
static drsys_param_type_t
type_from_arg_info(const sysinfo_arg_t *arg_info)
{
drsys_param_type_t type = DRSYS_TYPE_INVALID;
if (SYSARG_MISC_HAS_TYPE(arg_info->flags)) {
/* we don't need size b/c it's encoded in arg_info already */
type = map_to_exported_type(arg_info->misc, NULL);
}
return type;
}
bool
report_memarg_type(sysarg_iter_info_t *ii,
int ordinal, uint arg_flags,
app_pc ptr, size_t sz, const char *id,
drsys_param_type_t type, const char *type_name)
{
LOG(2, "%s: "PFX"-"PFX"\n", __FUNCTION__, ptr, ptr+sz);
return report_memarg_ex(ii, ordinal, mode_from_flags(arg_flags), ptr, sz, id,
type, type_name, DRSYS_TYPE_INVALID);
}
/* For memargs, we report their fields, so the arg type is the containing type.
* This routine allows specifying the type of the subfield.
*/
bool
report_memarg_field(sysarg_iter_info_t *ii,
const sysinfo_arg_t *arg_info,
app_pc ptr, size_t sz, const char *id,
drsys_param_type_t type, const char *type_name)
{
drsys_param_type_t containing_type = type_from_arg_info(arg_info);
return report_memarg_ex(ii, arg_info->param, mode_from_flags(arg_info->flags),
ptr, sz, id, type, type_name, containing_type);
}
/* When we're not reporting sub-fields, stored type is reported type
* and not just containing type.
*/
bool
report_memarg_nonfield(sysarg_iter_info_t *ii,
const sysinfo_arg_t *arg_info,
app_pc ptr, size_t sz, const char *id)
{
return report_memarg_type(ii, arg_info->param, arg_info->flags,
ptr, sz, id, type_from_arg_info(arg_info), NULL);
}
/* For memargs, we report their fields, so the arg type is the containing type. */
bool
report_memarg(sysarg_iter_info_t *ii,
const sysinfo_arg_t *arg_info,
app_pc ptr, size_t sz, const char *id)
{
return report_memarg_field(ii, arg_info, ptr, sz, id, DRSYS_TYPE_STRUCT, NULL);
}
/* Caller must fill in ii->arg fields, except for ii->arg->pre. */
static bool
report_sysarg_iter(sysarg_iter_info_t *ii)
{
drsys_arg_t *arg = ii->arg;
/* For arg iteration post-syscall we masquerade as pre so the complex-type
* handlers invoke report_sysarg(). XXX: cleaner to have some separate flag.
*/
bool set_pre = arg->pre;
arg->pre = ii->pt->pre;
/* We can't short-circuit on first iter b/c we have too much code that
* stores extra info in pre for post that's after several reports.
* Thus we just suppress future callbacks on first iter.
*/
if (!ii->abort) {
if (!(*ii->cb_arg)(arg, ii->user_data))
ii->abort = true;
}
else
ASSERT(ii->pt->first_iter, "other than 1st iter, shouldn't report after abort");
arg->pre = set_pre;
return ii->pt->first_iter || !ii->abort;
}
static void
set_return_arg_vals(void *drcontext, cls_syscall_t *pt,
drsys_arg_t *arg/*IN/OUT*/, bool have_retval,
size_t sz, drsys_param_type_t type, const char *type_name)
{
arg->ordinal = -1;
arg->size = sz;
arg->reg = DR_REG_NULL;
arg->start_addr = NULL;
arg->enum_name = NULL;
if (have_retval) {
get_syscall_result((syscall_info_t *)arg->syscall, pt,
NULL, &arg->value64, NULL);
arg->value = (ptr_uint_t) arg->value64;
} else {
arg->value = 0;
arg->value64 = 0;
}
arg->type = type;
arg->type_name = arg_type_name(type, type_name);
arg->mode = DRSYS_PARAM_RETVAL | DRSYS_PARAM_INLINED;
}
bool
report_sysarg_return(void *drcontext, sysarg_iter_info_t *ii,
size_t sz, drsys_param_type_t type, const char *type_name)
{
set_return_arg_vals(drcontext, ii->pt, ii->arg, ii->pt != NULL && !ii->pt->pre,
sz, type, type_name);
return report_sysarg_iter(ii);
}
bool
report_sysarg_type(sysarg_iter_info_t *ii, int ordinal, uint arg_flags,
size_t sz, drsys_param_type_t type, const char *type_name)
{
drsys_arg_t *arg = ii->arg;
arg->ordinal = ordinal;
arg->size = sz;
drsyscall_os_get_sysparam_location(ii->pt, ordinal, arg);
arg->value = (ptr_uint_t) ii->pt->sysarg[ordinal];
arg->value64 = ii->pt->sysarg[ordinal];
arg->type = type;
arg->type_name = arg_type_name(type, type_name);
arg->mode = mode_from_flags(arg_flags);
return report_sysarg_iter(ii);
}
bool
report_sysarg(sysarg_iter_info_t *ii, int ordinal, uint arg_flags)
{
return report_sysarg_type(ii, ordinal, arg_flags,
sizeof(reg_t), DRSYS_TYPE_UNKNOWN, NULL);
}
bool
sysarg_invalid(sysinfo_arg_t *arg)
{
return (arg->param == 0 && arg->size == 0 && arg->flags == 0);
}
#ifndef MAX_PATH
# define MAX_PATH 4096
#endif
/***************************************************************************
* Type-specific cross-platform syscall arg processing
*/
/* pass 0 for size if there is no max size */
bool
handle_cstring(sysarg_iter_info_t *ii, int ordinal, uint arg_flags, const char *id,
byte *start, size_t size/*in bytes*/, char *safe, bool check_addr)
{
/* the kernel wrote a string to the buffer: only up to the terminating
* null should be marked as defined
*/
uint i;
char c;
/* input params have size 0: for safety stopping at MAX_PATH */
size_t maxsz = (size == 0) ? (MAX_PATH*sizeof(char)) : size;
if (start == NULL)
return false; /* nothing to do */
if (ii->arg->pre && !TEST(SYSARG_READ, arg_flags)) {
if (!check_addr)
return false;
if (size > 0) {
/* if max size specified, on pre-write check whole thing for addr */
report_memarg_type(ii, ordinal, arg_flags, start, size, id,
DRSYS_TYPE_CSTRING, NULL);
return true;
}
}
if (!ii->arg->pre && !TEST(SYSARG_WRITE, arg_flags))
return false; /*nothing to do */
for (i = 0; i < maxsz; i += sizeof(char)) {
if (safe != NULL)
c = safe[i/sizeof(char)];
else if (!safe_read(start + i, sizeof(c), &c)) {
WARN("WARNING: unable to read syscall param string\n");
break;
}
if (c == L'\0')
break;
}
report_memarg_type(ii, ordinal, arg_flags, start, i + sizeof(char), id,
DRSYS_TYPE_CSTRING, NULL);
return true;
}
static size_t
safe_strnlen(const char *str, size_t max)
{
register char *s = (char *) str;
if (str == NULL)
return 0;
/* FIXME PR 408539: use safe_read(), in a general routine that can be used
* for SYSARG_SIZE_CSTRING in process_syscall_reads_and_writes()
*/
while ((s - str) < max && *s != '\0')
s++;
return (s - str);
}
/* struct sockaddr is large but the meaningful portions vary by family.
* This routine stores the socklen passed in pre-syscall and uses it to
* take a MIN(pre,post) in post.
* It performs all checks including on whole struct.
*/
bool
handle_sockaddr(cls_syscall_t *pt, sysarg_iter_info_t *ii, byte *ptr,
size_t socklen, int ordinal, uint arg_flags, const char *id)
{
struct sockaddr *sa = (struct sockaddr *) ptr;
#ifdef UNIX
sa_family_t family;
#else
ADDRESS_FAMILY family;
#endif
/* If not enough space kernel writes space needed, so we need to adjust
* to the passed-in size by storing it in pre-syscall.
*/
if (pt->first_iter && ii->arg->pre && TEST(SYSARG_WRITE, arg_flags)) {
store_extra_info(pt, EXTRA_INFO_SOCKADDR, socklen);
} else if (!ii->arg->pre && TEST(SYSARG_WRITE, arg_flags)) {
size_t pre_len = (size_t) read_extra_info(pt, EXTRA_INFO_SOCKADDR);
if (socklen > pre_len)
socklen = pre_len;
ASSERT(pre_len != 0, "check_sockaddr called in post but not pre");
}
/* Whole thing should be addressable, but only part must be
* defined. The kernel returns how much it wrote (once we MIN it
* with specified capacity above) and it seems to fill in solidly
* w/ no gaps, so on a write we do not walk the individual fields.
*/
if (TEST(SYSARG_WRITE, arg_flags)) {
if (!report_memarg_type(ii, ordinal, arg_flags, ptr,
socklen, id, DRSYS_TYPE_SOCKADDR, NULL))
return true;
return true; /* all done */
}
if (ii->arg->pre) {
if (!report_memarg_type(ii, ordinal, arg_flags, (app_pc) &sa->sa_family,
sizeof(sa->sa_family), id, DRSYS_TYPE_INT, NULL))
return true;
}
if (!safe_read(&sa->sa_family, sizeof(family), &family))
return true;
/* we're careful to not check beyond socklen */
switch (family) {
#ifdef WINDOWS
case AF_UNSPEC: {
/* FIXME i#386: I'm seeing 0 (AF_UNSPEC) a lot, e.g., with
* IOCTL_AFD_SET_CONTEXT where the entire sockaddrs are just zero. Not sure
* whether to require that anything beyond sa_family be defined. Sometimes
* there is further data and the family is set later. For now ignoring
* beyond sa_family.
*/
break;
}
#else
case AF_UNIX: {
struct sockaddr_un *sun = (struct sockaddr_un *) sa;
size_t sz_left = socklen - offsetof(struct sockaddr_un, sun_path);
size_t len = safe_strnlen(sun->sun_path, MIN(sz_left, sizeof(sun->sun_path)));
if (len > 0 &&
!report_memarg_type(ii, ordinal, arg_flags, (app_pc) sun->sun_path,
len, id, DRSYS_TYPE_CARRAY, NULL))
return true;
break;
}
#endif
case AF_INET: {
struct sockaddr_in *sin = (struct sockaddr_in *) sa;
if (socklen >= offsetof(struct sockaddr_in, sin_port) + sizeof(sin->sin_port) &&
!report_memarg_type(ii, ordinal, arg_flags, (app_pc) &sin->sin_port,
sizeof(sin->sin_port), id, DRSYS_TYPE_INT, NULL))
return true;
if (socklen >= offsetof(struct sockaddr_in, sin_addr) + sizeof(sin->sin_addr) &&
!report_memarg_type(ii, ordinal, arg_flags, (app_pc) &sin->sin_addr,
sizeof(sin->sin_addr), id, DRSYS_TYPE_STRUCT, NULL))
return true;
break;
}
case AF_INET6: {
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa;
if (socklen >= offsetof(struct sockaddr_in6, sin6_port) +
sizeof(sin6->sin6_port) &&
!report_memarg_type(ii, ordinal, arg_flags, (app_pc) &sin6->sin6_port,
sizeof(sin6->sin6_port), id, DRSYS_TYPE_INT, NULL))
return true;
if (socklen >= offsetof(struct sockaddr_in6, sin6_flowinfo) +
sizeof(sin6->sin6_flowinfo) &&
!report_memarg_type(ii, ordinal, arg_flags, (app_pc) &sin6->sin6_flowinfo,
sizeof(sin6->sin6_flowinfo), id, DRSYS_TYPE_INT, NULL))
return true;
if (socklen >= offsetof(struct sockaddr_in6, sin6_addr) +
sizeof(sin6->sin6_addr) &&
!report_memarg_type(ii, ordinal, arg_flags, (app_pc) &sin6->sin6_addr,
sizeof(sin6->sin6_addr), id, DRSYS_TYPE_STRUCT, NULL))
return true;
/* FIXME: when is sin6_scope_struct used? */
if (socklen >= offsetof(struct sockaddr_in6, sin6_scope_id) +
sizeof(sin6->sin6_scope_id) &&
!report_memarg_type(ii, ordinal, arg_flags, (app_pc) &sin6->sin6_scope_id,
sizeof(sin6->sin6_scope_id), id, DRSYS_TYPE_INT, NULL))
return true;
break;
}
#ifdef LINUX
case AF_NETLINK: {
struct sockaddr_nl *snl = (struct sockaddr_nl *) sa;
if (socklen >= offsetof(struct sockaddr_nl, nl_pad) + sizeof(snl->nl_pad) &&
!report_memarg_type(ii, ordinal, arg_flags, (app_pc) &snl->nl_pad,
sizeof(snl->nl_pad), id, DRSYS_TYPE_INT, NULL))
return true;
if (socklen >= offsetof(struct sockaddr_nl, nl_pid) + sizeof(snl->nl_pid) &&
!report_memarg_type(ii, ordinal, arg_flags, (app_pc) &snl->nl_pid,
sizeof(snl->nl_pid), id, DRSYS_TYPE_INT, NULL))
return true;
if (socklen >= offsetof(struct sockaddr_nl, nl_groups) + sizeof(snl->nl_groups) &&
!report_memarg_type(ii, ordinal, arg_flags, (app_pc) &snl->nl_groups,
sizeof(snl->nl_groups), id, DRSYS_TYPE_INT, NULL))
return true;
break;
}
#endif
default:
ELOGF(0, f_global, "WARNING: unknown sockaddr type %d\n", family);
IF_DEBUG(report_callstack(ii->arg->drcontext, ii->arg->mc);)
break;
}
return true;
}
/***************************************************************************
* General syscall arg processing
*/
/* We use this sentinel value for C string params. We want a non-zero value
* to indicate the param is present, but we want to pass 0 to handle_cstring().
* We need the non-zero value to be large enough to avoid triggering the
* truncation check vs sysarg_known_sz.
*/
#define SIZE_DYNAMIC (ptr_uint_t)-1
/* assumes pt->sysarg[] has already been filled in */
static ptr_uint_t
sysarg_get_size(void *drcontext, cls_syscall_t *pt, sysarg_iter_info_t *ii,
syscall_info_t *sysinfo, int argnum, bool pre, byte *start)
{
ptr_uint_t size = 0;
sysinfo_arg_t *arg = &sysinfo->arg[argnum];
if (arg->size == 0 && TEST(SYSARG_COMPLEX_TYPE, arg->flags) &&
arg->misc == SYSARG_TYPE_CSTRING) {
return SIZE_DYNAMIC; /* we'll figure out size later */
} else if (arg->size == SYSARG_POST_SIZE_RETVAL) {
/* XXX: some syscalls (in particular NtGdi* and NtUser*) return
* the capacity needed when the input buffer is NULL or
* size of input buffer is given as 0. For the buffer being NULL
* we won't erroneously mark as defined, but for size being 0
* if buffer is non-NULL we could: entry should use
* SYSARG_NO_WRITE_IF_COUNT_0 in such cases.
*/
if (pre) {
/* Can't ask for retval on pre but we have a few syscalls where the
* pre-size is only known if the app makes a prior syscall (w/ NULL
* buffer, usually) to find it out: i#485. Today we don't handle that
* and thus don't check for unaddr until after the kernel writes.
*/
size = 0;
} else {
size = dr_syscall_get_result(drcontext);
}
} else if (arg->size == SYSARG_SIZE_IN_FIELD) {
if (pre) {
/* 4-byte size field in struct */
uint sz;
byte *field = start + arg->misc/*offs of size field */;
if (start != NULL) {
/* by using this flag, os-specific code gives up first access
* rights (i.e., to skip this check, don't use this flag)
*/
if (!report_memarg_type(ii, arg->param, SYSARG_READ, field,
sizeof(sz), NULL, DRSYS_TYPE_INT, NULL))
return 0;
/* i#1494, i#992: On iterating sysarg via drsys_iterate_args,
* sysarg_get_size from process_pre_syscall_reads_and_writes is
* called even in post-syscall event with pre being true.
* Since the kernel can overwrite these struct fields during the syscall,
* to avoid the real size being overwritten, we only read the size
* on the first iteration.
*/
if (pt->first_iter) {
if (safe_read(field, sizeof(sz), &sz))
size = sz;
else
WARN("WARNING: cannot read struct size field\n");
} else {
/* release */
size = read_extra_info(pt, EXTRA_INFO_SIZE_FROM_FIELD);
}
}
/* Even if we failed to get the size, initialize this for
* post-syscall checks.
*/
if (pt->first_iter)
store_extra_info(pt, EXTRA_INFO_SIZE_FROM_FIELD, size);
} else {
/* i#992: The kernel can overwrite these struct fields during the
* syscall, so we save them in the pre-syscall event and use them
* post-syscall.
*/
size = read_extra_info(pt, EXTRA_INFO_SIZE_FROM_FIELD);
}
} else {
ASSERT(arg->size > 0 || -arg->size < SYSCALL_NUM_ARG_STORE,
"reached max syscall args stored");
if (arg->size > 0) {
size = arg->size;
} else {
int sz_argnum;
size = (ptr_uint_t) pt->sysarg[-arg->size];
sz_argnum = (-arg->size < arg->param) ? 0 : argnum + 1;
for (; !sysarg_invalid(&sysinfo->arg[sz_argnum]); sz_argnum++) {
if (sysinfo->arg[sz_argnum].param == -arg->size)
break;
}
if (sysinfo->arg[sz_argnum].size == sizeof(uint))
size = (uint) size;
}
if (TEST(SYSARG_LENGTH_INOUT, arg->flags)) {
size_t *ptr;
int sz_argnum;
ASSERT(arg->size <= 0, "inout can't be immed");
/* The size may be smaller than size_t (i#1108) so we need to find
* its entry to know the proper size to read.
* If the size is behind us, we start from 0; else, from next.
*/
sz_argnum = (-arg->size < arg->param) ? 0 : argnum + 1;
for (; !sysarg_invalid(&sysinfo->arg[sz_argnum]); sz_argnum++) {
if (sysinfo->arg[sz_argnum].param == -arg->size)
break;
}
ASSERT(!sysarg_invalid(&sysinfo->arg[sz_argnum]),
"in/out size should have own entry");
ASSERT(sysinfo->arg[sz_argnum].size > 0, "in/out size must be immed");
ASSERT(sysinfo->arg[sz_argnum].size <= sizeof(size),
"in/out size must be <= sizeof(size_t)");
ptr = SYSARG_AS_PTR(pt, -arg->size, size_t *);
size = 0; /* fill in top bytes */
/* XXX: in some cases, ptr isn't checked for definedness until
* after this de-ref (b/c the SYSARG_READ entry is after this
* entry in the arg array: we could re-arrange the entries?
*/
if (ptr == NULL ||
/* We assume little-endian. The portable way is to declare
* a char, a short, etc. which seems uglier.
*/
!safe_read((void *)ptr, sysinfo->arg[sz_argnum].size, &size))
size = 0;
} else if (TEST(SYSARG_POST_SIZE_IO_STATUS, arg->flags)) {
#ifdef WINDOWS
IO_STATUS_BLOCK *status = (IO_STATUS_BLOCK *) pt->sysarg[-arg->size];
ULONG_PTR sz;
ASSERT(sizeof(status->Information) == sizeof(sz), "");
ASSERT(!pre, "post-io flag should be on dup entry only");
ASSERT(arg->size <= 0, "io block can't be immed");
if (safe_read((void *)(&status->Information), sizeof(sz), &sz))
size = sz;
else
WARN("WARNING: cannot read IO_STATUS_BLOCK\n");
#else
ASSERT(false, "linux should not have io_status flag set");
#endif
}
}
if (TEST(SYSARG_SIZE_PLUS_1, arg->flags)) {
LOG(SYSCALL_VERBOSE, "\t adding 1 to original size of %d\n", size);
size++;
}
if (TEST(SYSARG_SIZE_IN_ELEMENTS, arg->flags)) {
ASSERT(arg->misc > 0 || -arg->misc < SYSCALL_NUM_ARG_STORE,
"reached max syscall args stored");
size *= ((arg->misc > 0) ? arg->misc : ((int) pt->sysarg[-arg->misc]));
}
return size;
}
static bool
should_ignore_arg(cls_syscall_t *pt, sysarg_iter_info_t *ii,
syscall_info_t *sysinfo, int i)
{
int if_null_arg = -1;
/* XXX: Unify these two flags by specifying the arg that might be NULL in
* misc. We skip that for now to avoid conflicting with type info for
* inline args.
*/
if (TEST(SYSARG_IGNORE_IF_NEXT_NULL, sysinfo->arg[i].flags))
if_null_arg = i+1;
else if (TEST(SYSARG_IGNORE_IF_PREV_NULL, sysinfo->arg[i].flags))
if_null_arg = i-1;
else
return false;
ASSERT(if_null_arg >= 0 && if_null_arg < MAX_ARGS_IN_ENTRY,
"sysarg index out of bound");
return (if_null_arg >= 0 && if_null_arg < MAX_ARGS_IN_ENTRY &&
SYSARG_AS_PTR(pt, sysinfo->arg[if_null_arg].param, app_pc) == NULL);
}
/* Walks the param entries stored in the syscall table and processes them
* for pre-syscall usage.
* Assumes that arg fields drcontext, sysnum, pre, and mc have already been filled in.
*/
static void
process_pre_syscall_reads_and_writes(cls_syscall_t *pt, sysarg_iter_info_t *ii)
{
void *drcontext = ii->arg->drcontext;
syscall_info_t *sysinfo = pt->sysinfo;
app_pc start;
ptr_uint_t size;
int i, last_param = -1;
char idmsg[32];
LOG(SYSCALL_VERBOSE, "processing pre system call #"SYSNUM_FMT"."SYSNUM_FMT" %s\n",
pt->sysnum.number, pt->sysnum.secondary, sysinfo->name);
for (i=0; i<MAX_ARGS_IN_ENTRY; i++) { /* not <arg_count b/c of double entries */
LOG(SYSCALL_VERBOSE, "\t pre considering arg %d %d %x\n", sysinfo->arg[i].param,
sysinfo->arg[i].size, sysinfo->arg[i].flags);
if (sysarg_invalid(&sysinfo->arg[i]))
break;
ASSERT(sysinfo->arg[i].param < sysinfo->arg_count, "param # > arg count!");
/* The length written may not match that requested, so we check whether
* addressable at pre-syscall point but only mark as defined (i.e.,
* commit the write) at post-syscall when know true length. This also
* waits to determine syscall success before committing, but it opens up
* more possibilities for races (PR 408540). When the pre and post
* sizes differ, we indicate what the post-syscall write size is via a
* second entry w/ the same param#.
* Xref PR 408536.
*/
if (sysinfo->arg[i].param == last_param) {
/* Only used in post-syscall */
continue;
}
last_param = sysinfo->arg[i].param;
if (TESTANY(SYSARG_INLINED | SYSARG_NON_MEMARG, sysinfo->arg[i].flags))
continue;
start = SYSARG_AS_PTR(pt, sysinfo->arg[i].param, app_pc);
size = sysarg_get_size(drcontext, pt, ii, sysinfo, i, true/*pre*/, start);
pt->sysarg_known_sz[sysinfo->arg[i].param] = size;
LOG(SYSCALL_VERBOSE, "\t pre storing size "PIFX" for arg %d\n",
size, sysinfo->arg[i].param);
if (ii->abort)
break;
/* FIXME PR 406355: we don't record which params are optional
* FIXME: some OUT params may not be written if the IN is bogus:
* we should check here since harder to undo post-syscall on failure.
*/
if (start != NULL && size > 0) {
size_t real_sz = (size == SIZE_DYNAMIC) ? 0 : size;
bool skip = os_handle_pre_syscall_arg_access(ii, &sysinfo->arg[i],
start, real_sz);
if (ii->abort)
break;
/* i#502-c#5, i#1169: some arg should be ignored if another arg is NULL */
if (!skip && should_ignore_arg(pt, ii, sysinfo, i))
skip = true;
/* pass syscall # as pc for reporting purposes */
/* we treat in-out read-and-write as simply read, since if
* not defined we'll report and then mark as defined anyway.
*/
if (!skip) {
/* indicate which syscall arg (i#510) */
IF_DEBUG(int res = )
dr_snprintf(idmsg, BUFFER_SIZE_ELEMENTS(idmsg), "parameter #%d",
sysinfo->arg[i].param);
ASSERT(res > 0 && res < BUFFER_SIZE_ELEMENTS(idmsg),
"message buffer too small");
NULL_TERMINATE_BUFFER(idmsg);
if (!report_memarg_nonfield(ii, &sysinfo->arg[i], start, real_sz, idmsg))
break;
}
}
}
}
/* Walks the param entries stored in the syscall table and processes them
* for post-syscall usage.
* Assumes that arg fields drcontext, sysnum, pre, and mc have already been filled in.
*/
static void
process_post_syscall_reads_and_writes(cls_syscall_t *pt, sysarg_iter_info_t *ii)
{
void *drcontext = ii->arg->drcontext;
syscall_info_t *sysinfo = pt->sysinfo;
app_pc start;
ptr_uint_t size, last_size = 0;
int i, last_param = -1;
IF_DEBUG(int res;)
char idmsg[32];
#ifdef WINDOWS
ptr_int_t result = dr_syscall_get_result(drcontext);
#endif
LOG(SYSCALL_VERBOSE, "processing post system call #"SYSNUM_FMT"."SYSNUM_FMT,
pt->sysnum.number, pt->sysnum.secondary);
LOG(SYSCALL_VERBOSE, " %s res="PIFX"\n",
sysinfo->name, dr_syscall_get_result(drcontext));
for (i=0; i<MAX_ARGS_IN_ENTRY; i++) { /* not <arg_count b/c of double entries */
LOG(SYSCALL_VERBOSE, "\t post considering arg %d %d %x "PFX"\n",
sysinfo->arg[i].param, sysinfo->arg[i].size, sysinfo->arg[i].flags,
pt->sysarg[sysinfo->arg[i].param]);
if (sysarg_invalid(&sysinfo->arg[i]))
break;
ASSERT(i < SYSCALL_NUM_ARG_STORE, "not storing enough args");
if (!TEST(SYSARG_WRITE, sysinfo->arg[i].flags))
continue;
ASSERT(!TEST(SYSARG_INLINED, sysinfo->arg[i].flags),
"inlined should not be written");
#ifdef WINDOWS
/* i#486, i#531, i#932: for too-small buffer, only last param written */
if (os_syscall_ret_small_write_last(sysinfo, result) &&
i+1 < MAX_ARGS_IN_ENTRY &&
!sysarg_invalid(&sysinfo->arg[i+1]))
continue;
#endif
start = SYSARG_AS_PTR(pt, sysinfo->arg[i].param, app_pc);
size = sysarg_get_size(drcontext, pt, ii, sysinfo, i, false/*!pre*/, start);
if (ii->abort)
break;
/* For some syscalls, if the output param is not large enough the
* syscall still succeeds and the data is truncated. The required
* size is still written, instead of the written size. We want
* the written size. Xref i#1119.
*/
if (size > pt->sysarg_known_sz[sysinfo->arg[i].param]) {
LOG(SYSCALL_VERBOSE, "\ttruncating out size of arg %d from "PIFX" to "PIFX"\n",
sysinfo->arg[i].param, size, pt->sysarg_known_sz[sysinfo->arg[i].param]);
size = pt->sysarg_known_sz[sysinfo->arg[i].param];
}
/* indicate which syscall arg (i#510) */
IF_DEBUG(res = )
dr_snprintf(idmsg, BUFFER_SIZE_ELEMENTS(idmsg), "parameter #%d",
sysinfo->arg[i].param);
ASSERT(res > 0 && res < BUFFER_SIZE_ELEMENTS(idmsg), "message buffer too small");
NULL_TERMINATE_BUFFER(idmsg);
if (sysinfo->arg[i].param == last_param) {
/* For a double entry, the 2nd indicates the actual written size */
if (size == 0
IF_WINDOWS(/* i#798: On async write, use capacity, not OUT size. */
|| result == STATUS_PENDING
/* i#486, i#531: don't use OUT size on partial write */
|| result == STATUS_BUFFER_TOO_SMALL
|| result == STATUS_BUFFER_OVERFLOW)) {
/* we use SYSARG_LENGTH_INOUT for some optional params: in that
* case use the 1st entry's max size.
* XXX: we could put in our own param when the app supplies NULL
*/
size = last_size;
}
if (TEST(SYSARG_NO_WRITE_IF_COUNT_0, sysinfo->arg[i].flags)) {
/* Currently used only for NtUserGetKeyboardLayoutList.
* If the count (passed in a param indicated by the first entry's
* size field) is zero, the kernel returns the capacity needed,
* but doesn't write anything, regardless of the buffer value.
*/
ASSERT(i > 0, "logic error");
ASSERT(sysinfo->arg[i-1].size <= 0, "invalid syscall table entry");
if (i > 0 && pt->sysarg[-sysinfo->arg[i-1].size] == 0)
size = 0;
}
if (start != NULL && size > 0) {
bool skip = os_handle_post_syscall_arg_access
(ii, &sysinfo->arg[i], start, size);
/* i#1169: some args (usually sizes) are not written if optional
* out params are not present.
*/
if (!skip && should_ignore_arg(pt, ii, sysinfo, i))
skip = true;
if (!skip) {
if (!report_memarg_nonfield(ii, &sysinfo->arg[i], start, size, idmsg))
break;
}
}
continue;
}
last_param = sysinfo->arg[i].param;
last_size = size;
/* If the first in a double entry, give 2nd entry precedence, but
* keep size in last_size in case 2nd was optional OUT and is NULL
*/
if (i < MAX_ARGS_IN_ENTRY-1 && sysinfo->arg[i+1].param == last_param &&
!sysarg_invalid(&sysinfo->arg[i+1]))
continue;
LOG(SYSCALL_VERBOSE, "\t start "PFX", size "PIFX"\n", start, size);
if (start != NULL && size > 0) {
bool skip = os_handle_post_syscall_arg_access(ii, &sysinfo->arg[i],
start, size);
if (!skip) {
if (!report_memarg_nonfield(ii, &sysinfo->arg[i], start, size, idmsg))
break;
}
}
}
}
static syscall_info_t *
get_sysinfo(void *drcontext, cls_syscall_t *pt, int initial_num,
drsys_sysnum_t *sysnum DR_PARAM_OUT)
{
syscall_info_t *sysinfo;
ASSERT(sysnum != NULL, "invalid param");
ASSERT(pt->pre, "not support for post: need pt->sysarg there");
sysnum->number = initial_num;
sysnum->secondary = 0;
sysinfo = syscall_lookup(*sysnum, false/*don't resolve 2ndary yet*/);
if (sysinfo != NULL) {
if (TEST(SYSINFO_SECONDARY_TABLE, sysinfo->flags)) {
uint code;
ASSERT(sysinfo->arg_count >= 1, "at least 1 arg for code");
/* We're called only from pre, before pt->sysarg is set, and not
* used for syscalls w/ 64-bit params in 32-bit, so we can use
* dr_syscall_get_param().
*/
/* The param # to use for the code is stored in the 1st entry
* (the entries are not otherwise used, as we'll switch to the
* other table).
*/
code = (uint) dr_syscall_get_param(drcontext, sysinfo->arg[0].param);
sysnum->secondary = code;
/* get a new sysinfo */
sysinfo = syscall_lookup(*sysnum, true/*resolve 2ndary*/);
if (sysinfo == NULL) {
sysnum->secondary = BASE_ENTRY_INDEX;
sysinfo = syscall_lookup(*sysnum, true/*resolve 2ndary*/);
}
}
}
return sysinfo;
}
/* used to ignore either memargs or regular args while iterating the other */
static bool
nop_iter_cb(drsys_arg_t *arg, void *user_data)
{
return true; /* must keep going to find the other type */
}
DR_EXPORT
drmf_status_t
drsys_iterate_memargs(void *drcontext, drsys_iter_cb_t cb, void *user_data)
{
cls_syscall_t *pt = (cls_syscall_t *) drmgr_get_cls_field(drcontext, cls_idx_drsys);
drsys_arg_t arg;
sysarg_iter_info_t iter_info = {&arg, cb, nop_iter_cb, user_data, pt, false};
if (!pt->memargs_iterated) {
if (pt->pre)
pt->memargs_iterated = true;
else /* can't call post w/o having called pre, b/c of extra_info */
return DRMF_ERROR_INVALID_CALL;
}
arg.drcontext = drcontext;
arg.syscall = get_cur_syscall(pt);
arg.sysnum = pt->sysnum;
arg.pre = pt->pre;
arg.mc = &pt->mc;
arg.valid = true;
arg.value = 0; /* only used for arg iterator */
arg.value64 = 0; /* only used for arg iterator */
if (pt->pre) {
if (pt->sysinfo != NULL) {
process_pre_syscall_reads_and_writes(pt, &iter_info);
os_handle_pre_syscall(drcontext, pt, &iter_info);
}
if (!pt->known) {
handle_pre_unknown_syscall(drcontext, pt, &iter_info);
}
} else {
#ifdef SYSCALL_DRIVER
if (drsys_ops.syscall_driver)
driver_process_writes(drcontext, sysnum);
#endif
if (pt->sysinfo != NULL) {
if (!os_syscall_succeeded(pt->sysnum, pt->sysinfo, pt)) {
LOG(SYSCALL_VERBOSE,
"system call #"SYSNUM_FMT"."SYSNUM_FMT" %s failed with "PFX"\n",
pt->sysnum.number, pt->sysnum.secondary,
pt->sysinfo->name, dr_syscall_get_result(drcontext));
} else if (!os_syscall_succeeded_custom(pt->sysnum, pt->sysinfo, pt)) {
process_post_syscall_reads_and_writes(pt, &iter_info);
}
os_handle_post_syscall(drcontext, pt, &iter_info);
}
if (!pt->known)
handle_post_unknown_syscall(drcontext, pt, &iter_info);
}
pt->first_iter = false;
return DRMF_SUCCESS;
}
/* Pass pt==NULL for static iteration.
* arg need not be initialized.
*/
static drmf_status_t
drsys_iterate_args_common(void *drcontext, cls_syscall_t *pt, syscall_info_t *sysinfo,
drsys_arg_t *arg, drsys_iter_cb_t cb, void *user_data)
{
int i, compacted;
if (sysinfo == NULL)
return DRMF_ERROR_DETAILS_UNKNOWN;
LOG(2, "iterating over args for syscall #"SYSNUM_FMT"."SYSNUM_FMT" %s\n",
sysinfo->num.number, sysinfo->num.secondary, sysinfo->name);
arg->drcontext = drcontext;
arg->syscall = (drsys_syscall_t *) sysinfo;
arg->sysnum = sysinfo->num;
if (pt == NULL) {
arg->pre = true; /* arbitrary */
arg->mc = NULL;
arg->valid = false;
} else {
arg->valid = true;
arg->pre = pt->pre;
arg->mc = &pt->mc;
}
arg->arg_name = NULL;
arg->containing_type = DRSYS_TYPE_INVALID;
/* Treat all parameters as IN.
* There are no inlined OUT params anyway: have to at least set
* to NULL, unless truly ignored based on another parameter.
*/
for (i = 0, compacted = 0; i < sysinfo->arg_count; i++) {
arg->ordinal = i;
arg->size = sizeof(void*);
if (pt == NULL) {
arg->reg = DR_REG_NULL;
arg->start_addr = NULL;
arg->value = 0;
arg->value64 = 0;
} else {
drsyscall_os_get_sysparam_location(pt, i, arg);
arg->value64 = pt->sysarg[i];
arg->value = (ptr_uint_t) pt->sysarg[i];
}
arg->type = DRSYS_TYPE_UNKNOWN;
arg->mode = DRSYS_PARAM_IN;
/* FIXME i#1089: add type info for the non-memory-complex-type args */
if (!sysarg_invalid(&sysinfo->arg[compacted]) &&
sysinfo->arg[compacted].param == i) {
if (SYSARG_MISC_HAS_TYPE(sysinfo->arg[compacted].flags)) {
arg->type = type_from_arg_info(&sysinfo->arg[compacted]);
} else if (!TEST(SYSARG_INLINED, sysinfo->arg[compacted].flags)) {
/* Rather than clutter up the tables with DRSYS_TYPE_STRUCT
* for all the types we haven't given special enums to,
* we mark the truly unknown and assume everything else is
* a struct.
*/
arg->type = DRSYS_TYPE_STRUCT;
}
if (TEST(SYSARG_INLINED, sysinfo->arg[compacted].flags)) {
int sz = sysinfo->arg[compacted].size;
ASSERT(sz > 0, "inlined must have regular size in bytes");
arg->size = sz;
/* We zero out the top bits here which are uninitialized, to
* avoid confusing the client.
*/
if (arg->size < sizeof(ptr_uint_t)) {
if (arg->size == 1)
arg->value &= 0xff;
else if (arg->size == 2)
arg->value &= 0xffff;
else if (arg->size == 4)
arg->value &= 0xffffffff;
arg->value64 = arg->value;
}
}
arg->mode = mode_from_flags(sysinfo->arg[compacted].flags);
arg->enum_name = sysinfo->arg[compacted].type_name;
/* Go to next entry. Skip double entries. */
while (sysinfo->arg[compacted].param == i &&
!sysarg_invalid(&sysinfo->arg[compacted]))
compacted++;
ASSERT(compacted <= MAX_ARGS_IN_ENTRY, "error in table entry");
} else
arg->enum_name = NULL;
ASSERT(arg->type < NUM_PARAM_TYPE_NAMES, "invalid type enum val");
arg->type_name = param_type_names[arg->type];
if (!(*cb)(arg, user_data))
break;
}
if (pt == NULL || !TEST(SYSINFO_RET_TYPE_VARIES, sysinfo->flags)) {
/* return value */
arg->size = sizeof(reg_t);
/* get exported type and size if different from reg_t */
arg->type = map_to_exported_type(sysinfo->return_type, &arg->size);
set_return_arg_vals(drcontext, pt, arg, pt != NULL && !pt->pre,
arg->size, arg->type, NULL);
(*cb)(arg, user_data);
}
return DRMF_SUCCESS;
}
DR_EXPORT
drmf_status_t
drsys_iterate_args(void *drcontext, drsys_iter_cb_t cb, void *user_data)
{
cls_syscall_t *pt = (cls_syscall_t *) drmgr_get_cls_field(drcontext, cls_idx_drsys);
drmf_status_t res;
drsys_arg_t arg;
sysarg_iter_info_t iter_info = {&arg, nop_iter_cb, cb, user_data, pt, false};
ASSERT(pt->sysinfo == NULL ||
drsys_sysnums_equal(&pt->sysnum, &pt->sysinfo->num), "sysnum mismatch");
res = drsys_iterate_args_common(drcontext, pt, pt->sysinfo, &arg, cb, user_data);
if (res == DRMF_SUCCESS) {
/* Handle dynamically-determined parameters. For simpler code, we pay the
* cost of calls to nop_iter_cb for all the memargs. An alternative would
* be to pass in a flag and check it before each report_{memarg,sysarg},
* or to split the routines up (but that would duplicate a lot of code).
*/
/* We rely on arg being initialized by drsys_iterate_args_common().
* We just need to set pre to true (yes, even if called from post-syscall)
* in order to hit all the report_sysarg() calls (i#1102).
* XXX: it's a little weird to call os_handle_pre_syscall() from post-syscall:
* rename to os_handle_syscall() w/ the only weirdness arg.pre in post?
*/
arg.pre = true;
/* We must call this to process SYSARG_COMPLEX_TYPE table entries while
* pt->first_iter is true, so that subsequent calls for memarg iteration
* work properly.
*/
process_pre_syscall_reads_and_writes(pt, &iter_info);
os_handle_pre_syscall(drcontext, pt, &iter_info);
pt->first_iter = false;
}
return res;
}
DR_EXPORT
drmf_status_t
drsys_iterate_arg_types(drsys_syscall_t *syscall, drsys_iter_cb_t cb, void *user_data)
{
void *drcontext = dr_get_current_drcontext();
syscall_info_t *sysinfo = (syscall_info_t *) syscall;
drsys_arg_t arg;
if (syscall == NULL)
return DRMF_ERROR_INVALID_PARAMETER;
return drsys_iterate_args_common(drcontext, NULL/*==static*/, sysinfo,
&arg, cb, user_data);
}
DR_EXPORT
drmf_status_t
drsys_iterate_syscalls(bool (*cb)(drsys_sysnum_t sysnum, drsys_syscall_t *syscall,
void *user_data),
void *user_data)
{
uint i;
/* we need a recursive lock to support queries during iteration */
dr_recurlock_lock(systable_lock);
for (i = 0; i < HASHTABLE_SIZE(systable.table_bits); i++) {
hash_entry_t *he;
for (he = systable.table[i]; he != NULL; he = he->next) {
syscall_info_t *sysinfo = (syscall_info_t *) he->payload;
if (!(*cb)(sysinfo->num, (drsys_syscall_t *) sysinfo, user_data))
break;
}
}
dr_recurlock_unlock(systable_lock);
return DRMF_SUCCESS;
}
static bool
drsys_event_pre_syscall(void *drcontext, int initial_num)
{
cls_syscall_t *pt = (cls_syscall_t *) drmgr_get_cls_field(drcontext, cls_idx_drsys);
int i;
/* cache values for dynamic iteration */
pt->pre = true;
pt->first_iter = true;
pt->memargs_iterated = false;
pt->mc.size = sizeof(pt->mc);
pt->mc.flags = DR_MC_CONTROL|DR_MC_INTEGER; /* don't need xmm */
dr_get_mcontext(drcontext, &pt->mc);
DODEBUG({
/* read_extra_info() calls can be bypassed if syscalls or safe reads
* fail so we always clear up front
*/
memset(pt->extra_inuse, 0, sizeof(pt->extra_inuse));
});
/* now that we have pt->sysarg set, get sysinfo and sysnum */
pt->sysinfo = get_sysinfo(drcontext, pt, initial_num, &pt->sysnum);
pt->known = (pt->sysinfo != NULL &&
TEST(SYSINFO_ALL_PARAMS_KNOWN, pt->sysinfo->flags));
/* Save params for post-syscall access.
* We are reading beyond the # of args of some syscalls and we can
* (and do: i#1419) read beyond the base of the stack so we use a try.
*/
LOG(SYSCALL_VERBOSE, "app xsp="PFX"\n", pt->mc.xsp);
DR_TRY_EXCEPT(drcontext, {
int dr_slot;
IF_NOT_X64(int compacted = 0;)
for (dr_slot = 0, i = 0; i < SYSCALL_NUM_ARG_STORE; i++) {
pt->sysarg[i] = (ptr_uint_t) dr_syscall_get_param(drcontext, dr_slot);
IF_NOT_X64({
/* Handle 32-bit MacOS syscalls that have 64-bit params, which DR
* treats as two slots (params are on the stack).
*/
while (pt->sysinfo != NULL &&
!sysarg_invalid(&pt->sysinfo->arg[compacted]) &&
pt->sysinfo->arg[compacted].param < i)
compacted++;
ASSERT(compacted <= MAX_ARGS_IN_ENTRY, "error in table entry");
if (pt->sysinfo != NULL &&
!sysarg_invalid(&pt->sysinfo->arg[compacted]) &&
TEST(SYSARG_INLINED, pt->sysinfo->arg[compacted].flags) &&
pt->sysinfo->arg[compacted].size == 8) {
/* This arg takes up two slots */
dr_slot++;
pt->sysarg[i] |= ((uint64)dr_syscall_get_param(drcontext, dr_slot)
<< 32);
}
});
dr_slot++;
LOG(SYSCALL_VERBOSE, "\targ %d = "ARGFMT"\n", i, pt->sysarg[i]);
}
}, { /* EXCEPT */
/* Do nothing: we assume we're beyond the real # of args. */
});
#ifdef SYSCALL_DRIVER
/* do this as late as possible to avoid our own syscalls from corrupting
* the list of writes.
* the current plan is to query the driver on all syscalls, not just unknown,
* as a sanity check on both sides.
*/
if (drsys_ops.syscall_driver)
driver_pre_syscall(drcontext, pt->sysnum);
#endif
return true;
}
static void
drsys_event_post_syscall(void *drcontext, int sysnum)
{
cls_syscall_t *pt = (cls_syscall_t *) drmgr_get_cls_field(drcontext, cls_idx_drsys);
#ifdef SYSCALL_DRIVER
/* do this as early as possible to avoid drmem's own syscalls.
* unfortunately the module load event runs before this: so we skip
* NtMapViewOfSection.
*/
if (drsys_ops.syscall_driver) {
const char *name = get_syscall_name(sysnum);
if (name == NULL || strcmp(name, "NtMapViewOfSection") != 0)
driver_freeze_writes(drcontext);
else
driver_reset_writes(drcontext);
}
#endif
/* cache values for dynamic iteration */
ASSERT(pt->mc.size == sizeof(pt->mc), "mc was clobbered");
ASSERT(pt->mc.flags == (DR_MC_CONTROL|DR_MC_INTEGER), "mc was clobbered");
dr_get_mcontext(drcontext, &pt->mc);
pt->pre = false;
}
static void
drsys_event_post_syscall_last(void *drcontext, int sysnum)
{
cls_syscall_t *pt = (cls_syscall_t *) drmgr_get_cls_field(drcontext, cls_idx_drsys);
/* The client's post-syscall event occurs prior to this due to our large
* priority value. Thus, all iterations are now done and we can perform
* a final iteration that enacts any necessary state changes.
*/
#ifdef SYSCALL_DRIVER
if (drsys_ops.syscall_driver)
driver_reset_writes(drcontext);
#endif
if (!pt->known)
handle_post_unknown_syscall(drcontext, pt, NULL);
}
/***************************************************************************
* Filters
*/
/* We keep a table as a convenience so that the client can use a
* static iterator and simply call our filter registration for each
* interesting syscall found.
*/
static bool filter_all;
#define FILTERED_TABLE_HASH_BITS 6
/* Operates on DR's simple "int sysnum" */
static hashtable_t filtered_table;
static bool
drsys_event_filter_syscall(void *drcontext, int sysnum)
{
return (filter_all ||
(hashtable_lookup(&filtered_table, (void *)(ptr_int_t)sysnum) != NULL));
}
DR_EXPORT
drmf_status_t
drsys_filter_syscall(drsys_sysnum_t sysnum)
{
/* DR only gives us the primary number, so we over-filter */
hashtable_add(&filtered_table, (void *)(ptr_uint_t)sysnum.number,
(void *)(ptr_uint_t)sysnum.number);
return DRMF_SUCCESS;
}
DR_EXPORT
drmf_status_t
drsys_filter_all_syscalls(void)
{
filter_all = true;
return DRMF_SUCCESS;
}
/***************************************************************************
* Events and Top-Level
*/
static dr_emit_flags_t
drsys_event_bb_analysis(void *drcontext, void *tag, instrlist_t *bb,
bool for_trace, bool translating, void **user_data)
{
instr_t *inst;
for (inst = instrlist_first(bb); inst != NULL; inst = instr_get_next(inst)) {
/* Check for valid opcode first to handle -fast_client_decode */
if (instr_opcode_valid(inst) && instr_is_syscall(inst))
check_syscall_gateway(inst);
}
return DR_EMIT_DEFAULT;
}
static dr_emit_flags_t
drsys_event_bb_insert(void *drcontext, void *tag, instrlist_t *bb, instr_t *inst,
bool for_trace, bool translating, void *user_data)
{
return DR_EMIT_DEFAULT;
}
static void
syscall_module_load(void *drcontext, const module_data_t *info, bool loaded)
{
drsyscall_os_module_load(drcontext, info, loaded);
}
static void
syscall_reset_per_thread(void *drcontext, cls_syscall_t *cpt)
{
int i;
for (i = 0; i < SYSCALL_NUM_ARG_TRACK; i++) {
if (cpt->sysarg_val_bytes[i] > 0) {
ASSERT(cpt->sysarg_val[i] != NULL, "sysarg alloc error");
thread_free(drcontext, cpt->sysarg_val[i], cpt->sysarg_val_bytes[i],
HEAPSTAT_MISC);
cpt->sysarg_val[i] = NULL;
cpt->sysarg_val_bytes[i] = 0;
} else {
ASSERT(cpt->sysarg_val[i] == NULL, "sysarg alloc error");
}
}
}
static void
syscall_context_init(void *drcontext, bool new_depth)
{
cls_syscall_t *cpt;
if (new_depth) {
cpt = (cls_syscall_t *) thread_alloc(drcontext, sizeof(*cpt), HEAPSTAT_MISC);
drmgr_set_cls_field(drcontext, cls_idx_drsys, cpt);
} else {
cpt = (cls_syscall_t *) drmgr_get_cls_field(drcontext, cls_idx_drsys);
syscall_reset_per_thread(drcontext, cpt);
}
memset(cpt, 0, sizeof(*cpt));
#ifdef SYSCALL_DRIVER
if (drsys_ops.syscall_driver &&
/* exclude thread init */
!new_depth || drmgr_get_parent_cls_field(drcontext, cls_idx_drsys) != NULL)
driver_handle_callback(drcontext);
#endif
}
static void
syscall_context_exit(void *drcontext, bool thread_exit)
{
if (thread_exit) {
cls_syscall_t *cpt = (cls_syscall_t *)
drmgr_get_cls_field(drcontext, cls_idx_drsys);
syscall_reset_per_thread(drcontext, cpt);
thread_free(drcontext, cpt, sizeof(*cpt), HEAPSTAT_MISC);
}
/* else, nothing to do: we leave the struct for re-use on next callback */
#ifdef SYSCALL_DRIVER
if (drsys_ops.syscall_driver && !thread_exit)
driver_handle_cbret(drcontext);
#endif
}
static void
syscall_thread_init(void *drcontext)
{
/* we lazily initialize sysarg_ arrays */
#ifdef SYSCALL_DRIVER
if (drsys_ops.syscall_driver)
driver_thread_init(drcontext);
#endif
drsyscall_os_thread_init(drcontext);
}
static void
syscall_thread_exit(void *drcontext)
{
drsyscall_os_thread_exit(drcontext);
#ifdef SYSCALL_DRIVER
if (drsys_ops.syscall_driver)
driver_thread_exit(drcontext);
#endif
}
DR_EXPORT
drmf_status_t
drsys_init(client_id_t client_id, drsys_options_t *ops)
{
void *drcontext = dr_get_current_drcontext();
drmf_status_t res;
drmgr_priority_t pri_modload =
{sizeof(pri_modload), DRMGR_PRIORITY_NAME_DRSYS, NULL, NULL,
DRMGR_PRIORITY_MODLOAD_DRSYS};
drmgr_priority_t pri_presys =
{sizeof(pri_presys), DRMGR_PRIORITY_NAME_DRSYS, NULL, NULL,
DRMGR_PRIORITY_PRESYS_DRSYS};
drmgr_priority_t pri_postsys =
{sizeof(pri_postsys), DRMGR_PRIORITY_NAME_DRSYS, NULL, NULL,
DRMGR_PRIORITY_POSTSYS_DRSYS};
drmgr_priority_t pri_postsys_last =
{sizeof(pri_postsys_last), DRMGR_PRIORITY_NAME_DRSYS_LAST, NULL, NULL,
DRMGR_PRIORITY_POSTSYS_DRSYS_LAST};
/* we don't insert anything so priority shouldn't matter */
drmgr_priority_t pri_bb =
{sizeof(pri_bb), DRMGR_PRIORITY_NAME_DRSYS, NULL, NULL, 0};
/* handle multiple sets of init/exit calls */
int count = dr_atomic_add32_return_sum(&drsys_init_count, 1);
if (count > 1)
return DRMF_SUCCESS;
res = drmf_check_version(client_id);
if (res != DRMF_SUCCESS)
return res;
drmgr_init();
if (ops->struct_size > sizeof(drsys_ops))
return DRMF_ERROR_INCOMPATIBLE_VERSION;
/* Appended fields so far are fine being the default 0 in drsys_ops so
* we have no reason yet to worry about an older struct.
*/
memcpy(&drsys_ops, ops, ops->struct_size); /* Leave rest 0 */
drmgr_register_thread_init_event(syscall_thread_init);
drmgr_register_thread_exit_event(syscall_thread_exit);
drmgr_register_module_load_event_ex(syscall_module_load, &pri_modload);
cls_idx_drsys = drmgr_register_cls_field(syscall_context_init, syscall_context_exit);
ASSERT(cls_idx_drsys > -1, "unable to reserve CLS field");
if (cls_idx_drsys == -1)
return DRMF_ERROR;
systable_lock = dr_recurlock_create();
res = drsyscall_os_init(drcontext);
if (res != DRMF_SUCCESS && res != DRMF_WARNING_UNSUPPORTED_KERNEL)
return res;
/* We used to handle all the gory details of Windows pre- and
* post-syscall hooking ourselves, including system call parameter
* bases varying by syscall type, and post-syscall hook complexity.
* Old notes to highlight some of the past issues:
*
* Since we aren't allowed to add code after a syscall instr, we have to
* find the post-syscall app instr: but for vsyscall sysenter, that ret
* is executed natively, so we have to step one level out to the wrapper.
* Simpler to set a flag and assume next bb is the one rather than
* identify the vsyscall call up front.
*
* We used to also do pre-syscall via the wrapper, to avoid
* worrying about system call numbers or differences in where the parameters are
* located between int and sysenter, but now that we're checking syscall args at
* the syscall point itself anyway we do our pre-syscall checks there and only
* use these to find the post-syscall wrapper points. Eventually we'll do
* post-syscall checks after the syscall point instead of using the wrappers and
* then we'll get rid of all of this and will properly handle hand-rolled system
* calls.
*
* But now that DR 1.3 has syscall events we use those, which also makes it
* easier to port to Linux.
*/
drmgr_register_pre_syscall_event_ex(drsys_event_pre_syscall, &pri_presys);
drmgr_register_post_syscall_event_ex(drsys_event_post_syscall, &pri_postsys);
drmgr_register_post_syscall_event_ex(drsys_event_post_syscall_last, &pri_postsys_last);
dr_register_filter_syscall_event(drsys_event_filter_syscall);
hashtable_init(&filtered_table, FILTERED_TABLE_HASH_BITS, HASH_INTPTR,
false/*!strdup*/);
if (!drmgr_register_bb_instrumentation_event
(drsys_event_bb_analysis, drsys_event_bb_insert, &pri_bb)) {
ASSERT(false, "drmgr registration failed");
}
#ifdef SYSCALL_DRIVER
if (drsys_ops.syscall_driver)
driver_init();
#endif
return res;
}
DR_EXPORT
drmf_status_t
drsys_exit(void)
{
/* handle multiple sets of init/exit calls */
int count = dr_atomic_add32_return_sum(&drsys_init_count, -1);
if (count > 0)
return DRMF_SUCCESS;
if (count < 0)
return DRMF_ERROR;
#ifdef SYSCALL_DRIVER
if (drsys_ops.syscall_driver)
driver_exit();
#endif
hashtable_delete(&filtered_table);
drsyscall_os_exit();
dr_recurlock_destroy(systable_lock);
systable_lock = NULL;
drmgr_unregister_cls_field(syscall_context_init, syscall_context_exit,
cls_idx_drsys);
drmgr_exit();
return DRMF_SUCCESS;
}
/***************************************************************************
* EXTRA_INFO SLOT USAGE
*/
void
store_extra_info(cls_syscall_t *pt, int index, ptr_int_t value)
{
ASSERT(pt->first_iter ||
/* exception for sysarg_get_size() */
index == EXTRA_INFO_SIZE_FROM_FIELD,
"only store on first iter");
ASSERT(index <= EXTRA_INFO_MAX, "index too high");
DODEBUG({
ASSERT(!pt->extra_inuse[index], "sysarg extra info conflict");
pt->extra_inuse[index] = true;
});
pt->extra_info[index] = value;
}
ptr_int_t
read_extra_info(cls_syscall_t *pt, int index)
{
ptr_int_t value;
ASSERT(index <= EXTRA_INFO_MAX, "index too high");
value = pt->extra_info[index];
DODEBUG({
ASSERT(pt->extra_inuse[index],
"extra info used improperly (iterating memargs in post but not pre?)");
/* we can't set to false b/c there are multiple iters */
});
return value;
}