blob: 50a02c38657d4623ccc1c41ef35ffddfd3582b50 [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2011-2014 Google, Inc. All rights reserved.
* Copyright (c) 2008-2010 VMware, Inc. All rights reserved.
* **********************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of VMware, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
#include <stdlib.h> /* for malloc */
#include <stdio.h>
#include <string.h>
#include "utils.h"
#include "share.h"
#include "utils.h"
#include "lib/dr_config.h"
#include "our_tchar.h"
#ifdef WINDOWS
# include <windows.h>
# include <io.h> /* for _get_osfhandle */
# include "processes.h"
# include "mfapi.h" /* for PLATFORM_WIN_2000 */
# define RELEASE32_DLL _TEXT("\\lib32\\release\\dynamorio.dll")
# define DEBUG32_DLL _TEXT("\\lib32\\debug\\dynamorio.dll")
# define RELEASE64_DLL _TEXT("\\lib64\\release\\dynamorio.dll")
# define DEBUG64_DLL _TEXT("\\lib64\\debug\\dynamorio.dll")
# define LOG_SUBDIR _TEXT("\\logs")
# define LIB32_SUBDIR _TEXT("\\lib32")
# define PREINJECT32_DLL _TEXT("\\lib32\\drpreinject.dll")
# define PREINJECT64_DLL _TEXT("\\lib64\\drpreinject.dll")
#else
# include <sys/stat.h>
# include <sys/types.h>
# include <unistd.h>
# ifdef MACOS
# include <sys/syscall.h>
# else
# include <syscall.h>
#endif
# define RELEASE32_DLL "/lib32/release/libdynamorio.so"
# define DEBUG32_DLL "/lib32/debug/libdynamorio.so"
# define RELEASE64_DLL "/lib64/release/libdynamorio.so"
# define DEBUG64_DLL "/lib64/debug/libdynamorio.so"
# define LOG_SUBDIR "/logs"
# define LIB32_SUBDIR "/lib32/"
extern bool
create_nudge_signal_payload(siginfo_t *info OUT, uint action_mask,
client_id_t client_id, uint64 client_arg);
#endif
/* The minimum option size is 3, e.g., "-x ". Note that we need the
* NULL term too so "-x -y" needs 6 characters.
*/
#define MAX_NUM_OPTIONS (DR_MAX_OPTIONS_LENGTH / 3)
/* Upper bound on the length of an fopen mode string, including the null
* terminator. For example, "rw+b\0" or "ra\0".
*/
enum { MAX_MODE_STRING_SIZE = 16 };
/* Data structs to hold info about the DYNAMORIO_OPTION registry entry */
typedef struct _client_opt_t {
TCHAR *path;
client_id_t id;
TCHAR *opts;
} client_opt_t;
typedef struct _opt_info_t {
dr_operation_mode_t mode;
TCHAR *extra_opts[MAX_NUM_OPTIONS];
size_t num_extra_opts;
/* note that clients are parsed and stored in priority order */
client_opt_t *client_opts[MAX_CLIENT_LIBS];
size_t num_clients;
} opt_info_t;
/* Does a straight copy when TCHAR is char, and a widening conversion when
* TCHAR is wchar_t. Does not null terminate for use in buffered printing.
*/
static void
convert_to_tchar(TCHAR *dst, const char *src, size_t dst_sz)
{
#ifdef _UNICODE
_snwprintf(dst, dst_sz, L"%S", src);
#else
strncpy(dst, src, dst_sz);
#endif
}
/* Function to iterate over the options in a DYNAMORIO_OPTIONS string.
* For the purposes of this function, we're not differentiating
* between an option and an option argument. We're simply looking for
* space-separated strings while taking into account that some strings
* can be quoted. 'ptr' should point to the current location in the
* options string; the option is copied to 'token'
*/
static TCHAR *
get_next_token(TCHAR* ptr, TCHAR *token)
{
/* advance to next non-space character */
while (*ptr == L' ') {
ptr++;
}
/* check for end-of-string */
if (*ptr == L'\0') {
token[0] = L'\0';
return NULL;
}
/* for quoted options, copy until the closing quote */
if (*ptr == L'\"') {
*token++ = *ptr++;
while (*ptr != L'\0') {
*token++ = *ptr++;
if (ptr[-1] == L'\"' && ptr[-2] != L'\\') {
break;
}
}
}
/* otherwise copy until the next space character */
else {
while (*ptr != L' ' && *ptr != L'\0') {
*token++ = *ptr++;
}
}
*token = L'\0';
return ptr;
}
/* Allocate a new client_opt_t */
static client_opt_t *
new_client_opt(const TCHAR *path, client_id_t id, const TCHAR *opts)
{
size_t len;
client_opt_t *opt = (client_opt_t *)malloc(sizeof(client_opt_t));
if (opt == NULL) {
return NULL;
}
opt->id = id;
len = MIN(MAXIMUM_PATH-1, _tcslen(path));
opt->path = malloc((len+1) * sizeof(opt->path[0]));
_tcsncpy(opt->path, path, len);
opt->path[len] = L'\0';
len = MIN(DR_MAX_OPTIONS_LENGTH-1, _tcslen(opts));
opt->opts = malloc((len+1) * sizeof(opt->opts[0]));
_tcsncpy(opt->opts, opts, len);
opt->opts[len] = L'\0';
return opt;
}
/* Free a client_opt_t */
static void
free_client_opt(client_opt_t *opt)
{
if (opt == NULL) {
return;
}
if (opt->path != NULL) {
free(opt->path);
}
if (opt->opts != NULL) {
free(opt->opts);
}
free(opt);
}
/* Add another client to an opt_info_t struct */
static dr_config_status_t
add_client_lib(opt_info_t *opt_info, client_id_t id, size_t pri,
const TCHAR *path, const TCHAR *opts)
{
size_t i;
if (opt_info->num_clients >= MAX_CLIENT_LIBS) {
return DR_FAILURE;
}
if (pri > opt_info->num_clients) {
return DR_ID_INVALID;
}
/* shift existing entries to make space for the new client info */
for (i=opt_info->num_clients; i>pri; i--) {
opt_info->client_opts[i] = opt_info->client_opts[i-1];
}
opt_info->client_opts[pri] = new_client_opt(path, id, opts);
opt_info->num_clients++;
return DR_SUCCESS;
}
static dr_config_status_t
remove_client_lib(opt_info_t *opt_info, client_id_t id)
{
size_t i, j;
for (i=0; i<opt_info->num_clients; i++) {
if (opt_info->client_opts[i]->id == id) {
free_client_opt(opt_info->client_opts[i]);
/* shift remaining entries down */
for (j=i; j<opt_info->num_clients-1; j++) {
opt_info->client_opts[j] = opt_info->client_opts[j+1];
}
opt_info->num_clients--;
return DR_SUCCESS;
}
}
return DR_ID_INVALID;
}
/* Add an 'extra' option (non-client related option) to an opt_info_t struct */
static dr_config_status_t
add_extra_option(opt_info_t *opt_info, const TCHAR *opt)
{
if (opt != NULL && opt[0] != L'\0') {
size_t idx, len;
idx = opt_info->num_extra_opts;
if (idx >= MAX_NUM_OPTIONS) {
return DR_FAILURE;
}
len = MIN(DR_MAX_OPTIONS_LENGTH-1, _tcslen(opt));
opt_info->extra_opts[idx] = malloc
((len+1) * sizeof(opt_info->extra_opts[idx][0]));
_tcsncpy(opt_info->extra_opts[idx], opt, len);
opt_info->extra_opts[idx][len] = L'\0';
opt_info->num_extra_opts++;
}
return DR_SUCCESS;
}
static dr_config_status_t
add_extra_option_char(opt_info_t *opt_info, const char *opt)
{
if (opt != NULL && opt[0] != '\0') {
TCHAR wbuf[DR_MAX_OPTIONS_LENGTH];
convert_to_tchar(wbuf, opt, DR_MAX_OPTIONS_LENGTH);
NULL_TERMINATE_BUFFER(wbuf);
return add_extra_option(opt_info, wbuf);
}
return DR_SUCCESS;
}
/* Free allocated memory in an opt_info_t */
static void
free_opt_info(opt_info_t *opt_info)
{
size_t i;
for (i=0; i<opt_info->num_clients; i++) {
free_client_opt(opt_info->client_opts[i]);
}
for (i=0; i<opt_info->num_extra_opts; i++) {
free(opt_info->extra_opts[i]);
}
}
/***************************************************************************
* i#85/PR 212034 and i#265/PR 486139: use config files
*
* The API uses char* here but be careful b/c this file is build w/ UNICODE
* so we use *A versions of Windows API routines.
* Also note that I left many types as TCHAR for less change in handling
* PARAMS_IN_REGISTRY and config files: eventually should convert all
* to char.
*/
#ifdef PARAMS_IN_REGISTRY
# define IF_REG_ELSE(x, y) x
# define PARAM_STR(name) L_IF_WIN(name)
#else
# define PARAM_STR(name) name
# define IF_REG_ELSE(x, y) y
#endif
#ifndef PARAMS_IN_REGISTRY
/* DYNAMORIO_VAR_CONFIGDIR is searched first, and then these: */
# ifdef WINDOWS
# define LOCAL_CONFIG_ENV "USERPROFILE"
# define LOCAL_CONFIG_SUBDIR "dynamorio"
# else
# define LOCAL_CONFIG_ENV "HOME"
# define LOCAL_CONFIG_SUBDIR ".dynamorio"
# endif
# define GLOBAL_CONFIG_SUBDIR "config"
# define CFG_SFX_64 "config64"
# define CFG_SFX_32 "config32"
# ifdef X64
# define CFG_SFX CFG_SFX_64
# else
# define CFG_SFX CFG_SFX_32
# endif
static const char *
get_config_sfx(dr_platform_t dr_platform)
{
if (dr_platform == DR_PLATFORM_DEFAULT)
return CFG_SFX;
else if (dr_platform == DR_PLATFORM_32BIT)
return CFG_SFX_32;
else if (dr_platform == DR_PLATFORM_64BIT)
return CFG_SFX_64;
else
DO_ASSERT(false);
return "";
}
static bool
env_var_exists(const char *name, char *buf, size_t buflen)
{
#ifdef WINDOWS
size_t len = GetEnvironmentVariableA(name, buf, (DWORD) buflen);
if (len == 0 || len > buflen)
return false;
#else
char *val = getenv(name);
if (val == NULL)
return false;
strncpy(buf, val, buflen);
buf[buflen - 1] = '\0';
#endif
return true;
}
/* If find_temp, will use a temp dir; else will fail if no standard config dir. */
static bool
get_config_dir(bool global, char *fname, size_t fname_len, bool find_temp)
{
char dir[MAXIMUM_PATH];
const char *subdir;
if (global) {
#ifdef WINDOWS
_snprintf(dir, BUFFER_SIZE_ELEMENTS(dir), TSTR_FMT, get_dynamorio_home());
NULL_TERMINATE_BUFFER(dir);
subdir = GLOBAL_CONFIG_SUBDIR;
#else
/* FIXME i#840: Support global config files by porting more of utils.c.
*/
return false;
#endif
} else {
/* DYNAMORIO_CONFIGDIR takes precedence */
if (!env_var_exists(DYNAMORIO_VAR_CONFIGDIR, dir, BUFFER_SIZE_ELEMENTS(dir))) {
if (!env_var_exists(LOCAL_CONFIG_ENV, dir, BUFFER_SIZE_ELEMENTS(dir))) {
if (!find_temp)
return false;
/* Attempt to make things work for non-interactive users (i#939) */
if (!env_var_exists("TMP", dir, BUFFER_SIZE_ELEMENTS(dir)) &&
!env_var_exists("TEMP", dir, BUFFER_SIZE_ELEMENTS(dir)) &&
!env_var_exists("TMPDIR", dir, BUFFER_SIZE_ELEMENTS(dir))) {
#ifdef WINDOWS
/* There is no straightforward hardcoded fallback for temp dirs
* on Windows. But for that reason even a sandbox will leave
* TMP and/or TEMP set so we don't expect to hit this case.
*/
return false;
#else
/* Prefer /tmp to PWD as the former is more likely writable */
strncpy(dir, "/tmp", BUFFER_SIZE_ELEMENTS(dir));
NULL_TERMINATE_BUFFER(dir);
if (!file_exists(dir) &&
!env_var_exists("PWD", dir, BUFFER_SIZE_ELEMENTS(dir)))
return false;
#endif
}
}
/* For anon config files (.0config32), we set DYNAMORIO_VAR_CONFIGDIR to be
* either LOCAL_CONFIG_ENV or TMP to ensure DR finds the same config file!
*/
#ifdef WINDOWS
if (!SetEnvironmentVariableA(DYNAMORIO_VAR_CONFIGDIR, dir))
return false;
#else
if (setenv(DYNAMORIO_VAR_CONFIGDIR, dir, 0) != 0)
return false;
#endif
}
subdir = LOCAL_CONFIG_SUBDIR;
}
_snprintf(fname, fname_len, "%s/%s", dir, subdir);
fname[fname_len - 1] = '\0';
return true;
}
/* No support yet here to create some types of files the core supports:
* - system config dir by reading home reg key: plan is to
* add a global setting to use that, so no change to params in API
* - default0.config
*/
static bool
get_config_file_name(const char *process_name,
process_id_t pid,
bool global,
dr_platform_t dr_platform,
char *fname,
size_t fname_len)
{
size_t dir_len;
/* i#939: we can't fall back to tmp dirs here b/c it's too late to set
* the DYNAMORIO_CONFIGDIR env var (child is already created).
*/
if (!get_config_dir(global, fname, fname_len, false)) {
DO_ASSERT(false && "get_config_dir failed");
return false;
}
#ifdef WINDOWS
/* make sure subdir exists*/
if (!CreateDirectoryA(fname, NULL) &&
GetLastError() != ERROR_ALREADY_EXISTS) {
DO_ASSERT(false && "failed to create subdir");
return false;
}
#else
{
struct stat st;
mkdir(fname, 0770);
if (stat(fname, &st) != 0 || !S_ISDIR(st.st_mode)) {
DO_ASSERT(false && "failed to create subdir");
return false;
}
}
#endif
dir_len = strlen(fname);
if (pid > 0) {
/* <root>/appname.<pid>.1config */
_snprintf(fname + dir_len, fname_len - dir_len, "/%s.%d.1%s",
process_name, pid, get_config_sfx(dr_platform));
} else {
/* <root>/appname.config */
_snprintf(fname + dir_len, fname_len - dir_len, "/%s.%s",
process_name, get_config_sfx(dr_platform));
}
fname[fname_len - 1] = '\0';
return true;
}
static FILE *
open_config_file(const char *process_name,
process_id_t pid,
bool global,
dr_platform_t dr_platform,
bool read, bool write, bool overwrite)
{
TCHAR wfname[MAXIMUM_PATH];
char fname[MAXIMUM_PATH];
char mode[MAX_MODE_STRING_SIZE];
int i = 0;
FILE *f;
DO_ASSERT(read || write);
DO_ASSERT(!(read && overwrite) && "read+overwrite incompatible");
if (!get_config_file_name(process_name, pid, global, dr_platform,
fname, BUFFER_SIZE_ELEMENTS(fname))) {
DO_ASSERT(false && "get_config_file_name failed");
return NULL;
}
/* XXX: Checking for existence before opening is racy. */
convert_to_tchar(wfname, fname, BUFFER_SIZE_ELEMENTS(wfname));
NULL_TERMINATE_BUFFER(wfname);
if (!read && write && !overwrite && file_exists(wfname)) {
return NULL;
}
/* Careful, Windows fopen aborts the process on invalid mode strings.
* Order matters. Modifiers like 'b' have to come after standard characters
* like r, w, and +.
*/
if (read)
mode[i++] = 'r';
if (write)
mode[i++] = (read ? '+' : 'w');
mode[i++] = 'b'; /* Avoid CRLF translation on Windows. */
mode[i++] = '\0';
DO_ASSERT(i <= BUFFER_SIZE_ELEMENTS(mode));
NULL_TERMINATE_BUFFER(mode);
f = fopen(fname, mode);
return f;
}
static void
trim_trailing_newline(TCHAR *line)
{
TCHAR *cur = line + _tcslen(line) - 1;
while (cur >= line && (*cur == '\n' || *cur == '\r')) {
*cur = '\0';
cur--;
}
}
/* Copies the value for var, converted to a TCHAR, into val. If elide is true,
* also overwrites var and its value in the file with all lines subsequent,
* allowing for a simple append to change the value (the file must have been
* opened with both read and write access).
*/
static bool
read_config_ex(FILE *f, const char *var, TCHAR *val, size_t val_len,
bool elide)
{
bool found = false;
/* FIXME: share code w/ core/config.c */
# define BUFSIZE (MAX_CONFIG_VALUE+128)
char line[BUFSIZE];
size_t var_len = strlen(var);
/* Offsets into the file for the start and end of var. */
size_t var_start = 0;
size_t var_end = 0;
/* each time we start from beginning: we assume a small file */
if (f == NULL)
return false;
if (fseek(f, 0, SEEK_SET))
return false;
while (fgets(line, BUFFER_SIZE_ELEMENTS(line), f) != NULL) {
fflush(stdout);
/* Find lines starting with VAR=. */
if (strncmp(line, var, var_len) == 0 && line[var_len] == '=') {
found = true;
var_end = var_start + strlen(line);
if (val != NULL) {
convert_to_tchar(val, line+var_len+1, val_len);
val[val_len-1] = '\0';
trim_trailing_newline(val);
}
break;
}
var_start += strlen(line);
fflush(stdout);
}
/* If elide is true, seek back to the line, delete it, and shift the rest of
* the file backward. It's easier to do this with a fixed size buffer than
* it is to it line-by-line with fgets/fputs.
*/
if (found && elide) {
/* Use long instead of ssize_t to match FILE* API. */
long write_cur = (long) var_start;
long read_cur = (long) var_end;
long bufread;
fflush(stdout);
while (true) {
fseek(f, read_cur, SEEK_SET);
bufread = (long) fread(line, 1, BUFFER_SIZE_ELEMENTS(line), f);
DO_ASSERT(ferror(f) == 0);
line[bufread] = '\0';
fflush(stdout);
fseek(f, write_cur, SEEK_SET);
if (bufread > 0) {
fwrite(line, 1, bufread, f);
DO_ASSERT(ferror(f) == 0);
read_cur += bufread;
write_cur += bufread;
} else {
break;
}
}
#ifdef WINDOWS
/* XXX: Can't find a way to truncate the file at the current position
* using the FILE API.
*/
SetEndOfFile((HANDLE)_get_osfhandle(_fileno(f)));
#else
{
int r;
off_t pos = lseek(fileno(f), 0, SEEK_CUR);
if (pos == -1)
return false;
r = ftruncate(fileno(f), (off_t)pos);
if (r != 0)
return false;
}
#endif
}
return found;
}
/* for simplest coexistence with PARAMS_IN_REGISTRY taking in TCHAR and
* converting to char. not very efficient though.
*/
static dr_config_status_t
write_config_param(FILE *f, const char *var, const TCHAR *val)
{
size_t written;
int len;
char buf[MAX_CONFIG_VALUE];
DO_ASSERT(f != NULL);
len = _snprintf(buf, BUFFER_SIZE_ELEMENTS(buf), "%s="TSTR_FMT"\n", var, val);
/* don't remove the newline: better to truncate options than to have none (i#547) */
buf[BUFFER_SIZE_ELEMENTS(buf) - 2] = '\n';
buf[BUFFER_SIZE_ELEMENTS(buf) - 1] = '\0';
written = fwrite(buf, 1, strlen(buf), f);
DO_ASSERT(written == strlen(buf));
if (len < 0)
return DR_CONFIG_STRING_TOO_LONG;
if (written != strlen(buf))
return DR_CONFIG_FILE_WRITE_FAILED;
return DR_SUCCESS;
}
static bool
read_config_param(FILE *f, const char *var, TCHAR *val, size_t val_len)
{
return read_config_ex(f, var, val, val_len, false);
}
#else /* !PARAMS_IN_REGISTRY */
static dr_config_status_t
write_config_param(ConfigGroup *policy, const TCHAR *var, const TCHAR *val)
{
set_config_group_parameter(policy, var, val);
return DR_SUCCESS;
}
static bool
read_config_param(FILE *f, const char *var, const TCHAR *val, size_t val_len)
{
TCHAR *ptr = get_config_group_parameter(proc_policy, L_DYNAMORIO_VAR_OPTIONS);
if (ptr == NULL)
return false;
_sntprintf(val, val_len, _TEXT("%s"), ptr);
return true;
}
#endif /* PARAMS_IN_REGISTRY */
/***************************************************************************/
/* Read a DYNAMORIO_OPTIONS string from 'wbuf' and populate an opt_info_t struct */
static dr_config_status_t
read_options(opt_info_t *opt_info, IF_REG_ELSE(ConfigGroup *proc_policy, FILE *f))
{
TCHAR buf[MAX_CONFIG_VALUE];
TCHAR *ptr, token[DR_MAX_OPTIONS_LENGTH], tmp[DR_MAX_OPTIONS_LENGTH];
opt_info_t null_opt_info = {0,};
size_t len;
*opt_info = null_opt_info;
if (!read_config_param(IF_REG_ELSE(proc_policy, f), PARAM_STR(DYNAMORIO_VAR_OPTIONS),
buf, BUFFER_SIZE_ELEMENTS(buf)))
return DR_FAILURE;
ptr = buf;
/* We'll be safe and not trust that get_config_group_parameter
* returns a nice NULL-terminated string with no more than
* DR_MAX_OPTIONS_LENGTH characters. Making sure we have such a
* string makes get_next_token() simpler. A more efficient
* approach would be to keep track of a string length and pass
* that to get_next_token().
*/
len = MIN(DR_MAX_OPTIONS_LENGTH-1, _tcslen(ptr));
_tcsncpy(tmp, ptr, len);
tmp[len] = L'\0';
opt_info->mode = DR_MODE_NONE;
ptr = tmp;
while (ptr != NULL) {
ptr = get_next_token(ptr, token);
/*
* look for the mode
*/
if (_tcscmp(token, _TEXT("-code_api")) == 0) {
if (opt_info->mode != DR_MODE_NONE) {
goto error;
}
opt_info->mode = DR_MODE_CODE_MANIPULATION;
}
#ifdef MF_API
else if (_tcscmp(token, _TEXT("-security_api")) == 0) {
if (opt_info->mode != DR_MODE_NONE) {
goto error;
}
opt_info->mode = DR_MODE_MEMORY_FIREWALL;
}
#endif
else if (_tcscmp(token, _TEXT("-probe_api")) == 0) {
#ifdef PROBE_API
/* nothing; we assign the mode when we see -code_api */
#else
/* we shouldn't see -probe_api */
goto error;
#endif
}
#ifdef PROBE_API
else if (_tcscmp(token, _TEXT("-hotp_only")) == 0) {
if (opt_info->mode != DR_MODE_NONE) {
goto error;
}
opt_info->mode = DR_MODE_PROBE;
}
#endif
/*
* look for client options
*/
else if (_tcscmp(token, _TEXT("-client_lib")) == 0) {
TCHAR *path_str, *id_str, *opt_str;
client_id_t id;
ptr = get_next_token(ptr, token);
if (ptr == NULL) {
goto error;
}
/* handle enclosing quotes */
path_str = token;
if (path_str[0] == L'\"') {
size_t last;
path_str++;
last = _tcslen(path_str)-1;
if (path_str[last] != L'\"') {
goto error;
}
path_str[last] = L'\0';
}
/* -client_lib options should have the form path;ID;options.
* Client priority is left-to-right.
*/
id_str = _tcsstr(path_str, _TEXT(";"));
if (id_str == NULL) {
goto error;
}
*id_str = L'\0';
id_str++;
opt_str = _tcsstr(id_str, _TEXT(";"));
if (opt_str == NULL) {
goto error;
}
*opt_str = L'\0';
opt_str++;
/* client IDs are in hex */
id = _tcstoul(id_str, NULL, 16);
/* add the client info to our opt_info structure */
if (add_client_lib(opt_info, id, opt_info->num_clients,
path_str, opt_str) != DR_SUCCESS) {
goto error;
}
}
/*
* Any remaining options are not related to clients. Put all
* these options (and their arguments) in one array.
*/
else {
if (add_extra_option(opt_info, token) != DR_SUCCESS) {
goto error;
}
}
}
fflush(stdout);
return DR_SUCCESS;
error:
free_opt_info(opt_info);
*opt_info = null_opt_info;
return DR_FAILURE;
}
/* Write the options stored in an opt_info_t to 'wbuf' in the form expected
* by the DYNAMORIO_OPTIONS registry entry.
*/
static dr_config_status_t
write_options(opt_info_t *opt_info, TCHAR *wbuf)
{
size_t i;
const char *mode_str = "";
ssize_t len;
ssize_t sofar = 0;
ssize_t bufsz = DR_MAX_OPTIONS_LENGTH;
/* NOTE - mode_str must come first since we want to give
* client-supplied options the chance to override (for
* ex. -stack_size which -code_api sets, see PR 247436
*/
switch (opt_info->mode) {
#ifdef MF_API
case DR_MODE_MEMORY_FIREWALL:
mode_str = "-security_api";
break;
#endif
case DR_MODE_CODE_MANIPULATION:
#ifdef PROBE_API
mode_str = "-code_api -probe_api";
#else
mode_str = "-code_api";
#endif
break;
#ifdef PROBE_API
case DR_MODE_PROBE:
mode_str = "-probe_api -hotp_only";
break;
#endif
case DR_MODE_DO_NOT_RUN:
/* this is a mode b/c can't add dr_register_process param w/o breaking
* backward compat, so just ignore in terms of options, user has to
* re-reg anyway to re-enable and can specify mode then.
*/
mode_str = "";
break;
default:
#ifndef CLIENT_INTERFACE
/* no API's so no added options */
mode_str = "";
break;
#else
DO_ASSERT(false);
#endif
}
len = _sntprintf(wbuf + sofar, bufsz - sofar, _TEXT(TSTR_FMT), mode_str);
if (len >= 0 && len <= bufsz - sofar)
sofar += len;
/* extra options */
for (i=0; i<opt_info->num_extra_opts; i++) {
/* FIXME: Note that we're blindly allowing any options
* provided so we can allow users to specify "undocumented"
* options. Maybe we should be checking that the options are
* actually valid?
*/
len = _sntprintf(wbuf + sofar, bufsz - sofar, _TEXT(" %s"),
opt_info->extra_opts[i]);
if (len >= 0 && len <= bufsz - sofar)
sofar += len;
}
/* client lib options */
for (i=0; i<opt_info->num_clients; i++) {
client_opt_t *client_opts = opt_info->client_opts[i];
/* i#1542: pick a delimiter that avoids conflicts w/ the client strings */
char delim = '\"';
if (strchr(client_opts->path, delim) || strchr(client_opts->opts, delim)) {
delim = '\'';
if (strchr(client_opts->path, delim) || strchr(client_opts->opts, delim)) {
delim = '`';
if (strchr(client_opts->path, delim) ||
strchr(client_opts->opts, delim)) {
return DR_CONFIG_OPTIONS_INVALID;
}
}
}
/* no ; allowed */
if (strchr(client_opts->path, ';') || strchr(client_opts->opts, ';'))
return DR_CONFIG_OPTIONS_INVALID;
len = _sntprintf(wbuf + sofar, bufsz - sofar,
_TEXT(" -client_lib %c%s;%x;%s%c"), delim,
client_opts->path, client_opts->id, client_opts->opts, delim);
if (len >= 0 && len <= bufsz - sofar)
sofar += len;
}
wbuf[DR_MAX_OPTIONS_LENGTH-1] = L'\0';
return DR_SUCCESS;
}
#ifdef PARAMS_IN_REGISTRY
/* caller must call free_config_group() on the returned ConfigGroup */
static ConfigGroup *
get_policy(dr_platform_t dr_platform)
{
ConfigGroup *policy;
/* PR 244206: set the registry view before any registry access.
* If we are ever part of a persistent agent maybe we should
* restore to the default platform at the end of this routine?
*/
set_dr_platform(dr_platform);
if (read_config_group(&policy, L_PRODUCT_NAME, TRUE) != ERROR_SUCCESS) {
return NULL;
}
return policy;
}
/* As a sub policy only the parent policy (from get_policy()) need be freed */
static ConfigGroup *
get_proc_policy(ConfigGroup *policy, const char *process_name)
{
ConfigGroup *res = NULL;
if (policy != NULL) {
TCHAR wbuf[MAXIMUM_PATH];
convert_to_tchar(wbuf, process_name, MAXIMUM_PATH);
NULL_TERMINATE_BUFFER(wbuf);
res = get_child(wbuf, policy);
}
return res;
}
#endif /* PARAMS_IN_REGISTRY */
static bool
platform_is_64bit(dr_platform_t platform)
{
return (platform == DR_PLATFORM_64BIT
IF_X64(|| platform == DR_PLATFORM_DEFAULT));
}
/* FIXME i#840: Syswide NYI for Linux. */
#ifdef WINDOWS
static void
get_syswide_path(TCHAR *wbuf,
const char *dr_root_dir)
{
TCHAR path[MAXIMUM_PATH];
int len;
if (!platform_is_64bit(get_dr_platform()))
_sntprintf(path, MAXIMUM_PATH, _TEXT("%S")PREINJECT32_DLL, dr_root_dir);
else
_sntprintf(path, MAXIMUM_PATH, _TEXT("%S")PREINJECT64_DLL, dr_root_dir);
path[MAXIMUM_PATH - 1] = '\0';
/* spaces are separator in AppInit so use short path */
len = GetShortPathName(path, wbuf, MAXIMUM_PATH);
DO_ASSERT(len > 0);
wbuf[MAXIMUM_PATH - 1] = '\0';
}
dr_config_status_t
dr_register_syswide(dr_platform_t dr_platform,
const char *dr_root_dir)
{
TCHAR wbuf[MAXIMUM_PATH];
set_dr_platform(dr_platform);
/* Set the appinit key */
get_syswide_path(wbuf, dr_root_dir);
/* Always overwrite, in case we have an older drpreinject version in there */
if (set_custom_autoinjection(wbuf, APPINIT_OVERWRITE) != ERROR_SUCCESS ||
(is_vista() && set_loadappinit() != ERROR_SUCCESS)) {
return DR_FAILURE;
}
return DR_SUCCESS;
}
dr_config_status_t
dr_unregister_syswide(dr_platform_t dr_platform,
const char *dr_root_dir)
{
TCHAR wbuf[MAXIMUM_PATH];
set_dr_platform(dr_platform);
/* Set the appinit key */
get_syswide_path(wbuf, dr_root_dir);
if (unset_custom_autoinjection(wbuf, APPINIT_OVERWRITE) != ERROR_SUCCESS)
return DR_FAILURE;
/* We leave Vista loadappinit on */
return DR_SUCCESS;
}
bool
dr_syswide_is_on(dr_platform_t dr_platform,
const char *dr_root_dir)
{
TCHAR wbuf[MAXIMUM_PATH];
set_dr_platform(dr_platform);
/* Set the appinit key */
get_syswide_path(wbuf, dr_root_dir);
return CAST_TO_bool(is_custom_autoinjection_set(wbuf));
}
#endif /* WINDOWS */
dr_config_status_t
dr_register_process(const char *process_name,
process_id_t pid,
bool global,
const char *dr_root_dir,
dr_operation_mode_t dr_mode,
bool debug,
dr_platform_t dr_platform,
const char *dr_options)
{
#ifdef PARAMS_IN_REGISTRY
ConfigGroup *policy, *proc_policy;
#else
FILE *f;
#endif
TCHAR wbuf[MAX(MAXIMUM_PATH,DR_MAX_OPTIONS_LENGTH)];
IF_WINDOWS(DWORD platform;)
opt_info_t opt_info = {0,};
dr_config_status_t status;
#ifdef PARAMS_IN_REGISTRY
/* PR 244206: set the registry view before any registry access.
* If we are ever part of a persistent agent maybe we should
* restore to the default platform at the end of this routine?
*/
set_dr_platform(dr_platform);
/* create top-level Determina/SecureCore key */
if (create_root_key() != ERROR_SUCCESS) {
return DR_FAILURE;
}
if (read_config_group(&policy, L_PRODUCT_NAME, TRUE) != ERROR_SUCCESS) {
return DR_FAILURE;
}
/* create process key */
convert_to_tchar(wbuf, process_name, MAXIMUM_PATH);
NULL_TERMINATE_BUFFER(wbuf);
proc_policy = get_child(wbuf, policy);
if (proc_policy == NULL) {
proc_policy = new_config_group(wbuf);
add_config_group(policy, proc_policy);
}
else {
return DR_PROC_REG_EXISTS;
}
#else
f = open_config_file(process_name, pid, global, dr_platform,
false/*!read*/, true/*write*/,
pid != 0/*overwrite for pid-specific*/);
if (f == NULL) {
# ifdef WINDOWS
int err = GetLastError();
if (err == ERROR_ALREADY_EXISTS)
return DR_PROC_REG_EXISTS;
else
# endif
return DR_CONFIG_DIR_NOT_FOUND;
}
#endif
/* set the rununder string */
_sntprintf(wbuf, MAXIMUM_PATH, (dr_mode == DR_MODE_DO_NOT_RUN) ? _TEXT("0") : _TEXT("1"));
NULL_TERMINATE_BUFFER(wbuf);
status = write_config_param(IF_REG_ELSE(proc_policy, f),
PARAM_STR(DYNAMORIO_VAR_RUNUNDER), wbuf);
DO_ASSERT(status == DR_SUCCESS);
if (status != DR_SUCCESS)
return status;
/* set the autoinject string (i.e., path to dynamorio.dll */
if (debug) {
if (!platform_is_64bit(dr_platform))
_sntprintf(wbuf, MAXIMUM_PATH, _TEXT(TSTR_FMT)DEBUG32_DLL, dr_root_dir);
else
_sntprintf(wbuf, MAXIMUM_PATH, _TEXT(TSTR_FMT)DEBUG64_DLL, dr_root_dir);
} else {
if (!platform_is_64bit(dr_platform))
_sntprintf(wbuf, MAXIMUM_PATH, _TEXT(TSTR_FMT)RELEASE32_DLL, dr_root_dir);
else
_sntprintf(wbuf, MAXIMUM_PATH, _TEXT(TSTR_FMT)RELEASE64_DLL, dr_root_dir);
}
NULL_TERMINATE_BUFFER(wbuf);
status = write_config_param(IF_REG_ELSE(proc_policy, f),
PARAM_STR(DYNAMORIO_VAR_AUTOINJECT), wbuf);
DO_ASSERT(status == DR_SUCCESS);
if (status != DR_SUCCESS)
return status;
/* set the logdir string */
/* XXX i#886: should we expose this in the dr_register_process() params (and
* thus dr_process_is_registered() and dr_registered_process_iterator_next())?
* We now have a -logdir runtime option so we don't need to expose it for full
* functionality anymore but it would serve to reduce the length of option
* strings to have more control over the default. Linux dr{config,run} does
* allow such control today.
*/
_sntprintf(wbuf, MAXIMUM_PATH, _TEXT(TSTR_FMT)LOG_SUBDIR, dr_root_dir);
NULL_TERMINATE_BUFFER(wbuf);
status = write_config_param(IF_REG_ELSE(proc_policy, f),
PARAM_STR(DYNAMORIO_VAR_LOGDIR), wbuf);
DO_ASSERT(status == DR_SUCCESS);
if (status != DR_SUCCESS)
return status;
/* set the options string last for faster updating w/ config files */
opt_info.mode = dr_mode;
add_extra_option_char(&opt_info, dr_options);
status = write_options(&opt_info, wbuf);
if (status != DR_SUCCESS)
return status;
status = write_config_param(IF_REG_ELSE(proc_policy, f),
PARAM_STR(DYNAMORIO_VAR_OPTIONS), wbuf);
free_opt_info(&opt_info);
DO_ASSERT(status == DR_SUCCESS);
if (status != DR_SUCCESS)
return status;
#ifdef PARAMS_IN_REGISTRY
/* write the registry */
if (write_config_group(policy) != ERROR_SUCCESS) {
DO_ASSERT(false);
return DR_FAILURE;
}
#else
fclose(f);
#endif
#ifdef WINDOWS
/* If on win2k, copy drearlyhelper?.dll to system32
* FIXME: this requires admin privs! oh well: only issue is early inject
* on win2k...
*/
if (get_platform(&platform) == ERROR_SUCCESS && platform == PLATFORM_WIN_2000) {
_sntprintf(wbuf, MAXIMUM_PATH, _TEXT(TSTR_FMT)LIB32_SUBDIR, dr_root_dir);
NULL_TERMINATE_BUFFER(wbuf);
copy_earlyhelper_dlls(wbuf);
}
#endif
return DR_SUCCESS;
}
dr_config_status_t
dr_unregister_process(const char *process_name,
process_id_t pid,
bool global,
dr_platform_t dr_platform)
{
#ifndef PARAMS_IN_REGISTRY
char fname[MAXIMUM_PATH];
if (get_config_file_name(process_name, pid, global, dr_platform,
fname, BUFFER_SIZE_ELEMENTS(fname))) {
if (remove(fname) == 0)
return DR_SUCCESS;
}
return DR_FAILURE;
#else
ConfigGroup *policy = get_policy(dr_platform);
ConfigGroup *proc_policy = get_proc_policy(policy, process_name);
TCHAR wbuf[MAXIMUM_PATH];
dr_config_status_t status = DR_SUCCESS;
if (proc_policy == NULL) {
status = DR_PROC_REG_INVALID;
goto exit;
}
/* remove it */
convert_to_tchar(wbuf, process_name, MAXIMUM_PATH);
NULL_TERMINATE_BUFFER(wbuf);
remove_child(wbuf, policy);
policy->should_clear = TRUE;
/* write the registry */
if (write_config_group(policy) != ERROR_SUCCESS) {
status = DR_FAILURE;
goto exit;
}
/* FIXME PR 232738: we should remove the drdearlyhelp?.dlls and preinject
* from system32, and remove the base reg keys, if dr_unregister_process()
* removes the last registered process.
*/
exit:
if (policy != NULL)
free_config_group(policy);
return status;
#endif
}
/* For !PARAMS_IN_REGISTRY, process_name is NOT filled in! */
static void
read_process_policy(IF_REG_ELSE(ConfigGroup *proc_policy, FILE *f),
char *process_name /* OUT */,
char *dr_root_dir /* OUT */,
dr_operation_mode_t *dr_mode /* OUT */,
bool *debug /* OUT */,
char *dr_options /* OUT */)
{
TCHAR autoinject[MAX_CONFIG_VALUE];
opt_info_t opt_info;
if (dr_mode != NULL)
*dr_mode = DR_MODE_NONE;
if (dr_root_dir != NULL)
*dr_root_dir = '\0';
if (dr_options != NULL)
*dr_options = '\0';
#ifdef PARAMS_IN_REGISTRY
if (process_name != NULL)
*process_name = '\0';
if (process_name != NULL && proc_policy->name != NULL) {
SIZE_T len = MIN(_tcslen(proc_policy->name), MAXIMUM_PATH-1);
_snprintf(process_name, len, TSTR_FMT, proc_policy->name);
process_name[len] = '\0';
}
#else
/* up to caller to fill in! */
#endif
if (dr_root_dir != NULL &&
read_config_param(IF_REG_ELSE(proc_policy, f),
PARAM_STR(DYNAMORIO_VAR_AUTOINJECT),
autoinject, BUFFER_SIZE_ELEMENTS(autoinject))) {
TCHAR *vers = _tcsstr(autoinject, RELEASE32_DLL);
if (vers == NULL) {
vers = _tcsstr(autoinject, DEBUG32_DLL);
}
if (vers == NULL) {
vers = _tcsstr(autoinject, RELEASE64_DLL);
}
if (vers == NULL) {
vers = _tcsstr(autoinject, DEBUG64_DLL);
}
if (vers != NULL) {
size_t len = MIN(MAXIMUM_PATH-1, vers - autoinject);
_snprintf(dr_root_dir, len, TSTR_FMT, autoinject);
dr_root_dir[len] = '\0';
}
else {
dr_root_dir[0] = '\0';
}
}
if (read_options(&opt_info, IF_REG_ELSE(proc_policy, f)) != DR_SUCCESS) {
/* note: read_options() frees any memory it allocates if it fails */
return;
}
if (dr_mode != NULL) {
*dr_mode = opt_info.mode;
if (read_config_param(IF_REG_ELSE(proc_policy, f),
PARAM_STR(DYNAMORIO_VAR_RUNUNDER),
autoinject, BUFFER_SIZE_ELEMENTS(autoinject))) {
if (_tcscmp(autoinject, _TEXT("0")) == 0)
*dr_mode = DR_MODE_DO_NOT_RUN;
}
}
if (debug != NULL) {
if (_tcsstr(autoinject, DEBUG32_DLL) != NULL ||
_tcsstr(autoinject, DEBUG64_DLL) != NULL) {
*debug = true;
}
else {
*debug = false;
}
}
if (dr_options != NULL) {
uint i;
size_t len_remain = DR_MAX_OPTIONS_LENGTH-1, cur_off = 0;
dr_options[0] = '\0';
for (i = 0; i < opt_info.num_extra_opts; i++) {
size_t len;
if (i > 0 && len_remain > 0) {
len_remain--;
dr_options[cur_off++] = ' ';
}
len = MIN(len_remain, _tcslen(opt_info.extra_opts[i]));
_snprintf(dr_options+cur_off, len, TSTR_FMT, opt_info.extra_opts[i]);
cur_off += len;
len_remain -= len;
dr_options[cur_off] = '\0';
}
}
free_opt_info(&opt_info);
}
/* FIXME i#840: NYI for Linux, need a FindFirstFile equivalent. */
#ifdef WINDOWS
struct _dr_registered_process_iterator_t {
#ifdef PARAMS_IN_REGISTRY
ConfigGroup *policy;
ConfigGroup *cur;
#else
bool has_next;
HANDLE find_handle;
/* because UNICODE is defined we have to use the wide version of
* FindFirstFile and convert back and forth
*/
WIN32_FIND_DATA find_data;
/* FindFirstFile only fills in the basename */
char dir[MAXIMUM_PATH];
char fname[MAXIMUM_PATH];
#endif
};
dr_registered_process_iterator_t *
dr_registered_process_iterator_start(dr_platform_t dr_platform,
bool global)
{
dr_registered_process_iterator_t *iter = (dr_registered_process_iterator_t *)
malloc(sizeof(dr_registered_process_iterator_t));
#ifdef PARAMS_IN_REGISTRY
iter->policy = get_policy(dr_platform);
if (iter->policy != NULL)
iter->cur = iter->policy->children;
else
iter->cur = NULL;
#else
if (!get_config_dir(global, iter->dir, BUFFER_SIZE_ELEMENTS(iter->dir), false)) {
iter->has_next = false;
return iter;
}
_sntprintf(iter->fname, BUFFER_SIZE_ELEMENTS(iter->fname),
_TEXT("%S/*.%S"), iter->dir, get_config_sfx(dr_platform));
NULL_TERMINATE_BUFFER(iter->fname);
iter->find_handle = FindFirstFile(iter->fname, &iter->find_data);
iter->has_next = (iter->find_handle != INVALID_HANDLE_VALUE);
#endif
return iter;
}
bool
dr_registered_process_iterator_hasnext(dr_registered_process_iterator_t *iter)
{
#ifdef PARAMS_IN_REGISTRY
return iter->policy != NULL && iter->cur != NULL;
#else
return iter->has_next;
#endif
}
bool
dr_registered_process_iterator_next(dr_registered_process_iterator_t *iter,
char *process_name /* OUT */,
char *dr_root_dir /* OUT */,
dr_operation_mode_t *dr_mode /* OUT */,
bool *debug /* OUT */,
char *dr_options /* OUT */)
{
#ifdef PARAMS_IN_REGISTRY
read_process_policy(iter->cur, process_name, dr_root_dir, dr_mode, debug, dr_options);
iter->cur = iter->cur->next;
return true;
#else
bool ok = true;
FILE *f;
_sntprintf(iter->fname, BUFFER_SIZE_ELEMENTS(iter->fname),
_TEXT("%S/%s"), iter->dir, iter->find_data.cFileName);
NULL_TERMINATE_BUFFER(iter->fname);
f = fopen(iter->fname, "r");
if (process_name != NULL) {
TCHAR *end;
end = _tcsstr(iter->find_data.cFileName, _TEXT(".config"));
if (end == NULL) {
process_name[0] = '\0';
ok = false;
} else {
# ifdef UNICODE
_snprintf(process_name, end - iter->find_data.cFileName, "%S",
iter->find_data.cFileName);
# else
_snprintf(process_name, end - iter->find_data.cFileName, "%s",
iter->find_data.cFileName);
# endif
}
}
if (!FindNextFile(iter->find_handle, &iter->find_data))
iter->has_next = false;
if (f == NULL || !ok)
return false;
read_process_policy(f, process_name, dr_root_dir, dr_mode, debug, dr_options);
fclose(f);
return true;
#endif
}
void
dr_registered_process_iterator_stop(dr_registered_process_iterator_t *iter)
{
#ifdef PARAMS_IN_REGISTRY
if (iter->policy != NULL)
free_config_group(iter->policy);
#else
if (iter->find_handle != INVALID_HANDLE_VALUE)
FindClose(iter->find_handle);
#endif
free(iter);
}
#endif /* WINDOWS */
bool
dr_process_is_registered(const char *process_name,
process_id_t pid,
bool global,
dr_platform_t dr_platform /* OUT */,
char *dr_root_dir /* OUT */,
dr_operation_mode_t *dr_mode /* OUT */,
bool *debug /* OUT */,
char *dr_options /* OUT */)
{
bool result = false;
#ifdef PARAMS_IN_REGISTRY
ConfigGroup *policy = get_policy(dr_platform);
ConfigGroup *proc_policy = get_proc_policy(policy, process_name);
#else
FILE *f = open_config_file(process_name, pid, global, dr_platform,
true/*read*/, false/*!write*/, false/*!overwrite*/);
#endif
if (IF_REG_ELSE(proc_policy == NULL, f == NULL))
goto exit;
result = true;
read_process_policy(IF_REG_ELSE(proc_policy, f), NULL,
dr_root_dir, dr_mode, debug, dr_options);
exit:
#ifdef PARAMS_IN_REGISTRY
if (policy != NULL)
free_config_group(policy);
#else
if (f != NULL)
fclose(f);
#endif
return result;
}
struct _dr_client_iterator_t {
opt_info_t opt_info;
uint cur;
bool valid;
};
dr_client_iterator_t *
dr_client_iterator_start(const char *process_name,
process_id_t pid,
bool global,
dr_platform_t dr_platform)
{
#ifdef PARAMS_IN_REGISTRY
ConfigGroup *policy = get_policy(dr_platform);
ConfigGroup *proc_policy = get_proc_policy(policy, process_name);
#else
FILE *f = open_config_file(process_name, pid, global, dr_platform,
true/*read*/, false/*!write*/, false/*!overwrite*/);
#endif
dr_client_iterator_t *iter = (dr_client_iterator_t *)
malloc(sizeof(dr_client_iterator_t));
iter->valid = false;
iter->cur = 0;
if (IF_REG_ELSE(proc_policy == NULL, f == NULL))
return iter;
if (read_options(&iter->opt_info, IF_REG_ELSE(proc_policy, f)) != DR_SUCCESS)
return iter;
iter->valid = true;
return iter;
}
bool
dr_client_iterator_hasnext(dr_client_iterator_t *iter)
{
return iter->valid && iter->cur < iter->opt_info.num_clients;
}
void
dr_client_iterator_next(dr_client_iterator_t *iter,
client_id_t *client_id, /* OUT */
size_t *client_pri, /* OUT */
char *client_path, /* OUT */
char *client_options /* OUT */)
{
client_opt_t *client_opt = iter->opt_info.client_opts[iter->cur];
if (client_pri != NULL)
*client_pri = iter->cur;
if (client_path != NULL) {
size_t len = MIN(MAXIMUM_PATH-1, _tcslen(client_opt->path));
_snprintf(client_path, len, TSTR_FMT, client_opt->path);
client_path[len] = '\0';
}
if (client_id != NULL)
*client_id = client_opt->id;
if (client_options != NULL) {
size_t len = MIN(DR_MAX_OPTIONS_LENGTH-1, _tcslen(client_opt->opts));
_snprintf(client_options, len, TSTR_FMT, client_opt->opts);
client_options[len] = '\0';
}
iter->cur++;
}
void
dr_client_iterator_stop(dr_client_iterator_t *iter)
{
if (iter->valid)
free_opt_info(&iter->opt_info);
free(iter);
}
size_t
dr_num_registered_clients(const char *process_name,
process_id_t pid,
bool global,
dr_platform_t dr_platform)
{
opt_info_t opt_info;
size_t num = 0;
#ifdef PARAMS_IN_REGISTRY
ConfigGroup *policy = get_policy(dr_platform);
ConfigGroup *proc_policy = get_proc_policy(policy, process_name);
#else
FILE *f = open_config_file(process_name, pid, global, dr_platform,
true/*read*/, false/*!write*/, false/*!overwrite*/);
#endif
if (IF_REG_ELSE(proc_policy == NULL, f == NULL))
goto exit;
if (read_options(&opt_info, IF_REG_ELSE(proc_policy, f)) != DR_SUCCESS)
goto exit;
num = opt_info.num_clients;
free_opt_info(&opt_info);
exit:
#ifdef PARAMS_IN_REGISTRY
if (policy != NULL)
free_config_group(policy);
#else
fclose(f);
#endif
return num;
}
dr_config_status_t
dr_get_client_info(const char *process_name,
process_id_t pid,
bool global,
dr_platform_t dr_platform,
client_id_t client_id,
size_t *client_pri,
char *client_path,
char *client_options)
{
opt_info_t opt_info;
dr_config_status_t status;
size_t i;
#ifdef PARAMS_IN_REGISTRY
ConfigGroup *policy = get_policy(dr_platform);
ConfigGroup *proc_policy = get_proc_policy(policy, process_name);
#else
FILE *f = open_config_file(process_name, pid, global, dr_platform,
true/*read*/, false/*!write*/, false/*!overwrite*/);
#endif
if (IF_REG_ELSE(proc_policy == NULL, f == NULL)) {
status = DR_PROC_REG_INVALID;
goto exit;
}
status = read_options(&opt_info, IF_REG_ELSE(proc_policy, f));
if (status != DR_SUCCESS)
goto exit;
for (i=0; i<opt_info.num_clients; i++) {
if (opt_info.client_opts[i]->id == client_id) {
client_opt_t *client_opt = opt_info.client_opts[i];
if (client_pri != NULL) {
*client_pri = i;
}
if (client_path != NULL) {
size_t len = MIN(MAXIMUM_PATH-1, _tcslen(client_opt->path));
_snprintf(client_path, len, TSTR_FMT, client_opt->path);
client_path[len] = '\0';
}
if (client_options != NULL) {
size_t len = MIN(DR_MAX_OPTIONS_LENGTH-1, _tcslen(client_opt->opts));
_snprintf(client_options, len, TSTR_FMT, client_opt->opts);
client_options[len] = '\0';
}
status = DR_SUCCESS;
goto exit;
}
}
status = DR_ID_INVALID;
exit:
#ifdef PARAMS_IN_REGISTRY
if (policy != NULL)
free_config_group(policy);
#else
fclose(f);
#endif
return status;
}
dr_config_status_t
dr_register_client(const char *process_name,
process_id_t pid,
bool global,
dr_platform_t dr_platform,
client_id_t client_id,
size_t client_pri,
const char *client_path,
const char *client_options)
{
TCHAR new_opts[DR_MAX_OPTIONS_LENGTH];
TCHAR wpath[MAXIMUM_PATH], woptions[DR_MAX_OPTIONS_LENGTH];
#ifdef PARAMS_IN_REGISTRY
ConfigGroup *policy = get_policy(dr_platform);
ConfigGroup *proc_policy = get_proc_policy(policy, process_name);
#else
FILE *f = open_config_file(process_name, pid, global, dr_platform,
true/*read*/, true/*write*/, false/*!overwrite*/);
#endif
dr_config_status_t status;
opt_info_t opt_info;
bool opt_info_alloc = false;
size_t i;
if (IF_REG_ELSE(proc_policy == NULL, f == NULL)) {
status = DR_PROC_REG_INVALID;
goto exit;
}
status = read_options(&opt_info, IF_REG_ELSE(proc_policy, f));
if (status != DR_SUCCESS) {
goto exit;
}
opt_info_alloc = true;
for (i=0; i<opt_info.num_clients; i++) {
if (opt_info.client_opts[i]->id == client_id) {
status = DR_ID_CONFLICTING;
goto exit;
}
}
if (client_pri > opt_info.num_clients) {
status = DR_PRIORITY_INVALID;
goto exit;
}
convert_to_tchar(wpath, client_path, MAXIMUM_PATH);
NULL_TERMINATE_BUFFER(wpath);
convert_to_tchar(woptions, client_options, DR_MAX_OPTIONS_LENGTH);
NULL_TERMINATE_BUFFER(woptions);
status = add_client_lib(&opt_info, client_id, client_pri, wpath, woptions);
if (status != DR_SUCCESS) {
goto exit;
}
/* write the registry */
status = write_options(&opt_info, new_opts);
if (status != DR_SUCCESS)
goto exit;
#ifndef PARAMS_IN_REGISTRY
/* shift rest of file up, overwriting old value, so we can append new value */
read_config_ex(f, DYNAMORIO_VAR_OPTIONS, NULL, 0, true);
#endif
status = write_config_param(IF_REG_ELSE(proc_policy, f),
PARAM_STR(DYNAMORIO_VAR_OPTIONS), new_opts);
if (status != DR_SUCCESS)
goto exit;
#ifdef PARAMS_IN_REGISTRY
if (write_config_group(policy) != ERROR_SUCCESS) {
status = DR_FAILURE;
goto exit;
}
#endif
status = DR_SUCCESS;
exit:
#ifdef PARAMS_IN_REGISTRY
if (policy != NULL)
free_config_group(policy);
#else
if (f != NULL)
fclose(f);
#endif
if (opt_info_alloc)
free_opt_info(&opt_info);
return status;
}
dr_config_status_t
dr_unregister_client(const char *process_name,
process_id_t pid,
bool global,
dr_platform_t dr_platform,
client_id_t client_id)
{
TCHAR new_opts[DR_MAX_OPTIONS_LENGTH];
#ifdef PARAMS_IN_REGISTRY
ConfigGroup *policy = get_policy(dr_platform);
ConfigGroup *proc_policy = get_proc_policy(policy, process_name);
#else
FILE *f = open_config_file(process_name, pid, global, dr_platform,
true/*read*/, true/*write*/, false/*!overwrite*/);
#endif
dr_config_status_t status;
opt_info_t opt_info;
bool opt_info_alloc = false;
if (IF_REG_ELSE(proc_policy == NULL, f == NULL)) {
status = DR_PROC_REG_INVALID;
goto exit;
}
status = read_options(&opt_info, IF_REG_ELSE(proc_policy, f));
if (status != DR_SUCCESS) {
goto exit;
}
opt_info_alloc = true;
status = remove_client_lib(&opt_info, client_id);
if (status != DR_SUCCESS) {
goto exit;
}
/* write the registry */
write_options(&opt_info, new_opts);
#ifndef PARAMS_IN_REGISTRY
/* shift rest of file up, overwriting old value, so we can append new value */
read_config_ex(f, DYNAMORIO_VAR_OPTIONS, NULL, 0, true);
#endif
write_config_param(IF_REG_ELSE(proc_policy, f), PARAM_STR(DYNAMORIO_VAR_OPTIONS),
new_opts);
#ifdef PARAMS_IN_REGISTRY
if (write_config_group(policy) != ERROR_SUCCESS) {
status = DR_FAILURE;
goto exit;
}
#endif
status = DR_SUCCESS;
exit:
#ifdef PARAMS_IN_REGISTRY
if (policy != NULL)
free_config_group(policy);
#else
if (f != NULL)
fclose(f);
#endif
if (opt_info_alloc)
free_opt_info(&opt_info);
return status;
}
#ifdef WINDOWS
typedef struct {
const char *process_name; /* if non-null nudges processes with matching name */
bool all; /* if set attempts to nudge all processes */
client_id_t client_id;
uint64 argument;
int count; /* number of nudges successfully delivered */
DWORD res; /* last failing error code */
DWORD timeout; /* amount of time to wait for nudge to finish */
} pw_nudge_callback_data_t;
static
BOOL pw_nudge_callback(process_info_t *pi, void **param)
{
char buf[MAXIMUM_PATH];
pw_nudge_callback_data_t *data = (pw_nudge_callback_data_t *)param;
if (pi->ProcessID == 0)
return true; /* skip system process */
buf[0] = '\0';
if (pi->ProcessName != NULL)
_snprintf(buf, MAXIMUM_PATH, "%S", pi->ProcessName);
NULL_TERMINATE_BUFFER(buf);
if (data->all || (data->process_name != NULL &&
strnicmp(data->process_name, buf, MAXIMUM_PATH) == 0)) {
DWORD res = generic_nudge(pi->ProcessID, true, NUDGE_GENERIC(client),
data->client_id, data->argument, data->timeout);
if (res == ERROR_SUCCESS || res == ERROR_TIMEOUT) {
data->count++;
if (res == ERROR_TIMEOUT && data->timeout != 0)
data->res = ERROR_TIMEOUT;
}
else if (res != ERROR_MOD_NOT_FOUND /* so failed for a good reason */)
data->res = res;
}
return true;
}
/* TODO: must be careful in invoking the correct VIPA's nudge handler,
* particularly a problem with multiple agents, but can be a problem even in a
* single agent if some other dll exports dr_nudge_handler() (remote
* contingency).
*/
dr_config_status_t
dr_nudge_process(const char *process_name, client_id_t client_id, uint64 arg,
uint timeout_ms, int *nudge_count /*OUT */)
{
pw_nudge_callback_data_t data = {0};
data.process_name = process_name;
data.client_id = client_id;
data.argument = arg;
data.timeout = timeout_ms;
data.res = ERROR_SUCCESS;
process_walk(&pw_nudge_callback, (void **)&data);
if (nudge_count != NULL)
*nudge_count = data.count;
if (data.res == ERROR_SUCCESS)
return DR_SUCCESS;
else if (data.res == ERROR_TIMEOUT)
return DR_NUDGE_TIMEOUT;
else
return DR_FAILURE;
}
dr_config_status_t
dr_nudge_pid(process_id_t process_id, client_id_t client_id, uint64 arg, uint timeout_ms)
{
DWORD res = generic_nudge(process_id, true, NUDGE_GENERIC(client),
client_id, arg, timeout_ms);
if (res == ERROR_SUCCESS)
return DR_SUCCESS;
if (res == ERROR_MOD_NOT_FOUND)
return DR_NUDGE_PID_NOT_INJECTED;
if (res == ERROR_TIMEOUT && timeout_ms != 0)
return DR_NUDGE_TIMEOUT;
return DR_FAILURE;
}
dr_config_status_t
dr_nudge_all(client_id_t client_id, uint64 arg, uint timeout_ms, int *nudge_count /*OUT*/)
{
pw_nudge_callback_data_t data = {0};
data.all = true;
data.client_id = client_id;
data.argument = arg;
data.timeout = timeout_ms;
data.res = ERROR_SUCCESS;
process_walk(&pw_nudge_callback, (void **)&data);
if (nudge_count != NULL)
*nudge_count = data.count;
if (data.res == ERROR_SUCCESS)
return DR_SUCCESS;
if (data.res == ERROR_TIMEOUT)
return DR_NUDGE_TIMEOUT;
return DR_FAILURE;
}
#elif defined LINUX
dr_config_status_t
dr_nudge_pid(process_id_t process_id, client_id_t client_id, uint64 arg, uint timeout_ms)
{
siginfo_t info;
int res;
/* construct the payload */
if (!create_nudge_signal_payload(&info, NUDGE_GENERIC(client), client_id, arg))
return DR_FAILURE;
/* send the nudge */
res = syscall(SYS_rt_sigqueueinfo, process_id, NUDGESIG_SIGNUM, &info);
if (res < 0)
return DR_FAILURE;
return DR_SUCCESS;
}
#endif /* WINDOWS */
/* XXX: perhaps we should take in a config dir as a parameter to all
* of the registration routines in the drconfiglib API rather than or
* in addition to having this env var DYNAMORIO_CONFIGLIB.
* Xref i#939.
*/
dr_config_status_t
dr_get_config_dir(bool global,
bool alternative_local,
char *config_dir /* OUT */,
size_t config_dir_sz)
{
if (get_config_dir(global, config_dir, config_dir_sz, alternative_local)) {
/* XXX: it would be nice to return DR_CONFIG_STRING_TOO_LONG if the
* buffer is too small, rather than just truncating it
*/
return DR_SUCCESS;
} else
return DR_CONFIG_DIR_NOT_FOUND;
}