blob: b203d27b3a0e4dcad8615f45ae26cdf2931cf9f6 [file]
/* **********************************************************
* Copyright (c) 2010-2024 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.
*/
#ifndef _DRSYSCALL_OS_H_
#define _DRSYSCALL_OS_H_ 1
#include "utils.h"
#ifdef WINDOWS
/* We need extra room for dup entries for diff in vs out size to writes. */
# define MAX_ARGS_IN_ENTRY 18 /* 17 is max known */
#elif defined(MACOS) && !defined(X64)
# define MAX_ARGS_IN_ENTRY 8
#else
# define MAX_ARGS_IN_ENTRY 6 /* 6 is max on Linux */
#endif
#define SYSCALL_NUM_ARG_STORE MAX_ARGS_IN_ENTRY
#define SYSCALL_NUM_ARG_TRACK IF_WINDOWS_ELSE(26, 6)
/* Flag indicates last entry in the additional syscall table.
* drsys_num_t number field is used for this value.
*/
#define SECONDARY_TABLE_ENTRY_MAX_NUMBER -1
/* There are enumeration classes with gaps (e.g. 0-2-4-8). We use
* special flag to handle such cases. drsys_num_t number field is
* used for this value.
*/
#define SECONDARY_TABLE_SKIP_ENTRY -2
/* We place base entry in the secondary table under -1 index to fall
* back to the base entry if the app calls syscall with unknown
* structure number.
*/
#define BASE_ENTRY_INDEX -1
/* for diagnostics: eventually provide some runtime option,
* -logmask or something: for now have to modify this constant
*/
#ifdef TOOL_DR_MEMORY
# define SYSCALL_VERBOSE 2
#else
# define SYSCALL_VERBOSE 3
#endif
/* syscall numbers are most natural in decimal on linux but hex on windows */
#ifdef UNIX
# define SYSNUM_FMT "%d"
#else
# define SYSNUM_FMT PIFX
#endif
/* With the widening to 64-bit, 32-bit code needs a lot of ugly casts so we try
* to simplify via this macro:
*/
#define SYSARG_AS_PTR(pt, ord, type) (type)(ptr_uint_t)(pt)->sysarg[(ord)]
#define ARGFMT "0x" HEX64_FORMAT_STRING
/* extra_info slot usage */
enum {
/* The size computed by SYSARG_SIZE_IN_FIELD is saved here across the
* syscall. We only support one such parameter per syscall.
*/
EXTRA_INFO_SIZE_FROM_FIELD,
EXTRA_INFO_SOCKADDR,
#ifdef UNIX
EXTRA_INFO_MSG_CONTROL,
EXTRA_INFO_MSG_CONTROLLEN,
#endif
EXTRA_INFO_MAX,
};
extern int cls_idx_drsys;
extern drsys_options_t drsys_ops;
extern const char * const param_type_names[];
enum {
/*****************************************/
/* sysinfo_arg_t.flags */
SYSARG_READ = 0x00000001,
SYSARG_WRITE = 0x00000002,
/* The data structure type has pointers or uninitialized fields
* or padding and needs special processing according to the
* SYSARG_TYPE_* code stored in sysinfo_arg_t.misc.
*/
SYSARG_COMPLEX_TYPE = 0x00000004,
/* the size points at the IO_STATUS_BLOCK param */
SYSARG_POST_SIZE_IO_STATUS = 0x00000008,
/* (available) = 0x00000010, */
/* The param holding the size is a pointer b/c it's an IN OUT var.
* This can be used in one of two ways:
* 1) A single entry exists for the buffer/struct. In this case, the param
* holding the size must be both read and write (R|W).
* 2) A duplicate entry exists for the buffer/struct. Then the second can
* be SYSARG_LENGTH_INOUT (often "WI" in the tables) and the size param
* can just be written and not read (W).
*/
SYSARG_LENGTH_INOUT = 0x00000020,
/* The size is not in bytes but in elements where the size of
* each element is in the misc field. The misc field can
* contain <= in which case the element size is stored in that
* parameter number.
* This flag trumps SYSARG_COMPLEX_TYPE, so if there is an
* overlap then special handling must be done for the type.
*/
SYSARG_SIZE_IN_ELEMENTS = 0x00000040,
/* A non-memory argument (i.e., entire value is in parameter slot). */
SYSARG_INLINED = 0x00000080,
/* for SYSARG_POST_SIZE_RETVAL on a duplicate entry, nothing is
* written if the count, given in the first entry, is zero,
* regardless of the buffer pointer value.
*/
SYSARG_NO_WRITE_IF_COUNT_0 = 0x00000100,
/* Contains a type specifier */
SYSARG_HAS_TYPE = 0x00000200,
/* i#502-c#5 the arg should be ignored if the next arg is null */
SYSARG_IGNORE_IF_NEXT_NULL = 0x00000400,
/* Ignore this arg if the previous arg is NULL.
* XXX: Unify this with SYSARG_IGNORE_IF_NEXT_NULL by storing the arg# in
* misc once that doesn't conflict with type info.
*/
SYSARG_IGNORE_IF_PREV_NULL = 0x00000800,
/* Indicates that this entry is for non-memarg iteration only:
* this memory parameter is complex and for memarg iteration
* it relies on handler code.
*/
SYSARG_NON_MEMARG = 0x00001000,
/* Indicates that the size value from an output parameter is one smaller
* than the actual written size (typically, a string's terminating null).
*/
SYSARG_SIZE_PLUS_1 = 0x00002000,
/*****************************************/
/* sysinfo_arg_t.size, using values that cannot be mistaken for
* a parameter reference.
*/
/* <available> = -100, */
/* used in repeated sysinfo_arg_t entry for post-syscall size */
SYSARG_POST_SIZE_RETVAL = -101,
/* Size is stored as a field of size 4 bytes with an offset given by
* sysinfo_arg_t.misc. Can only be used by one arg per syscall.
*/
SYSARG_SIZE_IN_FIELD = -102,
/*****************************************/
/* sysinfo_arg_t.misc when flags has SYSARG_COMPLEX_TYPE */
/* These occupy the same number-space as DRSYS_TYPE_*.
* We have duplicate labels here for legacy code.
* We have a separate namespace so we can use our own types
* internally w/o exposing in the public header.
*/
/* The following flags are used on Windows. */
SYSARG_TYPE_CSTRING = DRSYS_TYPE_CSTRING, /* Linux too */
SYSARG_TYPE_CSTRING_WIDE = DRSYS_TYPE_CWSTRING,
SYSARG_TYPE_PORT_MESSAGE = DRSYS_TYPE_PORT_MESSAGE,
SYSARG_TYPE_CONTEXT = DRSYS_TYPE_CONTEXT,
SYSARG_TYPE_EXCEPTION_RECORD = DRSYS_TYPE_EXCEPTION_RECORD,
SYSARG_TYPE_SECURITY_QOS = DRSYS_TYPE_SECURITY_QOS,
SYSARG_TYPE_SECURITY_DESCRIPTOR = DRSYS_TYPE_SECURITY_DESCRIPTOR,
SYSARG_TYPE_UNICODE_STRING = DRSYS_TYPE_UNICODE_STRING,
SYSARG_TYPE_OBJECT_ATTRIBUTES = DRSYS_TYPE_OBJECT_ATTRIBUTES,
SYSARG_TYPE_LARGE_STRING = DRSYS_TYPE_LARGE_STRING,
SYSARG_TYPE_DEVMODEW = DRSYS_TYPE_DEVMODEW,
SYSARG_TYPE_WNDCLASSEXW = DRSYS_TYPE_WNDCLASSEXW,
SYSARG_TYPE_CLSMENUNAME = DRSYS_TYPE_CLSMENUNAME,
SYSARG_TYPE_MENUITEMINFOW = DRSYS_TYPE_MENUITEMINFOW,
SYSARG_TYPE_ALPC_PORT_ATTRIBUTES = DRSYS_TYPE_ALPC_PORT_ATTRIBUTES,
SYSARG_TYPE_ALPC_SECURITY_ATTRIBUTES= DRSYS_TYPE_ALPC_SECURITY_ATTRIBUTES,
SYSARG_TYPE_BITMAPINFO = DRSYS_TYPE_BITMAPINFO,
SYSARG_TYPE_ALPC_CONTEXT_ATTRIBUTES = DRSYS_TYPE_ALPC_CONTEXT_ATTRIBUTES,
SYSARG_TYPE_ALPC_MESSAGE_ATTRIBUTES = DRSYS_TYPE_ALPC_MESSAGE_ATTRIBUTES,
SYSARG_TYPE_T2_SET_PARAMETERS = DRSYS_TYPE_T2_SET_PARAMETERS,
/* These are Linux-specific */
SYSARG_TYPE_SOCKADDR = DRSYS_TYPE_SOCKADDR,
SYSARG_TYPE_MSGHDR = DRSYS_TYPE_MSGHDR,
SYSARG_TYPE_MSGBUF = DRSYS_TYPE_MSGBUF,
/* Types that we map to other types. These need unique number separate
* from the DRSYS_TYPE_* numbers so we sequentially number from here:
*/
SYSARG_TYPE_UNICODE_STRING_NOLEN = DRSYS_TYPE_LAST + 1,
/* These are used to encode type+size into return_type field */
SYSARG_TYPE_SINT32,
SYSARG_TYPE_UINT32,
SYSARG_TYPE_SINT16,
SYSARG_TYPE_UINT16,
SYSARG_TYPE_BOOL32,
SYSARG_TYPE_BOOL8,
/* Be sure to update map_to_exported_type() when adding here */
};
/* We encode the actual size of a write, if it can differ from the
* requested size, as a subsequent sysinfo_arg_t entry with the same
* param#. A negative size there refers to a parameter that should be
* de-referenced to obtain the actual write size. That parameter to be
* de-referenced must have its own entry which indicates its size.
*/
typedef struct _sysinfo_arg_t {
int param; /* ordinal of parameter */
int size; /* >0 = abs size; <=0 = -param that holds size */
uint flags; /* SYSARG_ flags */
/* Meaning depends on flags. I'd use a union but that would make
* the syscall tables ugly w/ a ton of braces.
* Currently used for:
* - SYSARG_COMPLEX_TYPE: holds SYSARG_TYPE_* enum value
* - SYSARG_SIZE_IN_ELEMENTS: holds size of array entry
* - SYSARG_SIZE_IN_FIELD: holds offset of 4-byte size field
* - SYSARG_INLINED: holds SYSARG_TYPE_* enum value
* - SYSARG_HAS_TYPE: holds SYSARG_TYPE_* enum value
*/
int misc;
const char *type_name; /* Symbolic name of the arg type. */
} sysinfo_arg_t;
#define SYSARG_MISC_HAS_TYPE(flags) \
(TESTANY(SYSARG_COMPLEX_TYPE|SYSARG_INLINED|SYSARG_HAS_TYPE, (flags)))
enum {
/* If not set, automated param comparison is used to find writes */
SYSINFO_ALL_PARAMS_KNOWN = 0x00000001,
/* When checking the sysnum vs a wrapper function, do not consider
* removing the prefix
*/
SYSINFO_REQUIRES_PREFIX = 0x00000002,
/* NtUser syscall wrappers are spread across user32.dll and imm32.dll */
SYSINFO_IMM32_DLL = 0x00000004,
/* Return value indicates failure only when zero */
SYSINFO_RET_ZERO_FAIL = 0x00000008,
/* Return value of STATUS_BUFFER_TOO_SMALL (i#486),
* STATUS_BUFFER_OVERFLOW (i#531), or STATUS_INFO_LENGTH_MISMATCH
* (i#932) writes final arg but no others.
* If it turns out some syscalls distinguish between the ret values
* we can split the flag up but seems safer to combine.
*/
SYSINFO_RET_SMALL_WRITE_LAST= 0x00000010,
/* System call takes a code from one of its params that is in essence
* a new system call number in a new sub-space.
* The num_out field contains a pointer to a new syscall_info_t
* array to use with the first param's code.
* The first argument field indicates which param contains the code.
* Any other argument fields in the initial entry are ignored.
*/
SYSINFO_SECONDARY_TABLE = 0x00000020,
/* Return value indicates failure only when -1 */
SYSINFO_RET_MINUS1_FAIL = 0x00000040,
/* Return type varies dynamically and handler must call report_sysarg_return() */
SYSINFO_RET_TYPE_VARIES = 0x00000080,
/* Return value is 64-bit even in 32-bit mode */
SYSINFO_RET_64BIT = 0x00000100,
};
#define SYSCALL_ARG_TRACK_MAX_SZ 2048
typedef struct _syscall_info_t {
/* System call number: filled in dynamically, allowing us to use the static
* fields to indicate underlying version reliance. We read the static
* fields before we decide to use an entry and clobber the static value with
* the actual dynamic number. Statically, the number field, if non-zero,
* indicates the minimum Windows version at which this entry is valid; the
* secondary field, if non-zero, indicates the maximum Windows version at
* which this entry is valid. I'd use a union but then we'd need even more
* {} in the tables.
*/
drsys_sysnum_t num;
const char *name;
uint flags; /* SYSINFO_ flags */
uint return_type; /* not drsys_param_type_t so we can use extended SYSARG_TYPE_* */
int arg_count;
/* list of args that are not inlined: though we now store inlined too for drstrace */
sysinfo_arg_t arg[MAX_ARGS_IN_ENTRY];
/* For custom handling w/o separate number lookup.
* If SYSINFO_SECONDARY_TABLE is set in flags, this is instead
* a pointer to a new syscall_info_t table.
* (I'd use a union but that makes syscall table initializers uglier)
*/
drsys_sysnum_t *num_out;
} syscall_info_t;
typedef struct _cls_syscall_t {
/* the interface keeps state for API simplicity and for performance */
drsys_sysnum_t sysnum;
syscall_info_t *sysinfo;
dr_mcontext_t mc;
bool pre;
/* For recording args so post-syscall can examine.
* To maintain ordinals for 32-bit Mac, where syscalls can take 64-bit
* args, we have to widen what we store for 64-bit.
*/
uint64 sysarg[SYSCALL_NUM_ARG_STORE];
#ifdef WINDOWS
reg_t param_base;
#endif
/* for recording additional info for particular arg types */
ptr_int_t extra_info[EXTRA_INFO_MAX];
#ifdef DEBUG
/* We should be able to statically share extra_info[]. This helps find errors. */
bool extra_inuse[SYSCALL_NUM_ARG_STORE];
#endif
/* We need to store the size in pre for use in post (for i#1119)
* and we can't share sysarg_sz as some syscalls are both known
* and unknown.
*/
size_t sysarg_known_sz[SYSCALL_NUM_ARG_STORE];
bool first_iter;
bool first_iter_generic_loop; /* just for sysarg_get_size */
bool memargs_iterated; /* to enforce that post requires pre */
/* for comparing memory across unknown system calls */
bool known;
app_pc sysarg_ptr[SYSCALL_NUM_ARG_TRACK];
size_t sysarg_sz[SYSCALL_NUM_ARG_TRACK];
/* dynamically allocated */
size_t sysarg_val_bytes[SYSCALL_NUM_ARG_TRACK];
byte *sysarg_val[SYSCALL_NUM_ARG_TRACK];
/* for a writable info struct so we can set the sysnum */
syscall_info_t unknown_info;
} cls_syscall_t;
/* used for simpler arg passing among syscall arg handlers */
typedef struct _sysarg_iter_info_t {
drsys_arg_t *arg;
drsys_iter_cb_t cb_mem;
drsys_iter_cb_t cb_arg;
void *user_data;
cls_syscall_t *pt;
bool abort;
} sysarg_iter_info_t;
/* Type of callback for routine which returns secondary number. */
typedef uint (*drsys_get_secnum_cb_t)(const char *name, uint primary_number);
/* Hashtable maintained in os-specific code that maps drsys_sysnum_t to
* syscall_info_t*.
* To gain efficiency and merge static and dynamic queries, our API
* hands out an opaque copy of the syscall_info_t pointers that are
* stored in this table to the client.
* We assume the source tables pointed into are set at process init
* and never changed afterward.
*/
extern hashtable_t systable;
/* i#1549: We use separate hashtable to handle syscalls with secondary
* components.
*/
extern hashtable_t secondary_systable;
/* lock for systable & secondary_systable, maintained in drsyscall.c */
extern void *systable_lock;
drmf_status_t
drsyscall_os_init(void *drcontext);
void
drsyscall_os_exit(void);
syscall_info_t *
syscall_lookup(drsys_sysnum_t num, bool resolve_secondary);
void
drsyscall_os_thread_init(void *drcontext);
void
drsyscall_os_thread_exit(void *drcontext);
void
drsyscall_os_module_load(void *drcontext, const module_data_t *info, bool loaded);
bool
is_using_sysenter(void);
bool
is_using_sysint(void);
#ifdef WINDOWS
bool
is_using_wow64(void);
#endif
/* Either sets arg->reg to DR_REG_NULL and sets arg->start_addr, or sets arg->reg
* to non-DR_REG_NULL
*/
void
drsyscall_os_get_sysparam_location(cls_syscall_t *pt, uint argnum, drsys_arg_t *arg);
/* check syscall param at pre-syscall only */
void
check_sysparam(uint sysnum, uint argnum, dr_mcontext_t *mc, size_t argsz);
/* for memory shadowing checks */
void
os_handle_pre_syscall(void *drcontext, cls_syscall_t *pt, sysarg_iter_info_t *ii);
void
os_handle_post_syscall(void *drcontext, cls_syscall_t *pt, sysarg_iter_info_t *ii);
/* returns true if the given argument was processed in a non-standard way
* (e.g. OS-specific structures) and we should skip the standard check
*/
bool
os_handle_pre_syscall_arg_access(sysarg_iter_info_t *ii,
const sysinfo_arg_t *arg_info,
app_pc start, uint size);
/* returns true if the given argument was processed in a non-standard way
* (e.g. OS-specific structures) and we should skip the standard check
*/
bool
os_handle_post_syscall_arg_access(sysarg_iter_info_t *ii,
const sysinfo_arg_t *arg_info,
app_pc start, uint size);
bool
os_syscall_succeeded(drsys_sysnum_t sysnum, syscall_info_t *info, cls_syscall_t *pt);
/* Returns true if successful, yet we should skip automated table output
* params as we need custom output handling.
*/
bool
os_syscall_succeeded_custom(drsys_sysnum_t sysnum, syscall_info_t *info,
cls_syscall_t *pt);
#ifdef WINDOWS
bool
os_syscall_ret_small_write_last(syscall_info_t *info, ptr_int_t res);
#endif
bool
os_syscall_get_num(const char *name, drsys_sysnum_t *num DR_PARAM_OUT);
uint
sysnum_hash(void *val);
bool
sysnum_cmp(void *v1, void *v2);
bool
sysarg_invalid(sysinfo_arg_t *arg);
void
store_extra_info(cls_syscall_t *pt, int index, ptr_int_t value);
ptr_int_t
read_extra_info(cls_syscall_t *pt, int index);
bool
report_memarg_ex(sysarg_iter_info_t *iter_info,
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);
bool
report_memarg_type(sysarg_iter_info_t *iter_info,
int ordinal, uint arg_flags,
app_pc ptr, size_t sz, const char *id,
drsys_param_type_t type, const char *type_name);
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);
bool
report_memarg(sysarg_iter_info_t *iter_info,
const sysinfo_arg_t *arg_info,
app_pc ptr, size_t sz, const char *id);
bool
report_sysarg_return(void *drcontext, sysarg_iter_info_t *ii,
size_t sz, drsys_param_type_t type, const char *type_name);
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);
bool
report_sysarg(sysarg_iter_info_t *iter_info, int ordinal, uint arg_flags);
drsys_param_mode_t
mode_from_flags(uint arg_flags);
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);
bool
handle_sockaddr(cls_syscall_t *pt, sysarg_iter_info_t *ii, byte *ptr,
size_t len, int ordinal, uint arg_flags, const char *id);
#if DEBUG
void
report_callstack(void *drcontext, dr_mcontext_t *mc);
#endif /* DEBUG */
#endif /* _DRSYSCALL_OS_H_ */