mirror of
https://github.com/DynamoRIO/dynamorio.git
synced 2026-10-12 02:02:04 +00:00
In preparation of moving the TEST* defines into dr_project_wide_headers.h, we replace all uses of TEST with the equivalent TESTANY. Although we avoid exposing dr_project_wide_headers.h in DR_API headers, it is exposed in drmemtrace's headers where TEST causes collisions with third-party code such as googletest. Issue: #8001
2181 lines
70 KiB
C
2181 lines
70 KiB
C
/* **********************************************************
|
|
* Copyright (c) 2011-2026 Google, Inc. All rights reserved.
|
|
* Copyright (c) 2005-2010 VMware, Inc. All rights reserved.
|
|
* **********************************************************/
|
|
|
|
/*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
*
|
|
* * Neither the name of VMware, Inc. nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
|
* DAMAGE.
|
|
*/
|
|
|
|
#include "share.h"
|
|
#include "drlibc.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
#ifdef WINDOWS
|
|
# include "config.h"
|
|
# include "elm.h"
|
|
# include "events.h" /* for canary */
|
|
# include "processes.h" /* for canary */
|
|
# include "../options.h" /* for option checking */
|
|
# include "ntdll_types.h" /* for NT_SUCCESS */
|
|
|
|
# include <io.h> /* for canary */
|
|
# include <Fcntl.h> /* for canary */
|
|
# include <aclapi.h>
|
|
#else
|
|
# include <sys/stat.h>
|
|
#endif
|
|
|
|
#ifndef UNIT_TEST
|
|
|
|
# ifdef WINDOWS
|
|
|
|
# ifdef DEBUG
|
|
|
|
int debuglevel = DL_FATAL;
|
|
int abortlevel = DL_FATAL;
|
|
|
|
void
|
|
set_debuglevel(int level)
|
|
{
|
|
debuglevel = level;
|
|
}
|
|
|
|
void
|
|
set_abortlevel(int level)
|
|
{
|
|
abortlevel = level;
|
|
}
|
|
|
|
# define CONFIG_MAX 8192
|
|
|
|
# define HEADER_SNIPPET(defsfile) \
|
|
"POLICY_VERSION=30000\n" \
|
|
"BEGIN_BLOCK\n" \
|
|
"GLOBAL\n" \
|
|
"DYNAMORIO_OPTIONS=\n" \
|
|
"DYNAMORIO_RUNUNDER=1\n" \
|
|
"DYNAMORIO_AUTOINJECT=\\lib\\dynamorio.dll\n" \
|
|
"DYNAMORIO_HOT_PATCH_POLICIES=" defsfile "\n" \
|
|
"DYNAMORIO_UNSUPPORTED=\n" \
|
|
"END_BLOCK\n"
|
|
|
|
DWORD
|
|
load_test_config(const char *snippet, BOOL use_hotpatch_defs)
|
|
{
|
|
char buf[CONFIG_MAX];
|
|
_snprintf(buf, CONFIG_MAX, "%s%s",
|
|
use_hotpatch_defs ? HEADER_SNIPPET("\\conf") : HEADER_SNIPPET(""), snippet);
|
|
NULL_TERMINATE_BUFFER(buf);
|
|
DO_ASSERT(strlen(buf) < CONFIG_MAX - 2);
|
|
DO_DEBUG(DL_VERB, printf("importing %s\n", buf););
|
|
CHECKED_OPERATION(policy_import(buf, FALSE, NULL, NULL));
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
void
|
|
get_testdir(WCHAR *buf, UINT maxchars)
|
|
{
|
|
WCHAR *filePart;
|
|
WCHAR tmp[MAX_PATH];
|
|
DWORD len;
|
|
|
|
len = GetEnvironmentVariable(L"DYNAMORIO_WINDIR", tmp, maxchars);
|
|
DO_ASSERT(len < maxchars);
|
|
if (len == 0) {
|
|
len = GetEnvironmentVariable(L_DYNAMORIO_VAR_HOME, tmp, maxchars);
|
|
DO_ASSERT(len < maxchars);
|
|
/* check for cygwin paths on windows */
|
|
if (!file_exists(buf))
|
|
len = 0;
|
|
|
|
DO_DEBUG(DL_INFO, printf("ignoring invalid-looking DYNAMORIO_HOME=%S\n", tmp););
|
|
}
|
|
|
|
if (len == 0) {
|
|
wcsncpy(tmp, L"..", MAX_PATH);
|
|
}
|
|
|
|
len = GetFullPathName(tmp, maxchars, buf, &filePart);
|
|
|
|
DO_DEBUG(DL_INFO, printf("using drhome: %S\n", buf););
|
|
|
|
DO_ASSERT(len != 0);
|
|
|
|
return;
|
|
}
|
|
|
|
void
|
|
error_cb(unsigned int errcode, WCHAR *message)
|
|
{
|
|
if (errcode || !errcode || message) {
|
|
DO_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
extern BOOL do_once;
|
|
|
|
typedef struct evthelp__ {
|
|
DWORD type;
|
|
WCHAR *exename;
|
|
ULONG pid;
|
|
WCHAR *s3;
|
|
WCHAR *s4;
|
|
UINT maxchars;
|
|
BOOL found;
|
|
} evthelp;
|
|
|
|
evthelp *cb_eh = NULL;
|
|
int last_record = -1;
|
|
|
|
void
|
|
check_event_cb(EVENTLOGRECORD *record)
|
|
{
|
|
const WCHAR *strings;
|
|
|
|
if (cb_eh->found)
|
|
return;
|
|
|
|
last_record = record->RecordNumber;
|
|
|
|
if (record->EventID == cb_eh->type) {
|
|
|
|
if (cb_eh->exename != NULL &&
|
|
0 != wcscmp(get_event_exename(record), cb_eh->exename))
|
|
return;
|
|
|
|
if (cb_eh->pid != 0 && cb_eh->pid != get_event_pid(record))
|
|
return;
|
|
|
|
strings = get_message_strings(record);
|
|
strings = next_message_string(strings);
|
|
strings = next_message_string(strings);
|
|
if (cb_eh->s3 != NULL) {
|
|
cb_eh->s3[0] = L'\0';
|
|
if (strings != NULL) {
|
|
wcsncpy(cb_eh->s3, strings, cb_eh->maxchars);
|
|
cb_eh->s3[cb_eh->maxchars - 1] = L'\0';
|
|
}
|
|
}
|
|
if (cb_eh->s4 != NULL) {
|
|
cb_eh->s4[0] = L'\0';
|
|
strings = next_message_string(strings);
|
|
if (strings != NULL) {
|
|
wcsncpy(cb_eh->s4, strings, cb_eh->maxchars);
|
|
cb_eh->s4[cb_eh->maxchars - 1] = L'\0';
|
|
}
|
|
}
|
|
cb_eh->found = TRUE;
|
|
}
|
|
}
|
|
|
|
void
|
|
reset_last_event()
|
|
{
|
|
last_record = -1;
|
|
}
|
|
|
|
/* checks for events matching type, exename (if not null), and pid (if
|
|
* not 0). fills in s3 and s4 with 3rd and 4th message strings of
|
|
* match, if not null.
|
|
* next search will start with event after matched event. */
|
|
BOOL
|
|
check_for_event(DWORD type, WCHAR *exename, ULONG pid, WCHAR *s3, WCHAR *s4,
|
|
UINT maxchars)
|
|
{
|
|
evthelp eh;
|
|
|
|
eh.type = type;
|
|
eh.exename = exename;
|
|
eh.pid = pid;
|
|
eh.s3 = s3;
|
|
eh.s4 = s4;
|
|
eh.maxchars = maxchars;
|
|
eh.found = FALSE;
|
|
|
|
cb_eh = &eh;
|
|
|
|
/* backdoor */
|
|
do_once = TRUE;
|
|
|
|
CHECKED_OPERATION(
|
|
start_eventlog_monitor(FALSE, NULL, check_event_cb, error_cb, last_record));
|
|
DO_ASSERT(WAIT_OBJECT_0 ==
|
|
WaitForSingleObject(get_eventlog_monitor_thread_handle(), 10000));
|
|
stop_eventlog_monitor();
|
|
|
|
return eh.found;
|
|
}
|
|
|
|
FILE *event_list_fp;
|
|
|
|
void
|
|
show_event_cb(unsigned int mID, unsigned int type, WCHAR *message, DWORD timestamp)
|
|
{
|
|
/* fool the compiler */
|
|
DO_ASSERT(type == 0 || type != 0 || timestamp == 0);
|
|
fprintf(event_list_fp, " Event %d: %S\n", mID, message);
|
|
}
|
|
|
|
void
|
|
show_all_events(FILE *fp)
|
|
{
|
|
DO_ASSERT(fp != NULL);
|
|
|
|
event_list_fp = fp;
|
|
/* backdoor */
|
|
do_once = TRUE;
|
|
CHECKED_OPERATION(
|
|
start_eventlog_monitor(TRUE, show_event_cb, NULL, error_cb, (DWORD)-1));
|
|
DO_ASSERT(WAIT_OBJECT_0 ==
|
|
WaitForSingleObject(get_eventlog_monitor_thread_handle(), 10000));
|
|
stop_eventlog_monitor();
|
|
|
|
return;
|
|
}
|
|
|
|
# endif /* _DEBUG */
|
|
|
|
void
|
|
wcstolower(WCHAR *str)
|
|
{
|
|
UINT i;
|
|
for (i = 0; i < wcslen(str); i++)
|
|
str[i] = towlower(str[i]);
|
|
}
|
|
|
|
WCHAR *
|
|
get_exename_from_path(const WCHAR *path)
|
|
{
|
|
WCHAR *name = wcsrchr(path, L'\\');
|
|
if (name == NULL)
|
|
name = (WCHAR *)path;
|
|
else
|
|
name += 1;
|
|
return name;
|
|
}
|
|
|
|
DWORD
|
|
acquire_shutdown_privilege()
|
|
{
|
|
HANDLE hToken = NULL;
|
|
TOKEN_PRIVILEGES Priv;
|
|
|
|
// get current thread token
|
|
if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, FALSE,
|
|
&hToken)) {
|
|
// can't get thread token, try process token instead
|
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES,
|
|
&hToken)) {
|
|
return GetLastError();
|
|
}
|
|
}
|
|
|
|
Priv.PrivilegeCount = 1;
|
|
Priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
|
LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &Priv.Privileges[0].Luid);
|
|
// try to enable the privilege
|
|
if (!AdjustTokenPrivileges(hToken, FALSE, &Priv, sizeof(Priv), NULL, 0))
|
|
return GetLastError();
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* XXX: shutdown reason. we should probably use this, BUT
|
|
* InitiateSystemShutdownEx is not included in VS6.0, so we'll have
|
|
* to dynamically link it in.
|
|
*
|
|
* from msdn:
|
|
* SHTDN_REASON_FLAG_PLANNED: The shutdown was planned. On Windows .NET
|
|
* Server, the system generates a state snapshot. For more information,
|
|
* see the help for Shutdown Event Tracker.
|
|
*
|
|
* various combinations of major/minor flags are "recognized by the
|
|
* system"; the APPLICATION|RECONFIG is NOT one of these. However,
|
|
* "You can also define your own shutdown reasons and add them to the
|
|
* the registry." we should probably do this at installation, once
|
|
* we're happy we've got a good reason code.
|
|
*/
|
|
DWORD
|
|
reboot_system()
|
|
{
|
|
DWORD res;
|
|
|
|
res = acquire_shutdown_privilege();
|
|
if (res != ERROR_SUCCESS)
|
|
return res;
|
|
|
|
/* do we need to harden this at all?
|
|
* "If the the system is not ready to handle the request, the last
|
|
* error code is ERROR_NOT_READY. The application should wait a
|
|
* short while and retry the call."
|
|
* also ERROR_MACHINE_LOCKED, ERROR_SHUTDOWN_IN_PROGRESS, etc. */
|
|
|
|
res =
|
|
InitiateSystemShutdown(NULL, L"A System Restart was requested.", 30, TRUE, TRUE);
|
|
// SHTDN_REASON_MAJOR_APPLICATION |
|
|
// SHTDN_REASON_MINOR_RECONFIG);
|
|
|
|
return res;
|
|
}
|
|
|
|
# define LAST_WCHAR(wstr) wstr[wcslen(wstr) - 1]
|
|
|
|
# endif /* WINDOWS */
|
|
|
|
/* this sucks.
|
|
* i can't believe this is best way to implement this in Win32...
|
|
* but i can't seem to find a better way.
|
|
* msdn suggests using CreateFile() with CREATE_NEW or OPEN_EXISTING,
|
|
* and then checking error codes; but the problem there is that C:\\
|
|
* returns PATH_NOT_FOUND regardless. */
|
|
bool
|
|
file_exists(const TCHAR *fn)
|
|
{
|
|
# ifdef WINDOWS
|
|
WIN32_FIND_DATA fd;
|
|
HANDLE search;
|
|
|
|
DO_ASSERT(fn != NULL);
|
|
|
|
search = FindFirstFile(fn, &fd);
|
|
|
|
if (search == INVALID_HANDLE_VALUE) {
|
|
|
|
/* special handling for e.g. C:\\ */
|
|
if (LAST_WCHAR(fn) == L'\\' || LAST_WCHAR(fn) == L':') {
|
|
WCHAR buf[MAX_PATH];
|
|
_snwprintf(buf, MAX_PATH, L"%s%s*", fn,
|
|
LAST_WCHAR(fn) == L'\\' ? L"" : L"\\");
|
|
NULL_TERMINATE_BUFFER(buf);
|
|
search = FindFirstFile(buf, &fd);
|
|
if (search != INVALID_HANDLE_VALUE) {
|
|
FindClose(search);
|
|
return TRUE;
|
|
} else {
|
|
DO_DEBUG(
|
|
DL_VERB,
|
|
printf("%S: even though we tried hard, %d\n", buf, GetLastError()););
|
|
}
|
|
}
|
|
|
|
DO_DEBUG(DL_VERB,
|
|
printf("%S doesn't exist because of: %d\n", fn, GetLastError()););
|
|
return FALSE;
|
|
} else {
|
|
FindClose(search);
|
|
return TRUE;
|
|
}
|
|
# else
|
|
struct stat64 st;
|
|
/* Use the raw syscall to avoid glibc 2.33 deps (i#5474). */
|
|
return dr_stat_syscall(fn, &st) == 0;
|
|
# endif
|
|
}
|
|
|
|
# ifdef WINDOWS
|
|
# define MAX_COUNTER 999999
|
|
|
|
/* grokked from the core.
|
|
* XXX: shareme!
|
|
* if NULL is passed for directory, then it is ignored and no directory
|
|
* check is done, and filename_base is assumed to be absolute.
|
|
* TODO: make this a proactive check: make sure the file can be
|
|
* opened, eg, do a create/delete on the filename to be returned.
|
|
*/
|
|
BOOL
|
|
get_unique_filename(const WCHAR *directory, const WCHAR *filename_base,
|
|
const WCHAR *file_type, WCHAR *filename_buffer, UINT maxlen)
|
|
{
|
|
UINT counter = 0;
|
|
|
|
if (directory != NULL && !file_exists(directory))
|
|
return FALSE;
|
|
|
|
do {
|
|
if (directory == NULL)
|
|
_snwprintf(filename_buffer, maxlen, L"%s.%.8d%s", filename_base, counter,
|
|
file_type);
|
|
else
|
|
_snwprintf(filename_buffer, maxlen, L"%s\\%s.%.8d%s", directory,
|
|
filename_base, counter, file_type);
|
|
filename_buffer[maxlen - 1] = L'\0';
|
|
} while (file_exists(filename_buffer) && (++counter < MAX_COUNTER));
|
|
|
|
return (counter < MAX_COUNTER);
|
|
}
|
|
|
|
DWORD
|
|
delete_file_on_boot(WCHAR *filename)
|
|
{
|
|
DWORD res;
|
|
BOOL success = MoveFileEx(filename, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
|
|
|
|
/* reboot removal adds an entry to
|
|
* HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session
|
|
* Manager\PendingFileRenameOperations and smss.exe will delete the file on next boot
|
|
*/
|
|
if (success)
|
|
res = ERROR_SUCCESS;
|
|
else
|
|
res = GetLastError();
|
|
return res;
|
|
}
|
|
|
|
DWORD
|
|
delete_file_rename_in_use(WCHAR *filename)
|
|
{
|
|
DWORD res;
|
|
BOOL success = DeleteFile(filename);
|
|
|
|
if (success)
|
|
return ERROR_SUCCESS;
|
|
|
|
/* xref case 4512: if we leave a dll in a process after we're done
|
|
* using it, we won't be able to delete it; however, hopefully
|
|
* we can rename it so there won't be issues replacing it later. */
|
|
res = GetLastError();
|
|
if (res != ERROR_SUCCESS) {
|
|
WCHAR tempname[MAX_PATH];
|
|
if (get_unique_filename(NULL, filename, L".tmp", tempname, MAX_PATH)) {
|
|
success = MoveFile(filename, tempname);
|
|
if (success) {
|
|
res = ERROR_SUCCESS;
|
|
|
|
/* as best effort, we also schedule cleanup of the
|
|
* temporary file on next boot */
|
|
delete_file_on_boot(tempname);
|
|
} else
|
|
res = GetLastError();
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
# ifndef PROTECTED_DACL_SECURITY_INFORMATION
|
|
# define PROTECTED_DACL_SECURITY_INFORMATION (0x80000000L)
|
|
# endif
|
|
|
|
/*
|
|
* quick permissions xfer workaround for updating permissions
|
|
* on upgrade.
|
|
*/
|
|
DWORD
|
|
copy_file_permissions(WCHAR *filedst, WCHAR *filesrc)
|
|
{
|
|
DWORD res = ERROR_SUCCESS;
|
|
SECURITY_DESCRIPTOR *sd = NULL;
|
|
ACL *dacl = NULL;
|
|
res = GetNamedSecurityInfo(filesrc, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL,
|
|
NULL, &dacl, NULL, &sd);
|
|
|
|
if (res != ERROR_SUCCESS)
|
|
return res;
|
|
|
|
res = SetNamedSecurityInfo(filedst, SE_FILE_OBJECT,
|
|
DACL_SECURITY_INFORMATION |
|
|
PROTECTED_DACL_SECURITY_INFORMATION,
|
|
NULL, NULL, dacl, NULL);
|
|
|
|
LocalFree(sd);
|
|
|
|
return res;
|
|
}
|
|
|
|
/* NOTE: for now we only consider the major/minor versions and
|
|
* platform id.
|
|
*
|
|
* the osinfo.szCSDVersion string contains service pack information,
|
|
* which could be used to distinguish e.g. XPSP2, 2K3SP1, if
|
|
* necessary.
|
|
*/
|
|
|
|
DWORD
|
|
get_platform(DWORD *platform)
|
|
{
|
|
/* determine the OS version information */
|
|
OSVERSIONINFOW osinfo;
|
|
/* i#1418: GetVersionEx is just plain broken on win8.1+ so we use the Rtl version */
|
|
typedef NTSTATUS(NTAPI * RtlGetVersion_t)(OSVERSIONINFOW * info);
|
|
RtlGetVersion_t RtlGetVersion;
|
|
NTSTATUS res = -1;
|
|
HANDLE ntdll_handle = GetModuleHandle(_T("ntdll.dll"));
|
|
/* i#1598: on any error or on unknown ver, best to assume it's a new ver
|
|
* and will look most like the most recent known ver. We'll still return error
|
|
* return val below, but many callers don't check that (!).
|
|
*/
|
|
*platform = PLATFORM_WIN_10;
|
|
if (ntdll_handle == NULL)
|
|
return GetLastError();
|
|
RtlGetVersion =
|
|
(RtlGetVersion_t)GetProcAddress((HMODULE)ntdll_handle, "RtlGetVersion");
|
|
if (RtlGetVersion == NULL)
|
|
return GetLastError();
|
|
osinfo.dwOSVersionInfoSize = sizeof(osinfo);
|
|
res = RtlGetVersion(&osinfo);
|
|
if (NT_SUCCESS(res)) {
|
|
|
|
DO_DEBUG(DL_VERB, WCHAR verbuf[MAX_PATH];
|
|
_snwprintf(verbuf, MAX_PATH, L"Major=%d, Minor=%d, Build=%d, SPinfo=%s",
|
|
osinfo.dwMajorVersion, osinfo.dwMinorVersion,
|
|
osinfo.dwBuildNumber, osinfo.szCSDVersion);
|
|
NULL_TERMINATE_BUFFER(verbuf); printf("%S\n", verbuf););
|
|
|
|
if (osinfo.dwPlatformId != VER_PLATFORM_WIN32_NT)
|
|
return ERROR_UNSUPPORTED_OS;
|
|
|
|
if (osinfo.dwMajorVersion == 4) {
|
|
if (osinfo.dwMinorVersion == 0) {
|
|
*platform = PLATFORM_WIN_NT_4;
|
|
return ERROR_SUCCESS;
|
|
}
|
|
} else if (osinfo.dwMajorVersion == 5) {
|
|
if (osinfo.dwMinorVersion == 0) {
|
|
*platform = PLATFORM_WIN_2000;
|
|
return ERROR_SUCCESS;
|
|
} else if (osinfo.dwMinorVersion == 1) {
|
|
*platform = PLATFORM_WIN_XP;
|
|
return ERROR_SUCCESS;
|
|
} else if (osinfo.dwMinorVersion == 2) {
|
|
*platform = PLATFORM_WIN_2003;
|
|
return ERROR_SUCCESS;
|
|
}
|
|
} else if (osinfo.dwMajorVersion == 6) {
|
|
if (osinfo.dwMinorVersion == 0) {
|
|
*platform = PLATFORM_VISTA;
|
|
return ERROR_SUCCESS;
|
|
} else if (osinfo.dwMinorVersion == 1) {
|
|
*platform = PLATFORM_WIN_7;
|
|
return ERROR_SUCCESS;
|
|
} else if (osinfo.dwMinorVersion == 2) {
|
|
*platform = PLATFORM_WIN_8;
|
|
return ERROR_SUCCESS;
|
|
} else if (osinfo.dwMinorVersion == 3) {
|
|
*platform = PLATFORM_WIN_8_1;
|
|
return ERROR_SUCCESS;
|
|
}
|
|
} else if (osinfo.dwMajorVersion == 10) {
|
|
if (osinfo.dwMinorVersion == 0) {
|
|
if (GetProcAddress((HMODULE)ntdll_handle, "NtAllocateVirtualMemoryEx") !=
|
|
NULL)
|
|
*platform = PLATFORM_WIN_10_1803;
|
|
else if (GetProcAddress((HMODULE)ntdll_handle, "NtCallEnclave") != NULL)
|
|
*platform = PLATFORM_WIN_10_1709;
|
|
else if (GetProcAddress((HMODULE)ntdll_handle, "NtLoadHotPatch") != NULL)
|
|
*platform = PLATFORM_WIN_10_1703;
|
|
else if (GetProcAddress((HMODULE)ntdll_handle,
|
|
"NtCreateRegistryTransaction") != NULL)
|
|
*platform = PLATFORM_WIN_10_1607;
|
|
else if (GetProcAddress((HMODULE)ntdll_handle, "NtCreateEnclave") != NULL)
|
|
*platform = PLATFORM_WIN_10_1511;
|
|
else
|
|
*platform = PLATFORM_WIN_10;
|
|
return ERROR_SUCCESS;
|
|
}
|
|
}
|
|
return ERROR_UNSUPPORTED_OS;
|
|
} else {
|
|
return res;
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
is_wow64(HANDLE hProcess)
|
|
{
|
|
/* IsWow64Pocess is only available on XP+ */
|
|
typedef DWORD(WINAPI * IsWow64Process_Type)(HANDLE hProcess, PBOOL isWow64Process);
|
|
static HANDLE kernel32_handle;
|
|
static IsWow64Process_Type IsWow64Process;
|
|
if (kernel32_handle == NULL)
|
|
kernel32_handle = GetModuleHandle(L"kernel32.dll");
|
|
if (IsWow64Process == NULL && kernel32_handle != NULL) {
|
|
IsWow64Process =
|
|
(IsWow64Process_Type)GetProcAddress(kernel32_handle, "IsWow64Process");
|
|
}
|
|
if (IsWow64Process == NULL) {
|
|
/* should be NT or 2K */
|
|
DO_DEBUG(DL_INFO, {
|
|
DWORD platform = 0;
|
|
get_platform(&platform);
|
|
DO_ASSERT(platform == PLATFORM_WIN_NT_4 || platform == PLATFORM_WIN_2000);
|
|
});
|
|
return FALSE;
|
|
} else {
|
|
BOOL res;
|
|
if (!IsWow64Process(hProcess, &res))
|
|
return FALSE;
|
|
return res;
|
|
}
|
|
}
|
|
|
|
static const TCHAR *
|
|
get_dynamorio_home_helper(BOOL reset)
|
|
{
|
|
static TCHAR dynamorio_home[MAXIMUM_PATH] = { 0 };
|
|
int res;
|
|
|
|
if (reset)
|
|
dynamorio_home[0] = L'\0';
|
|
|
|
if (dynamorio_home[0] != L'\0')
|
|
return dynamorio_home;
|
|
|
|
res = get_config_parameter(L_PRODUCT_NAME, FALSE, L_DYNAMORIO_VAR_HOME,
|
|
dynamorio_home, MAXIMUM_PATH);
|
|
|
|
if (res == ERROR_SUCCESS && dynamorio_home[0] != L'\0')
|
|
return dynamorio_home;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
const TCHAR *
|
|
get_dynamorio_home()
|
|
{
|
|
return get_dynamorio_home_helper(FALSE);
|
|
}
|
|
|
|
static const TCHAR *
|
|
get_dynamorio_logdir_helper(BOOL reset)
|
|
{
|
|
static TCHAR dynamorio_logdir[MAXIMUM_PATH] = { 0 };
|
|
DWORD res;
|
|
|
|
if (reset)
|
|
dynamorio_logdir[0] = L'\0';
|
|
|
|
if (dynamorio_logdir[0] != L'\0')
|
|
return dynamorio_logdir;
|
|
|
|
res = get_config_parameter(L_PRODUCT_NAME, FALSE, L_DYNAMORIO_VAR_LOGDIR,
|
|
dynamorio_logdir, MAXIMUM_PATH);
|
|
|
|
if (res == ERROR_SUCCESS && dynamorio_logdir[0] != L'\0')
|
|
return dynamorio_logdir;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
const TCHAR *
|
|
get_dynamorio_logdir()
|
|
{
|
|
return get_dynamorio_logdir_helper(FALSE);
|
|
}
|
|
|
|
/* If a path is passed in, it is checked for 8.3 compatibility; else,
|
|
* the default path is checked. This routine does not check the
|
|
* actual 8.3 reg key.
|
|
*/
|
|
BOOL
|
|
using_system32_for_preinject(const WCHAR *preinject)
|
|
{
|
|
DWORD platform = 0;
|
|
get_platform(&platform);
|
|
if (platform == PLATFORM_WIN_NT_4) {
|
|
return TRUE;
|
|
} else {
|
|
/* case 7586: we need to check if the system has disabled
|
|
* 8.3 names; if so, we need to use the system32 for
|
|
* preinject (since spaces are not allowed in AppInitDLLs)
|
|
*/
|
|
WCHAR short_path[MAX_PATH];
|
|
WCHAR long_path[MAX_PATH];
|
|
if (preinject == NULL) {
|
|
/* note: with force_local_path == TRUE, we don't have
|
|
* to worry about get_preinject_path() calling this
|
|
* method back, and it will always return success.
|
|
*/
|
|
get_preinject_path(short_path, MAX_PATH, TRUE, TRUE);
|
|
wcsncat(short_path, L"\\" L_EXPAND_LEVEL(INJECT_DLL_8_3_NAME),
|
|
MAX_PATH - wcslen(short_path));
|
|
NULL_TERMINATE_BUFFER(short_path);
|
|
get_preinject_path(long_path, MAX_PATH, TRUE, FALSE);
|
|
wcsncat(long_path, L"\\" L_EXPAND_LEVEL(INJECT_DLL_8_3_NAME),
|
|
MAX_PATH - wcslen(long_path));
|
|
NULL_TERMINATE_BUFFER(long_path);
|
|
} else {
|
|
/* Check the passed-in file */
|
|
GetShortPathName(preinject, short_path, BUFFER_SIZE_ELEMENTS(short_path));
|
|
NULL_TERMINATE_BUFFER(short_path);
|
|
wcsncpy(long_path, preinject, BUFFER_SIZE_ELEMENTS(long_path));
|
|
}
|
|
|
|
/* if 8.3 names are disabled, file_exists will return FALSE on
|
|
* the GetShortPathName()'ed path.
|
|
*/
|
|
return (file_exists(long_path) && !file_exists(short_path));
|
|
}
|
|
}
|
|
|
|
/* if force_local_path, then this returns the in-installation
|
|
* path regardless of using_system32_for_preinject().
|
|
* otherwise, this returns the path to the actuall DLL that
|
|
* will be injected, which depends on
|
|
* using_system32_for_preinject()
|
|
* if short_path, calls GetShortPathName() on the path before returning it.
|
|
* for a canonical preinject path, this parameter should be TRUE.
|
|
*/
|
|
DWORD
|
|
get_preinject_path(WCHAR *buf, int nchars, BOOL force_local_path, BOOL short_path)
|
|
{
|
|
if (!force_local_path && using_system32_for_preinject(NULL)) {
|
|
UINT len;
|
|
len = GetSystemDirectory(buf, MAX_PATH);
|
|
|
|
if (len == 0)
|
|
return GetLastError();
|
|
} else {
|
|
const WCHAR *home = get_dynamorio_home();
|
|
/* using_system32_for_preinject() assumes we always succeed */
|
|
_snwprintf(buf, nchars, L"%s\\lib", home == NULL ? L"" : home);
|
|
}
|
|
|
|
buf[nchars - 1] = L'\0';
|
|
if (short_path)
|
|
GetShortPathName(buf, buf, nchars);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
DWORD
|
|
get_preinject_name(WCHAR *buf, int nchars)
|
|
{
|
|
DWORD res;
|
|
|
|
if (using_system32_for_preinject(NULL)) {
|
|
wcsncpy(buf, L_EXPAND_LEVEL(INJECT_DLL_NAME), nchars);
|
|
} else {
|
|
res = get_preinject_path(buf, nchars, FALSE, TRUE);
|
|
if (res != ERROR_SUCCESS)
|
|
return res;
|
|
wcsncat(buf, L"\\" L_EXPAND_LEVEL(INJECT_DLL_8_3_NAME), nchars - wcslen(buf));
|
|
}
|
|
|
|
buf[nchars - 1] = L'\0';
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
# endif /* WINDOWS */
|
|
|
|
static dr_platform_t registry_view = DR_PLATFORM_DEFAULT;
|
|
|
|
void
|
|
set_dr_platform(dr_platform_t platform)
|
|
{
|
|
registry_view = platform;
|
|
}
|
|
|
|
dr_platform_t
|
|
get_dr_platform(void)
|
|
{
|
|
if (registry_view ==
|
|
DR_PLATFORM_64BIT IF_X64(|| registry_view == DR_PLATFORM_DEFAULT))
|
|
return DR_PLATFORM_64BIT;
|
|
return DR_PLATFORM_32BIT;
|
|
}
|
|
|
|
# ifdef WINDOWS
|
|
|
|
DWORD
|
|
platform_key_flags()
|
|
{
|
|
/* PR 244206: have control over whether using WOW64 redirection or
|
|
* raw 64-bit registry view.
|
|
* These flags should be used for all Reg{Create,Open,Delete}KeyEx calls,
|
|
* on XP+ (invalid on earlier platforms) on redirected keys
|
|
* (most of HKLM\Software).
|
|
* The flags don't matter on non-redirected trees like HKLM\System.
|
|
* Since too many functions in libutil/ end up calling something
|
|
* that reads/writes the registry, we don't pass the dr_platform_t
|
|
* around and instead use a global variable.
|
|
*/
|
|
DWORD platform = 0;
|
|
get_platform(&platform);
|
|
if (platform == PLATFORM_WIN_NT_4 || platform == PLATFORM_WIN_2000)
|
|
return 0;
|
|
else {
|
|
switch (registry_view) {
|
|
case DR_PLATFORM_DEFAULT: return 0;
|
|
case DR_PLATFORM_32BIT: return KEY_WOW64_32KEY;
|
|
case DR_PLATFORM_64BIT: return KEY_WOW64_64KEY;
|
|
default: DO_ASSERT(false); return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* PR 244206: use this instead of RegDeleteKey for deleting redirected keys
|
|
* (most of HKLM\Software)
|
|
*/
|
|
DWORD
|
|
delete_product_key(HKEY hkey, LPCWSTR subkey)
|
|
{
|
|
/* RegDeleteKeyEx is only available on XP+. We cannot delete
|
|
* from 64-bit registry if we're WOW64 using RegDeleteKey, so we
|
|
* dynamically look up RegDeleteKeyEx.
|
|
* We could instead use NtDeleteKey and first open the subkey HKEY:
|
|
* we could link with the core's ntdll.c, and also use is_wow64_process().
|
|
*/
|
|
typedef DWORD(WINAPI * RegDeleteKeyExW_Type)(HKEY hKey, LPCWSTR lpSubKey,
|
|
REGSAM samDesired, DWORD Reserved);
|
|
static HANDLE advapi32_handle;
|
|
static RegDeleteKeyExW_Type RegDeleteKeyExW;
|
|
if (advapi32_handle == NULL)
|
|
advapi32_handle = GetModuleHandle(L"advapi32.dll");
|
|
if (RegDeleteKeyExW == NULL && advapi32_handle != NULL) {
|
|
RegDeleteKeyExW =
|
|
(RegDeleteKeyExW_Type)GetProcAddress(advapi32_handle, "RegDeleteKeyExW");
|
|
}
|
|
if (RegDeleteKeyExW == NULL) {
|
|
/* should be NT or 2K */
|
|
DO_DEBUG(DL_INFO, {
|
|
DWORD platform = 0;
|
|
get_platform(&platform);
|
|
DO_ASSERT(platform == PLATFORM_WIN_NT_4 || platform == PLATFORM_WIN_2000);
|
|
});
|
|
return RegDeleteKey(hkey, subkey);
|
|
} else
|
|
return RegDeleteKeyExW(hkey, subkey, platform_key_flags(), 0);
|
|
}
|
|
|
|
DWORD
|
|
create_root_key()
|
|
{
|
|
int res;
|
|
HKEY hkroot;
|
|
|
|
res = RegCreateKeyEx(DYNAMORIO_REGISTRY_HIVE, L_DYNAMORIO_REGISTRY_KEY, 0, NULL,
|
|
REG_OPTION_NON_VOLATILE,
|
|
platform_key_flags() | KEY_WRITE | KEY_ENUMERATE_SUB_KEYS, NULL,
|
|
&hkroot, NULL);
|
|
RegCloseKey(hkroot);
|
|
|
|
return res;
|
|
}
|
|
|
|
/* Deletes the reg key created by create_root_key/setup_installation and the parent
|
|
* company key if it's empty afterwards (might not be if PE or nodemgr has config subkeys
|
|
* there. */
|
|
DWORD
|
|
destroy_root_key()
|
|
{
|
|
DWORD res;
|
|
/* This deletes just the product key. */
|
|
res = recursive_delete_key(DYNAMORIO_REGISTRY_HIVE, L_DYNAMORIO_REGISTRY_KEY, NULL);
|
|
/* Delete the company key (this will only work if it is empty, so no need to worry
|
|
* about clobbering any config settings or doing too much damage if we screw up. */
|
|
if (res == ERROR_SUCCESS) {
|
|
WCHAR company_key[MAX_PATH];
|
|
WCHAR *pop;
|
|
wcsncpy(company_key, L_DYNAMORIO_REGISTRY_KEY, MAX_PATH);
|
|
NULL_TERMINATE_BUFFER(company_key);
|
|
pop = wcsstr(company_key, L_COMPANY_NAME);
|
|
if (pop != NULL) {
|
|
pop += wcslen(L_COMPANY_NAME);
|
|
/* sanity check */
|
|
if (pop == wcsrchr(company_key, L'\\')) {
|
|
*pop = L'\0';
|
|
delete_product_key(DYNAMORIO_REGISTRY_HIVE, company_key);
|
|
} else
|
|
res = ERROR_BAD_FORMAT;
|
|
} else
|
|
res = ERROR_BAD_FORMAT;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
DWORD
|
|
setup_installation(const WCHAR *path, BOOL overwrite)
|
|
{
|
|
WCHAR buf[MAX_PATH];
|
|
|
|
/* if there's something there, leave it */
|
|
if (!overwrite && get_dynamorio_home() != NULL)
|
|
return ERROR_SUCCESS;
|
|
|
|
DO_DEBUG(DL_INFO, printf("setting up installation at: %S\n", path););
|
|
|
|
mkdir_with_parents(path);
|
|
|
|
if (!file_exists(path))
|
|
return ERROR_PATH_NOT_FOUND;
|
|
|
|
_snwprintf(buf, MAX_PATH, L"%s\\%s", path, L"conf");
|
|
NULL_TERMINATE_BUFFER(buf);
|
|
|
|
DO_DEBUG(DL_INFO, printf("making config dir: %S\n", buf););
|
|
|
|
mkdir_with_parents(buf);
|
|
|
|
if (!file_exists(buf))
|
|
return ERROR_PATH_NOT_FOUND;
|
|
|
|
_snwprintf(buf, MAX_PATH, L"%s\\%s", path, L"logs");
|
|
NULL_TERMINATE_BUFFER(buf);
|
|
|
|
DO_DEBUG(DL_INFO, printf("making logdir: %S\n", buf););
|
|
|
|
mkdir_with_parents(buf);
|
|
|
|
if (!file_exists(buf))
|
|
return ERROR_PATH_NOT_FOUND;
|
|
|
|
CHECKED_OPERATION(create_root_key());
|
|
|
|
CHECKED_OPERATION(
|
|
set_config_parameter(L_PRODUCT_NAME, FALSE, L_DYNAMORIO_VAR_HOME, path));
|
|
|
|
CHECKED_OPERATION(
|
|
set_config_parameter(L_PRODUCT_NAME, FALSE, L_DYNAMORIO_VAR_LOGDIR, buf));
|
|
|
|
/* reset the DR_HOME cache */
|
|
get_dynamorio_home_helper(TRUE);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
/* modifies permissions for 4.3 cache/User-SID directories to be
|
|
* created by users themselves
|
|
*/
|
|
DWORD
|
|
setup_cache_permissions(WCHAR *cacheRootDirectory)
|
|
{
|
|
DWORD result = ERROR_UNSUPPORTED_OS;
|
|
|
|
# define NUM_ACES 2
|
|
/* in C const int isn't good enough */
|
|
EXPLICIT_ACCESS ea[NUM_ACES];
|
|
|
|
PSID pSIDEveryone = NULL;
|
|
PSID pSIDCreatorOwner = NULL;
|
|
PACL pACL = NULL;
|
|
PACL pOldDACL = NULL;
|
|
|
|
SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
|
|
SID_IDENTIFIER_AUTHORITY SIDAuthCreator = SECURITY_CREATOR_SID_AUTHORITY;
|
|
DWORD dwRes;
|
|
|
|
SECURITY_DESCRIPTOR *pSD = NULL;
|
|
|
|
DWORD platform = 0; /* accomodating NT permissions */
|
|
get_platform(&platform);
|
|
|
|
/* Note that we prefer to not create ACLs from scratch, so that we
|
|
* can accommodate Administrator groups unknown to us that would
|
|
* have been inherited from \Program Files\. We should always
|
|
* start with a known ACL and just edit the new ACEs
|
|
*/
|
|
|
|
dwRes = GetNamedSecurityInfo(cacheRootDirectory, SE_FILE_OBJECT,
|
|
DACL_SECURITY_INFORMATION, NULL, NULL, &pOldDACL, NULL,
|
|
&pSD);
|
|
|
|
if (dwRes != ERROR_SUCCESS)
|
|
return dwRes;
|
|
|
|
/* Note: Although we are ADDING possibly existing ACE, it seems
|
|
* like this is handled well and we don't grow the ACL. For now
|
|
* this doesn't matter to us, since we expect to have just copied
|
|
* the flags from the lib\ directory so can't really accumulate.
|
|
*/
|
|
|
|
// Create a SID for the Everyone group.
|
|
if (!AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0,
|
|
0, &pSIDEveryone)) {
|
|
DO_DEBUG(DL_VERB, printf("AllocateAndInitializeSid (Everyone).\n"););
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
// Create a SID for the CREATOR OWNER group
|
|
if (!AllocateAndInitializeSid(&SIDAuthCreator, 1, SECURITY_CREATOR_OWNER_RID, 0, 0, 0,
|
|
0, 0, 0, 0, &pSIDCreatorOwner)) {
|
|
DO_DEBUG(DL_VERB, printf("AllocateAndInitializeSid (CreatorOwner).\n"););
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
ZeroMemory(&ea, NUM_ACES * sizeof(EXPLICIT_ACCESS));
|
|
|
|
/* Grant create directory access to Everyone, which will be in
|
|
* addition to existing Read/Execute permissions we are starting
|
|
* with.
|
|
*/
|
|
ea[0].grfAccessPermissions = FILE_ADD_SUBDIRECTORY;
|
|
ea[0].grfAccessMode = GRANT_ACCESS; /* not SET_ACCESS */
|
|
ea[0].grfInheritance = NO_INHERITANCE; /* ONLY in cache\ folder! */
|
|
ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
|
ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
|
|
ea[0].Trustee.ptstrName = (LPTSTR)pSIDEveryone;
|
|
|
|
/* Set full control for CREATOR OWNER on any subfolders */
|
|
ea[1].grfAccessPermissions = GENERIC_ALL;
|
|
ea[1].grfAccessMode = SET_ACCESS; /* we SET ALL */
|
|
if (platform == PLATFORM_WIN_NT_4) {
|
|
/* case 10502 INHERIT_ONLY_ACE seems to not work */
|
|
/* we are mostly interested in any subdirectory, and cache/ is
|
|
* already created (and also trusted), so adding it there
|
|
* doesn't affect anything.
|
|
*/
|
|
ea[1].grfInheritance = OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE;
|
|
} else {
|
|
/* not using the same as NT, since Creator Owner may already
|
|
* have this ACE (and normally does) so we'll clutter with a
|
|
* new incomplete one */
|
|
ea[1].grfInheritance =
|
|
INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE;
|
|
}
|
|
ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
|
ea[1].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
|
|
ea[1].Trustee.ptstrName = (LPTSTR)pSIDCreatorOwner;
|
|
|
|
/* XXX: we may want to disable the default group maybe should
|
|
* set CREATOR GROUP to no access otherwise we get the default
|
|
* Domain Users group (which usually is the Primary group) added,
|
|
* e.g. KRAMMER\None:R(ead)
|
|
*/
|
|
|
|
/* MSDN gave a false alarm that this doesn't exist on NT - It is
|
|
* present at least on sp6. XXX: may want to use GetProcAddress
|
|
* if we support earlier versions, but we'll know early enough.
|
|
* We don't really need to support anything other than User SYSTEM
|
|
* on NT for which we don't need this to work and can return
|
|
* ERROR_UNSUPPORTED_OS
|
|
*/
|
|
if (ERROR_SUCCESS !=
|
|
SetEntriesInAcl(NUM_ACES, ea, pOldDACL, /* original DACL */
|
|
&pACL)) {
|
|
DO_DEBUG(DL_VERB, printf("SetEntriesInAcl 0x%x\n", GetLastError()););
|
|
goto cleanup;
|
|
}
|
|
|
|
// Try to modify the object's DACL.
|
|
result = SetNamedSecurityInfo(
|
|
cacheRootDirectory, // name of the object
|
|
SE_FILE_OBJECT, // type of object
|
|
DACL_SECURITY_INFORMATION |
|
|
PROTECTED_DACL_SECURITY_INFORMATION, // change only the object's DACL
|
|
NULL, NULL, // do not change owner or group
|
|
pACL, // new DACL specified
|
|
NULL); // do not change SACL
|
|
|
|
if (ERROR_SUCCESS == result) {
|
|
DO_DEBUG(DL_VERB, printf("Successfully changed DACL\n"););
|
|
}
|
|
|
|
cleanup:
|
|
if (pSIDEveryone)
|
|
FreeSid(pSIDEveryone);
|
|
if (pSIDCreatorOwner)
|
|
FreeSid(pSIDCreatorOwner);
|
|
if (pACL)
|
|
LocalFree(pACL);
|
|
if (pSD)
|
|
LocalFree(pSD);
|
|
|
|
return result;
|
|
# undef NUM_ACES
|
|
}
|
|
|
|
/* cache_root should normally be get_dynamorio_home() */
|
|
DWORD
|
|
setup_cache_shared_directories(const WCHAR *cache_root)
|
|
{
|
|
DWORD res;
|
|
|
|
/* support for new-in-4.2 directories, update the permissions
|
|
* on the cache/ to be the same as those on lib/, and the
|
|
* cache/shared/ folder to be the same as those on logs/
|
|
*
|
|
* note that the relative paths of the cache and shared cache
|
|
* directories here should match the values set in
|
|
* setup_cache_shared_registry()
|
|
*/
|
|
WCHAR libpath[MAX_PATH];
|
|
WCHAR cachepath[MAX_PATH];
|
|
WCHAR logspath[MAX_PATH];
|
|
WCHAR sharedcachepath[MAX_PATH];
|
|
|
|
_snwprintf(libpath, MAX_PATH, L"%s\\lib", get_dynamorio_home());
|
|
NULL_TERMINATE_BUFFER(libpath);
|
|
_snwprintf(cachepath, MAX_PATH, L"%s\\cache", cache_root);
|
|
NULL_TERMINATE_BUFFER(cachepath);
|
|
_snwprintf(logspath, MAX_PATH, L"%s\\logs", get_dynamorio_home());
|
|
NULL_TERMINATE_BUFFER(logspath);
|
|
_snwprintf(sharedcachepath, MAX_PATH, L"%s\\shared", cachepath);
|
|
NULL_TERMINATE_BUFFER(sharedcachepath);
|
|
|
|
mkdir_with_parents(sharedcachepath);
|
|
/* XXX: no error checking */
|
|
|
|
res = copy_file_permissions(cachepath, libpath);
|
|
if (res != ERROR_SUCCESS) {
|
|
return res;
|
|
}
|
|
|
|
res = copy_file_permissions(sharedcachepath, logspath);
|
|
if (res != ERROR_SUCCESS) {
|
|
return res;
|
|
}
|
|
|
|
/* For in 4.3 ONLY if all users (most importantly services)
|
|
* validate their per-user directory (or files) for ownership
|
|
*/
|
|
res = setup_cache_permissions(cachepath);
|
|
if (res != ERROR_SUCCESS) {
|
|
return res;
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
/* cache_root should normally be get_dynamorio_home() */
|
|
DWORD
|
|
setup_cache_shared_registry(const WCHAR *cache_root, ConfigGroup *policy)
|
|
{
|
|
/* note that nodemgr doesn't need to do call this routine,
|
|
* since the registry keys are added to the node policies in
|
|
* controller/servlets/PolicyUpdateResponseHandler.java in the controller. but
|
|
* anyway we expect these to be forever the same, and in any
|
|
* case not configurable from the controller.
|
|
*/
|
|
WCHAR wpathbuf[MAX_PATH];
|
|
|
|
/* set up cache\ shared\ registry keys */
|
|
_snwprintf(wpathbuf, MAX_PATH, L"%s\\cache", cache_root);
|
|
NULL_TERMINATE_BUFFER(wpathbuf);
|
|
set_config_group_parameter(policy, L_IF_WIN(DYNAMORIO_VAR_CACHE_ROOT), wpathbuf);
|
|
|
|
/* set up cache\ shared\ registry keys */
|
|
_snwprintf(wpathbuf, MAX_PATH, L"%s\\cache\\shared", cache_root);
|
|
NULL_TERMINATE_BUFFER(wpathbuf);
|
|
set_config_group_parameter(policy, L_IF_WIN(DYNAMORIO_VAR_CACHE_SHARED), wpathbuf);
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
/* note that this checks the opstring against the
|
|
* version of core that matches this build, NOT the version
|
|
* of the core that's actually installed! */
|
|
BOOL
|
|
check_opstring(const WCHAR *opstring)
|
|
{
|
|
char *cbuf;
|
|
options_t ops;
|
|
int res;
|
|
size_t cbuf_size = wcslen(opstring) + 1;
|
|
cbuf = (char *)malloc(cbuf_size);
|
|
/* XXX: if malloc fails, do something */
|
|
_snprintf(cbuf, cbuf_size, "%S", opstring);
|
|
cbuf[cbuf_size - 1] = '\0';
|
|
res = set_dynamo_options(&ops, cbuf);
|
|
free(cbuf);
|
|
return !res;
|
|
}
|
|
|
|
HANDLE hToken = NULL;
|
|
TOKEN_PRIVILEGES Priv, OldPriv;
|
|
DWORD PrivSize = sizeof(OldPriv);
|
|
|
|
DWORD
|
|
acquire_privileges()
|
|
{
|
|
DWORD error;
|
|
|
|
/* if the privileges are already acquired, don't bother.
|
|
this almost certainly will cause failures if multiple
|
|
threads are trying to acquire privileges.
|
|
*/
|
|
// XXX - this should have real synchronization!!!
|
|
if (hToken != NULL)
|
|
return ERROR_ALREADY_INITIALIZED;
|
|
|
|
// get current thread token
|
|
if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, FALSE,
|
|
&hToken)) {
|
|
// can't get thread token, try process token instead
|
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES,
|
|
&hToken)) {
|
|
return GetLastError();
|
|
}
|
|
}
|
|
|
|
Priv.PrivilegeCount = 1;
|
|
Priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
|
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &Priv.Privileges[0].Luid);
|
|
// try to enable the privilege
|
|
if (!AdjustTokenPrivileges(hToken, FALSE, &Priv, sizeof(Priv), &OldPriv, &PrivSize)) {
|
|
return GetLastError();
|
|
}
|
|
error = GetLastError();
|
|
if (error == ERROR_NOT_ALL_ASSIGNED) {
|
|
/* acquiring SeDebugPrivilege requires being admin */
|
|
return error;
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
DWORD
|
|
release_privileges()
|
|
{
|
|
if (hToken == NULL)
|
|
return ERROR_NO_SUCH_PRIVILEGE;
|
|
|
|
AdjustTokenPrivileges(hToken, FALSE, &OldPriv, sizeof(OldPriv), NULL, NULL);
|
|
CloseHandle(hToken);
|
|
hToken = NULL;
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
void
|
|
wstr_replace(WCHAR *str, WCHAR orig, WCHAR new)
|
|
{
|
|
UINT i;
|
|
for (i = 0; i < wcslen(str); i++)
|
|
if (str[i] == orig)
|
|
str[i] = new;
|
|
return;
|
|
}
|
|
|
|
/* XXX: should return error code if the directory wasn't created and
|
|
* doesn't exist already
|
|
*/
|
|
void
|
|
mkdir_with_parents(const WCHAR *dirname)
|
|
{
|
|
WCHAR buf[MAX_PATH], *temp_subdir;
|
|
|
|
wcsncpy(buf, dirname, MAX_PATH);
|
|
NULL_TERMINATE_BUFFER(buf);
|
|
|
|
/* ensure proper slashes */
|
|
wstr_replace(buf, L'/', L'\\');
|
|
|
|
temp_subdir = buf;
|
|
|
|
while (temp_subdir != NULL) {
|
|
temp_subdir = wcschr(temp_subdir, L'\\');
|
|
if (temp_subdir != NULL)
|
|
*temp_subdir = L'\0';
|
|
|
|
DO_DEBUG(DL_VERB, printf("trying to make: %S\n", buf););
|
|
|
|
/* ok if this fails, eg the first time it will be C: */
|
|
CreateDirectory(buf, NULL);
|
|
|
|
if (temp_subdir != NULL) {
|
|
*temp_subdir = L'\\';
|
|
temp_subdir = temp_subdir + 1;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
void
|
|
ensure_directory_exists_for_file(WCHAR *filename)
|
|
{
|
|
WCHAR *slashptr, buf[MAX_PATH];
|
|
wcsncpy(buf, filename, MAX_PATH);
|
|
NULL_TERMINATE_BUFFER(buf);
|
|
|
|
slashptr = wcsrchr(buf, L'\\');
|
|
if (slashptr == NULL)
|
|
return;
|
|
|
|
*slashptr = L'\0';
|
|
|
|
mkdir_with_parents(buf);
|
|
}
|
|
|
|
/* XXX: apparently there's a bug in MSVCRT that converts
|
|
* \r\n to \r\r\n ? anyway that's what google and the evidence
|
|
* seem to indicate. (see policy.c for more)
|
|
*
|
|
* so we may want to convert this to using Win32 API instead of
|
|
* CRT. but then again we may not, just on principle. */
|
|
DWORD
|
|
write_file_contents(WCHAR *path, char *contents, BOOL overwrite)
|
|
{
|
|
FILE *fp = NULL;
|
|
DWORD res = ERROR_SUCCESS;
|
|
|
|
ensure_directory_exists_for_file(path);
|
|
|
|
fp = _wfopen(path, L"r");
|
|
if (fp != NULL) {
|
|
if (!overwrite)
|
|
return ERROR_ALREADY_EXISTS;
|
|
fclose(fp);
|
|
}
|
|
|
|
fp = _wfopen(path, L"w");
|
|
if (fp == NULL) {
|
|
res = delete_file_rename_in_use(path);
|
|
if (res != ERROR_SUCCESS || (fp = _wfopen(path, L"w")) == NULL) {
|
|
DO_DEBUG(DL_ERROR, printf("Unable to open file: %S (%d)\n", path, errno););
|
|
return res;
|
|
}
|
|
}
|
|
|
|
if (strlen(contents) != fwrite(contents, 1, strlen(contents), fp)) {
|
|
DO_DEBUG(DL_ERROR, printf("Write failed to file: %S (errno=%d)\n", path, errno););
|
|
res = ERROR_WRITE_FAULT;
|
|
}
|
|
|
|
DO_DEBUG(DL_INFO, printf("wrote file %S\n", path););
|
|
|
|
fclose(fp);
|
|
return res;
|
|
}
|
|
|
|
DWORD
|
|
write_file_contents_if_different(WCHAR *path, char *contents, BOOL *changed)
|
|
{
|
|
char *existing;
|
|
DWORD res;
|
|
|
|
DO_ASSERT(path != NULL);
|
|
DO_ASSERT(contents != NULL);
|
|
DO_ASSERT(changed != NULL);
|
|
|
|
existing = (char *)malloc(strlen(contents) + 1);
|
|
res = read_file_contents(path, existing, strlen(contents) + 1, NULL);
|
|
|
|
if (res == ERROR_SUCCESS && 0 == strcmp(contents, existing)) {
|
|
*changed = FALSE;
|
|
res = ERROR_SUCCESS;
|
|
} else {
|
|
*changed = TRUE;
|
|
res = write_file_contents(path, contents, TRUE);
|
|
}
|
|
|
|
free(existing);
|
|
|
|
return res;
|
|
}
|
|
|
|
# define READ_BUF_SZ 1024
|
|
|
|
DWORD
|
|
read_file_contents(WCHAR *path, char *contents, SIZE_T maxchars, SIZE_T *needed)
|
|
{
|
|
FILE *fp = NULL;
|
|
DWORD res = ERROR_SUCCESS;
|
|
SIZE_T n_read = 0;
|
|
SIZE_T n_needed = 0;
|
|
char buf[READ_BUF_SZ];
|
|
|
|
DO_ASSERT(path != NULL);
|
|
DO_ASSERT(contents != NULL || needed != NULL);
|
|
DO_ASSERT(contents == NULL || maxchars > 0);
|
|
|
|
fp = _wfopen(path, L"r");
|
|
if (fp == NULL) {
|
|
DO_DEBUG(DL_INFO, printf("Not found: %S\n", path););
|
|
return ERROR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
if (contents != NULL) {
|
|
n_read = fread(contents, 1, maxchars, fp);
|
|
|
|
/* NULL terminate string. */
|
|
contents[n_read == maxchars ? n_read - 1 : n_read] = '\0';
|
|
|
|
DO_DEBUG(DL_FINEST,
|
|
printf("*Read %zu bytes from %S (max=%zu)\n", n_read, path, maxchars););
|
|
}
|
|
|
|
n_needed = n_read;
|
|
|
|
while (!feof(fp)) {
|
|
res = ERROR_MORE_DATA;
|
|
|
|
n_read = fread(buf, 1, READ_BUF_SZ, fp);
|
|
|
|
DO_DEBUG(DL_FINEST, printf(" Read an additional %zu bytes\n", n_read););
|
|
|
|
if (n_read == 0 && !feof(fp)) {
|
|
res = ERROR_READ_FAULT;
|
|
break;
|
|
}
|
|
|
|
n_needed += n_read;
|
|
}
|
|
|
|
/* + 1 for the NULL terminator */
|
|
n_needed += 1;
|
|
|
|
if (needed != NULL)
|
|
*needed = n_needed;
|
|
|
|
fclose(fp);
|
|
|
|
if (res == ERROR_SUCCESS || res == ERROR_MORE_DATA) {
|
|
DO_DEBUG(
|
|
DL_VERB,
|
|
printf("file %S contents: (%zu needed)\n\n%s\n", path, n_needed, contents););
|
|
} else {
|
|
DO_DEBUG(DL_ERROR, printf("read failed, error %d\n", res););
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
DWORD
|
|
delete_tree(const WCHAR *path)
|
|
{
|
|
WIN32_FIND_DATA data;
|
|
HANDLE hFind;
|
|
WCHAR pathbuf[MAX_PATH], subdirbuf[MAX_PATH];
|
|
|
|
if (path == NULL)
|
|
return ERROR_INVALID_PARAMETER;
|
|
|
|
_snwprintf(pathbuf, MAX_PATH, L"%s\\*.*", path);
|
|
NULL_TERMINATE_BUFFER(pathbuf);
|
|
|
|
hFind = FindFirstFile(pathbuf, &data);
|
|
if (hFind == INVALID_HANDLE_VALUE)
|
|
return GetLastError();
|
|
|
|
DO_DEBUG(DL_VERB, printf("dt working on %S\n", path););
|
|
do {
|
|
if (wcscmp(data.cFileName, L".") == 0 || wcscmp(data.cFileName, L"..") == 0)
|
|
continue;
|
|
|
|
/* case 7407: FindNextFile on a FAT32 filesystem returns files in
|
|
* the order they were written to disk, which could be different
|
|
* from NTFS where the order is alphabetical (from MSDN).
|
|
* Also on FAT32, FindNextFile sometimes puts us back in the loop
|
|
* for the file we just renamed and we try to
|
|
* delete_file_rename_in_use the file we just renamed for a very
|
|
* long time (>3 hrs).
|
|
*
|
|
* XXX: temporary hack: if filename has .tmp in its name
|
|
* (first ocurrance), assume we just renamed it and skip.
|
|
*
|
|
* note we may want to doublecheck that the file is indeed not
|
|
* deletable although we now add it to the
|
|
* PendingFileRenameOperations so such unused files can't stay
|
|
* around for too long.
|
|
*/
|
|
if (wcsstr(data.cFileName, L".tmp") != NULL)
|
|
continue;
|
|
|
|
DO_DEBUG(DL_VERB,
|
|
printf("dt still working on %S, %d\n", data.cFileName,
|
|
data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY););
|
|
|
|
_snwprintf(subdirbuf, MAX_PATH, L"%s\\%s", path, data.cFileName);
|
|
NULL_TERMINATE_BUFFER(subdirbuf);
|
|
|
|
/* case 4512: use rename trick if file is in use, so that
|
|
* the uninstall/reinstall case will work */
|
|
if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
delete_tree(subdirbuf);
|
|
else
|
|
delete_file_rename_in_use(subdirbuf);
|
|
} while (FindNextFile(hFind, &data));
|
|
|
|
if (!FindClose(hFind))
|
|
return GetLastError();
|
|
|
|
if (!RemoveDirectory(path))
|
|
return GetLastError();
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* helper function for registry permissions workaround. stopgap until
|
|
* we can make a decent permissions api.
|
|
*
|
|
* based on example code obtained from:
|
|
* http://www.codeproject.com/system/secntobj.asp
|
|
*/
|
|
|
|
PSID
|
|
getSID(WCHAR *user)
|
|
{
|
|
DWORD dwSidLen = 0, dwDomainLen = 0;
|
|
SID_NAME_USE SidNameUse;
|
|
PSID pRet = NULL;
|
|
PSID pSid = NULL;
|
|
WCHAR *lpDomainName = NULL;
|
|
|
|
/* The function on the first call retives the length that we need
|
|
* to initialize the SID & domain name pointers */
|
|
if (!LookupAccountName(NULL, user, NULL, &dwSidLen, NULL, &dwDomainLen,
|
|
&SidNameUse)) {
|
|
if (ERROR_INSUFFICIENT_BUFFER == GetLastError()) {
|
|
|
|
pSid = LocalAlloc(LMEM_ZEROINIT, dwSidLen);
|
|
lpDomainName = LocalAlloc(LMEM_ZEROINIT, dwDomainLen * sizeof(WCHAR));
|
|
|
|
if (pSid && lpDomainName &&
|
|
LookupAccountName(NULL, user, pSid, &dwSidLen, lpDomainName, &dwDomainLen,
|
|
&SidNameUse)) {
|
|
pRet = pSid;
|
|
pSid = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* if successful, was set to NULL and left in pRet */
|
|
LocalFree(pSid);
|
|
LocalFree(lpDomainName);
|
|
|
|
return pRet;
|
|
}
|
|
|
|
BOOL
|
|
make_acl(DWORD count, WCHAR **userArray, DWORD *maskArray, ACL **acl)
|
|
{
|
|
DWORD dwLoop = 0;
|
|
DWORD dwAclLen = 0;
|
|
PACL pRetAcl = NULL;
|
|
PSID *ppStoreSid = NULL;
|
|
BOOL bRes = FALSE;
|
|
|
|
if (acl == NULL)
|
|
goto cleanup;
|
|
|
|
ppStoreSid = LocalAlloc(LMEM_ZEROINIT, count * sizeof(void *));
|
|
|
|
if (ppStoreSid == NULL)
|
|
goto cleanup;
|
|
|
|
for (dwLoop = 0; dwLoop < count; dwLoop++) {
|
|
|
|
ppStoreSid[dwLoop] = getSID(userArray[dwLoop]);
|
|
|
|
if (ppStoreSid[dwLoop] == NULL)
|
|
goto cleanup;
|
|
|
|
dwAclLen +=
|
|
GetLengthSid(ppStoreSid[dwLoop]) + sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD);
|
|
}
|
|
|
|
dwAclLen += sizeof(ACL);
|
|
pRetAcl = LocalAlloc(LMEM_ZEROINIT, dwAclLen);
|
|
|
|
if (pRetAcl == NULL || !InitializeAcl(pRetAcl, dwAclLen, ACL_REVISION))
|
|
goto cleanup;
|
|
|
|
for (dwLoop = 0; dwLoop < count; dwLoop++) {
|
|
/* only adding access allowed ACE's */
|
|
if (!AddAccessAllowedAce(pRetAcl, ACL_REVISION, maskArray[dwLoop],
|
|
ppStoreSid[dwLoop]))
|
|
goto cleanup;
|
|
}
|
|
|
|
*acl = pRetAcl;
|
|
pRetAcl = NULL;
|
|
bRes = TRUE;
|
|
|
|
cleanup:
|
|
|
|
if (ppStoreSid != NULL) {
|
|
for (dwLoop = 0; dwLoop < count; dwLoop++)
|
|
LocalFree(ppStoreSid[dwLoop]);
|
|
LocalFree(ppStoreSid);
|
|
}
|
|
|
|
/* if successful, was set to NULL and left in *acl */
|
|
LocalFree(pRetAcl);
|
|
|
|
return bRes;
|
|
}
|
|
|
|
# define NUM_ACL_ENTRIES 4
|
|
|
|
DWORD
|
|
set_registry_permissions_for_user(WCHAR *hklm_keyname, WCHAR *user)
|
|
{
|
|
SECURITY_DESCRIPTOR sd;
|
|
SID *owner = NULL;
|
|
ACL *acl1 = NULL;
|
|
DWORD res;
|
|
HKEY hkey = NULL;
|
|
|
|
WCHAR *users[NUM_ACL_ENTRIES] = {
|
|
L"Administrators",
|
|
L"Everyone",
|
|
L"SYSTEM",
|
|
NULL,
|
|
};
|
|
|
|
DWORD masks[NUM_ACL_ENTRIES] = {
|
|
KEY_ALL_ACCESS | DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER,
|
|
KEY_READ,
|
|
KEY_ALL_ACCESS | DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER,
|
|
KEY_ALL_ACCESS,
|
|
};
|
|
|
|
users[NUM_ACL_ENTRIES - 1] = user;
|
|
|
|
DO_DEBUG(DL_VERB, printf("Starting acl..\n"););
|
|
|
|
res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, hklm_keyname, 0,
|
|
platform_key_flags() | KEY_ALL_ACCESS, &hkey);
|
|
|
|
if (res != ERROR_SUCCESS)
|
|
goto error_out;
|
|
|
|
DO_DEBUG(DL_VERB, printf("Got key handle.\n"););
|
|
|
|
if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) {
|
|
res = GetLastError();
|
|
goto error_out;
|
|
}
|
|
|
|
owner = getSID(users[0]);
|
|
|
|
if (NULL == owner) {
|
|
res = ERROR_INVALID_DATA;
|
|
goto error_out;
|
|
}
|
|
|
|
if (!SetSecurityDescriptorOwner(&sd, owner, FALSE)) {
|
|
res = GetLastError();
|
|
goto error_out;
|
|
}
|
|
|
|
DO_DEBUG(DL_VERB, printf("Set owner.\n"););
|
|
|
|
if (!make_acl(NUM_ACL_ENTRIES, users, masks, &acl1)) {
|
|
res = ERROR_ACCESS_DENIED;
|
|
goto error_out;
|
|
}
|
|
|
|
DO_DEBUG(DL_VERB, printf("Made ACL.\n"););
|
|
|
|
if (!SetSecurityDescriptorDacl(&sd, TRUE, acl1, FALSE)) {
|
|
res = GetLastError();
|
|
goto error_out;
|
|
}
|
|
|
|
if (!IsValidSecurityDescriptor(&sd)) {
|
|
res = GetLastError();
|
|
goto error_out;
|
|
}
|
|
|
|
res = RegSetKeySecurity(hkey, DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION,
|
|
&sd);
|
|
|
|
DO_DEBUG(DL_VERB, printf("Set sacl.\n"););
|
|
|
|
goto cleanup;
|
|
|
|
error_out:
|
|
|
|
/* make sure to return an error */
|
|
if (res == ERROR_SUCCESS)
|
|
res = ERROR_ACCESS_DENIED;
|
|
|
|
cleanup:
|
|
|
|
if (hkey != NULL)
|
|
RegCloseKey(hkey);
|
|
|
|
LocalFree(owner);
|
|
LocalFree(acl1);
|
|
|
|
return res;
|
|
}
|
|
|
|
/* will limit to 1 MB */
|
|
# define MAX_INSERT_SIZE (1024 * 1024)
|
|
|
|
static void
|
|
insert_file(FILE *file, wchar_t *file_src_name, BOOL delete)
|
|
{
|
|
/* 3rd arg not needed but older headers do not declare as optional */
|
|
int fd_src = _wopen(file_src_name, _O_RDONLY | _O_BINARY, 0);
|
|
long length;
|
|
int error;
|
|
|
|
if (fd_src == -1) {
|
|
fprintf(file, "Unable to open file \"%S\" for inserting\n", file_src_name);
|
|
return;
|
|
}
|
|
length = _filelength(fd_src);
|
|
if (length == -1L) {
|
|
fprintf(file, "Unable to get file length for file \"%S\"\n", file_src_name);
|
|
return;
|
|
}
|
|
if (length > MAX_INSERT_SIZE) {
|
|
fprintf(file, "File size exceeds max insert length, truncating from %d to %d\n",
|
|
length, MAX_INSERT_SIZE);
|
|
length = MAX_INSERT_SIZE;
|
|
}
|
|
|
|
fprintf(file, "Inserting file: name=\"%S\" length=%d\n", file_src_name, length);
|
|
/* hmm, there's prob. a better way to do this ... */
|
|
# define COPY_BUF_SIZE 4096
|
|
{
|
|
char buf[COPY_BUF_SIZE] = { 0 };
|
|
long i = 0;
|
|
while (i + COPY_BUF_SIZE <= length) {
|
|
_read(fd_src, buf, COPY_BUF_SIZE);
|
|
fwrite(buf, 1, COPY_BUF_SIZE, file);
|
|
i += COPY_BUF_SIZE;
|
|
}
|
|
if (i < length) {
|
|
_read(fd_src, buf, length - i);
|
|
fwrite(buf, 1, length - i, file);
|
|
}
|
|
}
|
|
|
|
fprintf(file, "Finished inserting file\n");
|
|
if (_read(fd_src, &error, sizeof(error)) != 0)
|
|
fprintf(file, "ERROR : file continues beyond length\n");
|
|
_close(fd_src);
|
|
if (delete) {
|
|
DeleteFile(file_src_name);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* see utils.h for description */
|
|
DWORD
|
|
get_violation_info(EVENTLOGRECORD *pevlr, /* INOUT */ VIOLATION_INFO *info)
|
|
{
|
|
DO_ASSERT(pevlr != NULL && info != NULL && pevlr->EventID == MSG_SEC_FORENSICS);
|
|
info->report = NULL;
|
|
if (pevlr->EventID != MSG_SEC_FORENSICS)
|
|
return ERROR_INVALID_PARAMETER;
|
|
info->report = get_forensics_filename(pevlr);
|
|
if (file_exists(info->report))
|
|
return ERROR_SUCCESS;
|
|
else
|
|
return ERROR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
wchar_t *canary_process_names[] = { L"canary.exe", L"services.exe", L"iexplore.exe" };
|
|
# define num_canary_processes BUFFER_SIZE_ELEMENTS(canary_process_names)
|
|
/* how long to wait for an apparently hung canary process */
|
|
# define CANARY_HANG_WAIT 20000
|
|
/* interval to wait for the canary process to do something */
|
|
# define CANARY_SLEEP_WAIT 100
|
|
|
|
# define OPTIONS_CANARY_NATIVE \
|
|
L" -list_modules -check_for_hooked_mods_list ntdll.dll"
|
|
# define OPTIONS_CANARY_THIN_CLIENT L""
|
|
# define OPTIONS_CANARY_CLIENT L""
|
|
# define OPTIONS_CANARY_MF L""
|
|
# define OPTIONS_CANARY_INJECT L"-wait"
|
|
|
|
/* XXX - could even get ldmps ... */
|
|
/* XXX - xref case 10322 on -syslog_mask 0, eventually should remove and verify
|
|
* expected eventlog output (and get PE to ignore them). */
|
|
# define OPTIONS_THIN_CLIENT L"-thin_client -syslog_mask 0"
|
|
# define OPTIONS_CLIENT L"-client -syslog_mask 0"
|
|
/* XXX - temporary hack so virus scan correctly identified by canary. Weird case
|
|
* since this is considered a survivable violation by default (and so ignores kill
|
|
* proc).
|
|
*/
|
|
# define OPTIONS_MF L"-apc_policy 0 -syslog_mask 0"
|
|
|
|
/* returns the appropriate canary fail code */
|
|
static int
|
|
run_individual_canary_test(FILE *file, WCHAR *logbase, WCHAR *dr_options, int exe_index,
|
|
ConfigGroup *policy, WCHAR *exe, WCHAR *exe_args,
|
|
BOOL inject_test, char *type, BOOL early_test)
|
|
{
|
|
STARTUPINFO sinfo = { sizeof(sinfo), NULL, L"", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
NULL, NULL, NULL, NULL };
|
|
PROCESS_INFORMATION pinfo;
|
|
int canary_code = CANARY_SUCCESS;
|
|
WCHAR logbuf[MAX_PATH] = { 0 };
|
|
WCHAR outfile[MAX_PATH];
|
|
WCHAR cmd_buf[5 * MAX_PATH];
|
|
|
|
/* set up registry */
|
|
get_unique_filename(logbase, L"canary_logs", L"", logbuf,
|
|
BUFFER_SIZE_ELEMENTS(logbuf));
|
|
CreateDirectory(logbuf, NULL);
|
|
set_config_group_parameter(get_child(canary_process_names[exe_index], policy),
|
|
L_DYNAMORIO_VAR_LOGDIR, logbuf);
|
|
set_config_group_parameter(get_child(canary_process_names[exe_index], policy),
|
|
L_DYNAMORIO_VAR_OPTIONS, dr_options);
|
|
write_config_group(policy);
|
|
|
|
/* set up cmd_buf */
|
|
_snwprintf(outfile, BUFFER_SIZE_ELEMENTS(outfile), L"%s\\out.rep", logbuf);
|
|
NULL_TERMINATE_BUFFER(outfile);
|
|
if (early_test) {
|
|
/* we get the canary_process to re-launch itself to run test with early inject */
|
|
_snwprintf(cmd_buf, BUFFER_SIZE_ELEMENTS(cmd_buf),
|
|
L"\"%s\" \"%s\" -launch_child %s%d \"\\\"%s\\\" %s\"", exe, outfile,
|
|
inject_test ? L"-verify_inject " : L"", CANARY_HANG_WAIT / 2, outfile,
|
|
exe_args);
|
|
} else {
|
|
_snwprintf(cmd_buf, BUFFER_SIZE_ELEMENTS(cmd_buf), L"\"%s\" \"%s\" %s", exe,
|
|
outfile, exe_args);
|
|
}
|
|
NULL_TERMINATE_BUFFER(cmd_buf);
|
|
|
|
fprintf(file, "Starting Canary Process \"%S\" core_ops=\"%S\" type=%s%s\n", cmd_buf,
|
|
dr_options, type, inject_test ? " inject" : "");
|
|
if (CreateProcess(NULL, cmd_buf, NULL, NULL, TRUE, 0, NULL, NULL, &sinfo, &pinfo)) {
|
|
if (inject_test && !early_test) {
|
|
DWORD sleep_count = 0, under_dr_code, ws, build = 0;
|
|
do {
|
|
ws = WaitForSingleObject(pinfo.hProcess, CANARY_SLEEP_WAIT);
|
|
sleep_count += CANARY_SLEEP_WAIT;
|
|
under_dr_code = under_dynamorio_ex(pinfo.dwProcessId, &build);
|
|
} while (ws == WAIT_TIMEOUT && sleep_count < CANARY_HANG_WAIT &&
|
|
(under_dr_code == DLL_UNKNOWN || under_dr_code == DLL_NONE));
|
|
if (under_dr_code == DLL_UNKNOWN || under_dr_code == DLL_NONE) {
|
|
canary_code = CANARY_FAIL_APP_INIT_INJECTION;
|
|
fprintf(file, "Injection Failed - verify registry settings\n");
|
|
} else {
|
|
fprintf(file, "Verified Injection, build %d\n", build);
|
|
}
|
|
if (ws == WAIT_TIMEOUT)
|
|
terminate_process(pinfo.dwProcessId);
|
|
} else {
|
|
DWORD ws = WaitForSingleObject(pinfo.hProcess, CANARY_HANG_WAIT);
|
|
if (ws == WAIT_TIMEOUT) {
|
|
if (early_test && inject_test) {
|
|
canary_code = CANARY_FAIL_EARLY_INJECTION;
|
|
fprintf(file, "Early Injection Failed\n");
|
|
} else {
|
|
canary_code = CANARY_FAIL_HUNG;
|
|
fprintf(file, "Canary Hung\n");
|
|
}
|
|
terminate_process(pinfo.dwProcessId);
|
|
} else {
|
|
DWORD exit_code = 0;
|
|
GetExitCodeProcess(pinfo.hProcess, &exit_code);
|
|
/* XXX - check return value, shouldn't ever fail though */
|
|
if (exit_code != CANARY_PROCESS_EXP_EXIT_CODE) {
|
|
/* XXX - the -1 is based on the core value for kill
|
|
* proc, should export that and use it, or really just check for
|
|
* violations since we'll want the forensics anyways. Doesn't
|
|
* disambiguate between dr error and violation. */
|
|
if (exit_code == (DWORD)-1) {
|
|
canary_code = CANARY_FAIL_VIOLATION;
|
|
fprintf(file, "Canary Violation or DR error\n");
|
|
} else {
|
|
canary_code = CANARY_FAIL_CRASH;
|
|
fprintf(file, "Canary Crashed 0x%08x\n", exit_code);
|
|
}
|
|
} else if (early_test && inject_test) {
|
|
fprintf(file, "Verified Early Injection\n");
|
|
}
|
|
}
|
|
}
|
|
CloseHandle(pinfo.hProcess);
|
|
CloseHandle(pinfo.hThread);
|
|
{
|
|
HANDLE hFind;
|
|
WIN32_FIND_DATA data;
|
|
WCHAR file_name[MAX_PATH], pattern[MAX_PATH];
|
|
|
|
_snwprintf(pattern, BUFFER_SIZE_ELEMENTS(pattern), L"%s\\*.*", logbuf);
|
|
NULL_TERMINATE_BUFFER(pattern);
|
|
hFind = FindFirstFile(pattern, &data);
|
|
if (hFind != INVALID_HANDLE_VALUE) {
|
|
do {
|
|
if (wcscmp(data.cFileName, L".") == 0 ||
|
|
wcscmp(data.cFileName, L"..") == 0)
|
|
continue;
|
|
_snwprintf(file_name, BUFFER_SIZE_ELEMENTS(file_name), L"%s\\%s",
|
|
logbuf, data.cFileName);
|
|
NULL_TERMINATE_BUFFER(file_name);
|
|
insert_file(file, file_name, FALSE);
|
|
} while (FindNextFile(hFind, &data));
|
|
FindClose(hFind);
|
|
}
|
|
}
|
|
fprintf(file, "Canary Finished\n");
|
|
} else {
|
|
fprintf(file, "Canary \"%S\" Failed to Launch\n", cmd_buf);
|
|
}
|
|
return canary_code;
|
|
}
|
|
|
|
# pragma warning(disable \
|
|
: 4127) // conditional expression is constant i.e while (FALSE)
|
|
|
|
/* see utils.h for description */
|
|
BOOL
|
|
run_canary_test_ex(FILE *file, /* INOUT */ CANARY_INFO *info, const WCHAR *scratch_folder,
|
|
const WCHAR *canary_process)
|
|
{
|
|
ConfigGroup *policy, *save_policy;
|
|
WCHAR exe_buf[num_canary_processes][MAX_PATH];
|
|
WCHAR log_folder[MAX_PATH];
|
|
DWORD i;
|
|
BOOL autoinject_set = is_autoinjection_set();
|
|
|
|
info->canary_code = ERROR_SUCCESS;
|
|
info->url = L"CFail";
|
|
info->msg = L"Canary Failed";
|
|
|
|
read_config_group(&save_policy, L_PRODUCT_NAME, TRUE);
|
|
save_policy->should_clear = TRUE;
|
|
read_config_group(&policy, L_PRODUCT_NAME, TRUE);
|
|
policy->should_clear = TRUE;
|
|
remove_children(policy);
|
|
|
|
_snwprintf(log_folder, BUFFER_SIZE_ELEMENTS(log_folder), L"%s\\canary_logs",
|
|
scratch_folder);
|
|
NULL_TERMINATE_BUFFER(log_folder);
|
|
CreateDirectory(log_folder, NULL);
|
|
|
|
for (i = 0; i < num_canary_processes; i++) {
|
|
_snwprintf(exe_buf[i], BUFFER_SIZE_ELEMENTS(exe_buf[i]), L"%s\\%s",
|
|
scratch_folder, canary_process_names[i]);
|
|
NULL_TERMINATE_BUFFER(exe_buf[i]);
|
|
if (CopyFile(canary_process, exe_buf[i], FALSE) == 0) {
|
|
fprintf(file, "Failed to copy canary file %S to %S\n", canary_process,
|
|
exe_buf[i]);
|
|
/* XXX- continue if file exists from a previous run that didn't clean up */
|
|
info->canary_code = CANARY_UNABLE_TO_TEST;
|
|
goto canary_exit;
|
|
}
|
|
add_config_group(policy, new_config_group(canary_process_names[i]));
|
|
set_config_group_parameter(get_child(canary_process_names[i], policy),
|
|
L_DYNAMORIO_VAR_RUNUNDER, L"1");
|
|
}
|
|
write_config_group(policy);
|
|
|
|
/* XXX - monitor eventlog though we should still detect via forensics and/or
|
|
* exit code (crash/violation). Xref 10322, for now we suppress eventlogs. */
|
|
/* XXX - the verify injection tests need work, should just talk to canary proc. */
|
|
/* XXX - verify canary output - necessary? not clear what action would be */
|
|
/* Files are copied, begin runs */
|
|
|
|
# define DO_RUN(run_flag, core_ops, canary_options, inject, run_name, test_type) \
|
|
do { \
|
|
if (TESTANY(run_flag, info->run_flags)) { \
|
|
WCHAR *canary_ops = TESTANY(run_flag, info->fault_run) \
|
|
? info->canary_fault_args \
|
|
: canary_options; \
|
|
for (i = 0; i < num_canary_processes; i++) { \
|
|
int code = run_individual_canary_test( \
|
|
file, log_folder, core_ops, i, policy, exe_buf[i], \
|
|
canary_ops, inject, run_name, FALSE /* not early */); \
|
|
if (code >= 0 && test_type != CANARY_TEST_TYPE_NATIVE) { \
|
|
code = run_individual_canary_test( \
|
|
file, log_folder, core_ops, i, policy, exe_buf[i], \
|
|
canary_ops, inject, run_name, TRUE /* early inject*/); \
|
|
} \
|
|
if (code < 0) { \
|
|
if (CANARY_RUN_REQUIRES_PASS(run_flag, info->run_flags)) { \
|
|
info->canary_code = GET_CANARY_CODE(test_type, code); \
|
|
goto canary_exit; \
|
|
} \
|
|
break; /* skip remaining tests in run once first failure \
|
|
found */ \
|
|
} \
|
|
} \
|
|
} \
|
|
} while (FALSE)
|
|
|
|
/* First the native runs. */
|
|
unset_autoinjection();
|
|
|
|
/* native info gathering run. */
|
|
DO_RUN(CANARY_RUN_NATIVE, L"", OPTIONS_CANARY_NATIVE, FALSE, "native",
|
|
CANARY_TEST_TYPE_NATIVE);
|
|
|
|
set_autoinjection(); /* Going to do the non-native runs now */
|
|
|
|
/* Now the -thin_client inject run */
|
|
DO_RUN(CANARY_RUN_THIN_CLIENT_INJECT, OPTIONS_THIN_CLIENT, OPTIONS_CANARY_INJECT,
|
|
TRUE, "-thin_client", CANARY_TEST_TYPE_THIN_CLIENT);
|
|
|
|
/* now the full -thin_client run */
|
|
DO_RUN(CANARY_RUN_THIN_CLIENT, OPTIONS_THIN_CLIENT, OPTIONS_CANARY_THIN_CLIENT, FALSE,
|
|
"-thin_client", CANARY_TEST_TYPE_THIN_CLIENT);
|
|
|
|
/* Now the -client run */
|
|
DO_RUN(CANARY_RUN_CLIENT, OPTIONS_CLIENT, OPTIONS_CANARY_CLIENT, FALSE, "-client",
|
|
CANARY_TEST_TYPE_CLIENT);
|
|
|
|
/* Now the MF run */
|
|
DO_RUN(CANARY_RUN_MF, OPTIONS_MF, OPTIONS_CANARY_MF, FALSE, "MF",
|
|
CANARY_TEST_TYPE_MF);
|
|
|
|
# undef DO_RUN
|
|
|
|
canary_exit:
|
|
if (autoinject_set)
|
|
set_autoinjection();
|
|
else
|
|
unset_autoinjection();
|
|
free_config_group(policy);
|
|
write_config_group(save_policy);
|
|
free_config_group(save_policy);
|
|
fprintf(file, "Canary code 0x%08x\n", info->canary_code);
|
|
if (info->canary_code >= 0) {
|
|
info->url = L"ctest";
|
|
info->msg = L"Canary success";
|
|
}
|
|
return (info->canary_code >= 0);
|
|
}
|
|
|
|
/* see utils.h for description */
|
|
BOOL
|
|
run_canary_test(/* INOUT */ CANARY_INFO *info, WCHAR *version_msg)
|
|
{
|
|
BOOL result;
|
|
DWORD res;
|
|
FILE *report_file;
|
|
WCHAR scratch_folder[MAX_PATH], canary_process[MAX_PATH];
|
|
const WCHAR *dynamorio_home = get_dynamorio_home();
|
|
const WCHAR *dynamorio_logdir = get_dynamorio_logdir();
|
|
_snwprintf(canary_process, BUFFER_SIZE_ELEMENTS(canary_process),
|
|
L"%s\\bin\\canary.exe", dynamorio_home);
|
|
NULL_TERMINATE_BUFFER(canary_process);
|
|
_snwprintf(scratch_folder, BUFFER_SIZE_ELEMENTS(scratch_folder), L"%s\\canary_test",
|
|
dynamorio_logdir);
|
|
NULL_TERMINATE_BUFFER(scratch_folder);
|
|
/* xref case 10157, let's try to make sure this stays clean */
|
|
delete_tree(scratch_folder);
|
|
CreateDirectory(scratch_folder, NULL);
|
|
/* XXX - verify directory created */
|
|
/* Using get unique file name since we plan to run this more then once,
|
|
* though only an issue if the caller doesn't cleanup the report file and
|
|
* leaves it locked. */
|
|
get_unique_filename(dynamorio_logdir, L"canary_report", L".crep", info->buf_report,
|
|
BUFFER_SIZE_ELEMENTS(info->buf_report));
|
|
info->report = info->buf_report;
|
|
report_file = _wfopen(info->report, L"wb");
|
|
/* XXX - verify file creation */
|
|
fprintf(report_file, "%S\n", version_msg == NULL ? L"unknown version" : version_msg);
|
|
result = run_canary_test_ex(report_file, info, scratch_folder, canary_process);
|
|
res = delete_tree(scratch_folder);
|
|
fprintf(report_file, "Deleted scratch folder \"%S\", code %d\n", scratch_folder, res);
|
|
fclose(report_file);
|
|
return result;
|
|
}
|
|
|
|
# endif /* WINDOWS */
|
|
|
|
#else // ifdef UNIT_TEST
|
|
|
|
int
|
|
main()
|
|
{
|
|
set_debuglevel(DL_INFO);
|
|
set_abortlevel(DL_WARN);
|
|
|
|
/* read/write file */
|
|
{
|
|
char *test1, *test2;
|
|
char buffy[1024];
|
|
WCHAR *fn = L"utils.tst";
|
|
SIZE_T needed;
|
|
BOOL changed;
|
|
|
|
test1 = "This is a stupid file.\r\n\r\nDon't you think?\r\n";
|
|
test2 = "foo\r\n";
|
|
|
|
CHECKED_OPERATION(write_file_contents(fn, test1, TRUE));
|
|
|
|
DO_ASSERT(ERROR_MORE_DATA == read_file_contents(fn, NULL, 0, &needed));
|
|
DO_ASSERT(strlen(test1) + 1 == needed);
|
|
|
|
CHECKED_OPERATION(read_file_contents(fn, buffy, needed, NULL));
|
|
DO_ASSERT(0 == strcmp(test1, buffy));
|
|
|
|
CHECKED_OPERATION(write_file_contents_if_different(fn, test1, &changed));
|
|
DO_ASSERT(!changed);
|
|
|
|
CHECKED_OPERATION(write_file_contents_if_different(fn, test2, &changed));
|
|
DO_ASSERT(changed);
|
|
|
|
CHECKED_OPERATION(read_file_contents(fn, buffy, 1024, NULL));
|
|
DO_ASSERT(0 == strcmp(test2, buffy));
|
|
}
|
|
|
|
/* file existence */
|
|
{
|
|
WCHAR *fn = L"tester-file";
|
|
|
|
DeleteFile(fn);
|
|
DO_ASSERT(!file_exists(fn));
|
|
DO_ASSERT(!file_exists(fn));
|
|
|
|
CHECKED_OPERATION(write_file_contents(fn, "testing", TRUE));
|
|
DO_ASSERT(file_exists(fn));
|
|
DeleteFile(fn);
|
|
|
|
DO_ASSERT(file_exists(L"C:\\"));
|
|
DO_ASSERT(!file_exists(L"%%RY:\\\\zZsduf"));
|
|
}
|
|
|
|
/* mkdir_with_parents / delete_tree */
|
|
{
|
|
delete_tree(L"__foo_test");
|
|
mkdir_with_parents(L"__foo_test");
|
|
DO_ASSERT(file_exists(L"__foo_test"));
|
|
mkdir_with_parents(L"__foo_test\\foo\\bar\\goo");
|
|
DO_ASSERT(file_exists(L"__foo_test\\foo\\bar\\goo"));
|
|
mkdir_with_parents(L"__foo_test/lib/bar/goo/dood");
|
|
DO_ASSERT(file_exists(L"__foo_test\\lib\\bar\\goo\\dood"));
|
|
CHECKED_OPERATION(delete_tree(L"__foo_test"));
|
|
DO_ASSERT(!file_exists(L"__foo_test"));
|
|
DO_ASSERT(!file_exists(L"__foo_test\\foo\\bar\\goo"));
|
|
DO_ASSERT(!file_exists(L"__foo_test\\lib\\bar\\goo\\dood"));
|
|
}
|
|
|
|
/* setup_installation */
|
|
{
|
|
CHECKED_OPERATION(setup_installation(L"C:\\", TRUE));
|
|
CHECKED_OPERATION(setup_installation(L"C:\\foobarra", FALSE));
|
|
DO_ASSERT_WSTR_EQ(L"C:\\", get_dynamorio_home());
|
|
CHECKED_OPERATION(setup_installation(L"C:\\foobarra", TRUE));
|
|
DO_ASSERT_WSTR_EQ(L"C:\\foobarra", get_dynamorio_home());
|
|
}
|
|
|
|
{
|
|
WCHAR piname[MAX_PATH];
|
|
BOOL bres = using_system32_for_preinject(NULL);
|
|
printf("Using SYSTEM32 for preinject: %s\n", bres ? "TRUE" : "FALSE");
|
|
CHECKED_OPERATION(get_preinject_name(piname, MAX_PATH));
|
|
printf("Preinject name: %S\n", piname);
|
|
}
|
|
|
|
printf("All Test Passed\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|