rizin/librz/util/sys.c

2232 lines
54 KiB
C

// SPDX-FileCopyrightText: 2009-2020 pancake <pancake@nopcode.org>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_util.h>
#include <rz_userconf.h>
#include <stdlib.h>
#include <string.h>
#if defined(__NetBSD__)
#include <sys/param.h>
#include <sys/sysctl.h>
#endif /* defined(__NetBSD__) */
#if defined(__FreeBSD__)
#include <sys/param.h>
#include <sys/sysctl.h>
#endif /* defined(__FreeBSD__) */
#if defined(__DragonFly__)
#include <sys/param.h>
#include <sys/sysctl.h>
#endif /* defined(__DragonFly__) */
#if defined(__OpenBSD__)
#include <sys/sysctl.h>
#include <sys/stat.h>
#endif /* defined(__OpenBSD__) */
#if defined(__HAIKU__)
#include <kernel/image.h>
#include <sys/param.h>
#endif /* defined(__HAIKU__) */
#include <sys/types.h>
#include <rz_types.h>
#include <rz_util.h>
#include <rz_lib.h>
static char **env = NULL;
#if HAVE_DECL_ADDR_NO_RANDOMIZE
#include <sys/personality.h>
#endif /* HAVE_DECL_ADDR_NO_RANDOMIZE */
#if defined(__FreeBSD__) || defined(__DragonFly__)
#if HAVE_DECL_PROCCTL_ASLR_CTL
#include <sys/procctl.h>
#endif /* HAVE_DECL_PROCCTL_ASLR_CTL */
#include <sys/sysctl.h>
#endif /* defined(__FreeBSD__) || defined(__DragonFly__) */
#if HAVE_BACKTRACE
#include <execinfo.h>
#endif /* HAVE_BACKTRACE */
#if __APPLE__
#include <errno.h>
#if HAVE_ENVIRON
#include <execinfo.h>
#endif
// iOS don't have this we can't hardcode
// #include <crt_externs.h>
extern char ***_NSGetEnviron(void);
#ifndef PROC_PIDPATHINFO_MAXSIZE
#define PROC_PIDPATHINFO_MAXSIZE 1024
int proc_pidpath(int pid, void *buffer, ut32 buffersize);
// # include <libproc.h>
#endif /* PROC_PIDPATHINFO_MAXSIZE */
#endif /* __APPLE__ */
#if __UNIX__
#include <sys/utsname.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <errno.h>
#include <signal.h>
extern char **environ;
#ifdef __HAIKU__
#define Sleep sleep
#endif /* __HAIKU__ */
#endif /* __UNIX__ */
#if __WINDOWS__
#include <io.h>
#include <rz_windows.h>
#include <VersionHelpers.h>
#include <signal.h>
#define TMP_BUFSIZE 4096
#ifdef _MSC_VER
#include <psapi.h>
#include <dbghelp.h>
#include <process.h> // to allow getpid under windows msvc compilation
#include <direct.h> // to allow getcwd under windows msvc compilation
#endif /* _MSC_VER */
typedef BOOL(WINAPI *SetProcessMitigationPolicy_t)(
PROCESS_MITIGATION_POLICY mitigation_policy,
PVOID buffer,
SIZE_T length);
typedef BOOL(WINAPI *GetProcessMitigationPolicy_t)(
HANDLE h_process,
PROCESS_MITIGATION_POLICY mitigation_policy,
PVOID buffer,
SIZE_T length);
#endif /* __WINDOWS__ */
/* For "openpty" family of funtcions */
#if HAVE_OPENPTY && HAVE_FORKPTY && HAVE_LOGIN_TTY
#if defined(__APPLE__) || defined(__NetBSD__) || defined(__OpenBSD__)
#include <util.h>
#elif defined(__FreeBSD__) || defined(__DragonFly__)
#include <libutil.h>
#else
#include <pty.h>
#include <utmp.h>
#endif /* defined(__APPLE__) || defined(__NetBSD__) || defined(__OpenBSD__) */
#endif /* HAVE_OPENPTY && HAVE_FORKPTY && HAVE_LOGIN_TTY */
RZ_LIB_VERSION(rz_util);
#ifdef __x86_64__
#ifdef _MSC_VER
#define RZ_SYS_ASM_START_ROP() \
RZ_LOG_ERROR("rz_sys_run_rop: Unsupported architecture\n");
#else
#define RZ_SYS_ASM_START_ROP() \
__asm__ __volatile__("leaq %0, %%rsp; ret" \
: \
: "m"(*bufptr));
#endif
#elif __i386__
#ifdef _MSC_VER
#define RZ_SYS_ASM_START_ROP() \
__asm { \
__asm lea esp, bufptr\
__asm ret \
}
#else
#define RZ_SYS_ASM_START_ROP() \
__asm__ __volatile__("leal %0, %%esp; ret" \
: \
: "m"(*bufptr));
#endif
#else
#define RZ_SYS_ASM_START_ROP() \
RZ_LOG_ERROR("rz_sys_run_rop: Unsupported architecture\n");
#endif
static const struct {
const char *name;
ut64 bit;
} arch_bit_array[] = {
{ "x86", RZ_SYS_ARCH_X86 },
{ "arm", RZ_SYS_ARCH_ARM },
{ "ppc", RZ_SYS_ARCH_PPC },
{ "m68k", RZ_SYS_ARCH_M68K },
{ "java", RZ_SYS_ARCH_JAVA },
{ "mips", RZ_SYS_ARCH_MIPS },
{ "sparc", RZ_SYS_ARCH_SPARC },
{ "xap", RZ_SYS_ARCH_XAP },
{ "tms320", RZ_SYS_ARCH_TMS320 },
{ "msil", RZ_SYS_ARCH_MSIL },
{ "objd", RZ_SYS_ARCH_OBJD },
{ "bf", RZ_SYS_ARCH_BF },
{ "sh", RZ_SYS_ARCH_SH },
{ "avr", RZ_SYS_ARCH_AVR },
{ "dalvik", RZ_SYS_ARCH_DALVIK },
{ "z80", RZ_SYS_ARCH_Z80 },
{ "arc", RZ_SYS_ARCH_ARC },
{ "i8080", RZ_SYS_ARCH_I8080 },
{ "rar", RZ_SYS_ARCH_RAR },
{ "lm32", RZ_SYS_ARCH_LM32 },
{ "v850", RZ_SYS_ARCH_V850 },
{ "tricore", RZ_SYS_ARCH_TRICORE },
};
#if __WINDOWS__
static char *sys_windows_error_msg(DWORD dw) {
LPTSTR lpMsgBuf;
if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf,
0, NULL)) {
char *message = rz_sys_conv_win_to_utf8(lpMsgBuf);
LocalFree(lpMsgBuf);
rz_str_trim_tail(message);
return message;
}
return NULL;
}
#endif /* __WINDOWS__ */
#if HAVE_SIGACTION
RZ_API int rz_sys_sigaction(int *sig, void (*handler)(int)) {
struct sigaction sigact = {};
int ret, i;
if (!sig) {
return -EINVAL;
}
sigact.sa_handler = handler;
sigemptyset(&sigact.sa_mask);
for (i = 0; sig[i] != 0; i++) {
sigaddset(&sigact.sa_mask, sig[i]);
}
for (i = 0; sig[i] != 0; i++) {
ret = sigaction(sig[i], &sigact, NULL);
if (ret) {
RZ_LOG_ERROR("Failed to set signal handler for signal '%d': %s\n", sig[i], strerror(errno));
return ret;
}
}
return 0;
}
#else
RZ_API int rz_sys_sigaction(int *sig, void (*handler)(int)) {
if (!sig) {
return -EINVAL;
}
size_t i;
for (i = 0; sig[i] != 0; i++) {
void (*ret)(int) = signal(sig[i], handler);
if (ret == SIG_ERR) {
RZ_LOG_ERROR("Failed to set signal handler for signal '%d': %s\n", sig[i], strerror(errno));
return -1;
}
}
return 0;
}
#endif
RZ_API int rz_sys_signal(int sig, void (*handler)(int)) {
int s[2] = { sig, 0 };
return rz_sys_sigaction(s, handler);
}
RZ_API void rz_sys_exit(int status, bool nocleanup) {
if (nocleanup) {
_exit(status);
} else {
exit(status);
}
}
#if __WINDOWS__
static HANDLE sys_opendir(const char *path, WIN32_FIND_DATAW *entry) {
rz_return_val_if_fail(path, NULL);
wchar_t dir[MAX_PATH];
wchar_t *wcpath = 0;
static bool win_ver_initialized = false;
static bool is_win_7_or_greater = false;
if (!win_ver_initialized) {
is_win_7_or_greater = IsWindows7OrGreater();
win_ver_initialized = true;
}
if (!(wcpath = rz_utf8_to_utf16(path))) {
return NULL;
}
swprintf(dir, MAX_PATH, L"%ls\\*.*", wcpath);
free(wcpath);
return FindFirstFileExW(dir, is_win_7_or_greater ? FindExInfoBasic : FindExInfoStandard, entry, FindExSearchNameMatch, NULL, 0);
}
#else
static DIR *sys_opendir(const char *path) {
rz_return_val_if_fail(path, NULL);
return opendir(path);
}
#endif
RZ_API RzList /*<char *>*/ *rz_sys_dir(const char *path) {
RzList *list = NULL;
#if __WINDOWS__
WIN32_FIND_DATAW entry;
char *cfname;
HANDLE fh = sys_opendir(path, &entry);
if (fh == INVALID_HANDLE_VALUE) {
// IFDGB eprintf ("Cannot open directory %ls\n", wcpath);
return list;
}
list = rz_list_newf(free);
if (list) {
do {
if ((cfname = rz_utf16_to_utf8(entry.cFileName))) {
rz_list_append(list, cfname);
}
} while (FindNextFileW(fh, &entry));
}
FindClose(fh);
#else
struct dirent *entry;
DIR *dir = sys_opendir(path);
if (dir) {
list = rz_list_new();
if (list) {
list->free = free;
while ((entry = readdir(dir))) {
rz_list_append(list, rz_str_dup(entry->d_name));
}
}
closedir(dir);
}
#endif
return list;
}
RZ_API char *rz_sys_cmd_strf(const char *fmt, ...) {
char *ret, cmd[4096];
va_list ap;
va_start(ap, fmt);
vsnprintf(cmd, sizeof(cmd), fmt, ap);
ret = rz_sys_cmd_str(cmd, NULL, NULL);
va_end(ap);
return ret;
}
/**
* \brief Print the backtrace at the point this function is called from.
*/
RZ_API void rz_sys_backtrace(void) {
#if HAVE_BACKTRACE
void *array[10];
size_t size = backtrace(array, 10);
eprintf("Backtrace %zd stack frames.\n", size);
backtrace_symbols_fd(array, size, 2);
#elif __APPLE__
void **fp = (void **)__builtin_frame_address(0);
void *saved_pc = __builtin_return_address(0);
void *saved_fp = __builtin_frame_address(1);
int depth = 0;
eprintf("[%d] pc == %p fp == %p\n", depth++, saved_pc, saved_fp);
fp = saved_fp;
while (fp) {
saved_fp = *fp;
fp = saved_fp;
if (!*fp) {
break;
}
saved_pc = *(fp + 2);
eprintf("[%d] pc == %p fp == %p\n", depth++, saved_pc, saved_fp);
}
#else
#ifdef _MSC_VER
SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);
HANDLE process = GetCurrentProcess();
if (!SymInitialize(process, NULL, TRUE)) {
eprintf("SymInitialize() failed (%d), no backtrace will be generated\n", GetLastError());
return;
}
void *stack[64];
ut16 frame_count = CaptureStackBackTrace(0, 64, stack, NULL);
eprintf("Tracing %" PFMT32u " stack frames:\n", frame_count);
ut8 *buffer = malloc(sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR));
if (!buffer) {
eprintf("malloc failed, no backtrace will be generated\n");
SymCleanup(process);
return;
}
SYMBOL_INFO *symbol = (SYMBOL_INFO *)buffer;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
symbol->MaxNameLen = MAX_SYM_NAME;
for (ut16 i = 0; i < frame_count; i++) {
ut64 offset_from_sym = 0;
ut32 line_displacement = 0;
IMAGEHLP_LINE64 line = { .SizeOfStruct = sizeof(IMAGEHLP_LINE64) };
if (!SymFromAddr(process, (utptr)(stack[i]), &offset_from_sym, symbol)) {
// Print only symbol name + pc/rip address
eprintf(" [%" PFMT32u "]: 0x%" PFMT64x "\n", i, (ut64)(stack[i]));
continue;
}
if (SymGetLineFromAddr64(process, (utptr)(stack[i]), &line_displacement, &line)) {
// Source file and line details available
eprintf(" [%" PFMT32u "]: %s() at %s:%" PFMT32u "\n", i, symbol->Name, line.FileName, line.LineNumber);
} else {
// Print only symbol name + offset from symbol
eprintf(" [%" PFMT32u "]: %s()+0x%" PFMT64x "\n", i, symbol->Name, offset_from_sym);
}
}
free(buffer);
SymCleanup(process);
#else
#warning TODO: rz_sys_backtrace : unimplemented
#endif
#endif
}
/**
* \brief Sleep for \p secs seconds
*/
RZ_API int rz_sys_sleep(int secs) {
if (secs < 1) {
return 0;
}
#if HAVE_CLOCK_NANOSLEEP && defined(CLOCK_MONOTONIC)
struct timespec rqtp;
rqtp.tv_sec = secs;
rqtp.tv_nsec = 0;
return clock_nanosleep(CLOCK_MONOTONIC, 0, &rqtp, NULL);
#elif __UNIX__
return sleep(secs);
#else
Sleep(secs * 1000); // W32
return 0;
#endif
}
/**
* \brief Sleep for \p usecs microseconds
*/
RZ_API int rz_sys_usleep(int usecs) {
if (usecs < 1) {
return 0;
}
#if HAVE_CLOCK_NANOSLEEP && defined(CLOCK_MONOTONIC)
struct timespec rqtp;
rqtp.tv_sec = usecs / 1000000;
rqtp.tv_nsec = (usecs - (rqtp.tv_sec * 1000000)) * 1000;
return clock_nanosleep(CLOCK_MONOTONIC, 0, &rqtp, NULL);
#elif __UNIX__
#if defined(__GLIBC__) && defined(__GLIBC_MINOR__) && (__GLIBC__ <= 2) && (__GLIBC_MINOR__ <= 2)
// Old versions of GNU libc return void for usleep
usleep(usecs);
return 0;
#else
return usleep(usecs);
#endif
#else
// w32 api uses milliseconds
usecs /= 1000;
Sleep(usecs); // W32
return 0;
#endif
}
/**
* \brief Clean all environment variables in the calling process.
*
* Please note that environment variables should not be used to store sensitive
* info as they might be kept elsewhere and there is no access control over that
* data.
*/
RZ_API int rz_sys_clearenv(void) {
#if __UNIX__
#if __APPLE__ && !HAVE_ENVIRON
/* do nothing */
if (!env) {
rz_sys_env_init();
return 0;
}
char **e = env;
while (*e) {
*e++ = NULL;
}
#else
if (!environ) {
return 0;
}
while (*environ) {
*environ++ = NULL;
}
#endif
return 0;
#else
#ifdef __WINDOWS__
LPWCH env = GetEnvironmentStringsW();
LPWCH var = env;
while (*var) {
wchar_t *eq = wcschr(var, L'=');
if (!eq) {
FreeEnvironmentStringsW(env);
return -1;
}
const size_t len = eq - var;
if (!len) {
var += wcslen(var) + 1;
continue;
}
wchar_t *v = RZ_NEWS0(wchar_t, len + 1);
if (!v) {
return -1;
}
wcsncpy(v, var, len);
if (_wputenv_s(v, L"")) {
free(v);
break;
}
free(v);
var += wcslen(var) + 1;
}
FreeEnvironmentStringsW(env);
#else
#warning rz_sys_clearenv : unimplemented for this platform
#endif
return 0;
#endif
}
/**
* \brief Set an environment variable in the calling process
*/
RZ_API int rz_sys_setenv(const char *key, const char *value) {
if (!key) {
return 0;
}
#if __UNIX__
if (!value) {
unsetenv(key);
return 0;
}
return setenv(key, value, 1);
#elif __WINDOWS__
LPWSTR key_ = rz_utf8_to_utf16(key);
LPWSTR value_ = value ? rz_utf8_to_utf16(value) : L"";
bool ret = !_wputenv_s(key_, value_);
free(key_);
if (value) {
free(value_);
}
return ret ? 0 : -1;
#else
#warning rz_sys_setenv : unimplemented for this platform
return 0;
#endif
}
/**
* \brief Get the value of an environment variable named \p key or NULL if none exists.
*/
RZ_API char *rz_sys_getenv(const char *key) {
#if __WINDOWS__
if (!key) {
return NULL;
}
wchar_t *val;
wchar_t *wkey = rz_utf8_to_utf16(key);
if (_wdupenv_s(&val, NULL, wkey) || !val) {
free(wkey);
return NULL;
}
free(wkey);
char *ret = rz_utf16_to_utf8(val);
free(val);
return ret;
#else
char *b;
if (!key) {
return NULL;
}
b = getenv(key);
return rz_str_dup(b);
#endif
}
/**
* \brief Return true if the environment variable has the value 1, false otherwise
*/
RZ_API bool rz_sys_getenv_asbool(const char *key) {
char *env = rz_sys_getenv(key);
const bool res = (env && *env == '1');
free(env);
return res;
}
/**
* \brief Get current working directory
*/
RZ_API char *rz_sys_getdir(void) {
#if __WINDOWS__
return _getcwd(NULL, 0);
#else
return getcwd(NULL, 0);
#endif
}
/**
* \brief Change current directory to \p s, taking care of home expansion ~.
*/
RZ_API bool rz_sys_chdir(RZ_NONNULL const char *s) {
rz_return_val_if_fail(s, false);
char *homepath = rz_path_home_expand(s);
if (homepath) {
int ret = chdir(homepath);
free(homepath);
return ret == 0;
}
return chdir(s) == 0;
}
/**
* \brief Enable or disable ASLR for the calling process
*
* Host is Linux:
*
* We can disable ASLR using the personlity flags (does not require root) or
* system-wide by setting /proc/sys/kernel/randomize_va_space to 0.
* If we want to enable it we must write 1 or 2 into /proc/sys/kernel/randomize_va_space
* ASLR support was added in linux 2.6 (year 2005)
*
* Host is NetBSD:
*
* We can enable/disable ASLR by calling sysctlbyname("security.pax.aslr.enabled").
* May require root/super-user priviledges.
*
* Host is DragonFly:
*
* We can enable/disable ASLR by calling sysctlbyname("vm.randomize_mmap")
* May require root/super-user priviledges.
*
* Host is FreeBSD (since version 13.0):
*
* We can enable/disable ASLR by calling sysctlbyname("kern.elf32.aslr.enable") and
* sysctlbyname("kern.elf64.aslr.enable")
* May require root/super-user priviledges.
*
* Host is Windows:
*
* We can only enable ASLR and not disable it.
* To enable it we just need to initialize PROCESS_MITIGATION_ASLR_POLICY by setting to true
* EnableBottomUpRandomization, EnableForceRelocateImages and EnableHighEntropy, and then call
* SetProcessMitigationPolicy(ProcessASLRPolicy).
* https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setprocessmitigationpolicy
*
* This feature is available since _WIN32_WINNT >= 0x0602 (Windows 8, Windows Server 2012)
* It is possible to manually disable ASLR on a binary by removing the PE flags called DLL Characteristics:
* - IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
* - IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA
* or as admin from PowerShell by running `Set-ProcessMitigation -Name file.exe -Disable ForceRelocateImages`
*
* Host is Apple:
*
* We can enable/disable ASRL when we spawn a process by adding the flag called _POSIX_SPAWN_DISABLE_ASLR (0x0100) to
* the flag field of posix_spawnattr_setflags.
* This was reverse engineered and documented in: https://opensource.apple.com/source/gdb/gdb-2831/src/gdb/macosx/macosx-nat-inferior.c.auto.html
*
* It is possible to manually disable ASLR on a binary by removing the MH_PIE flag from the mach0 header.
* https://web.archive.org/web/20140906073648/http://src.chromium.org:80/svn/trunk/src/build/mac/change_mach_o_flags.py
*
* On Snow Leopard this also can be achieved by setting the environment variable DYLD_NO_PIE to 1 (export DYLD_NO_PIE=1)
*/
RZ_API bool rz_sys_aslr(bool enable) {
#if __linux__
#if HAVE_DECL_ADDR_NO_RANDOMIZE && !__ANDROID__
if (!enable) {
/*
* https://manpages.debian.org/testing/manpages-dev/personality.2.en.html
* Disable ASLR using personality flags and avoids to change the OS
* value system-wide, which may require to have root priviledges.
*/
if (personality(ADDR_NO_RANDOMIZE) < 0) {
RZ_LOG_ERROR("Cannot set personality as ADDR_NO_RANDOMIZE\n");
return false;
}
return true;
}
#endif /* HAVE_DECL_ADDR_NO_RANDOMIZE && !__ANDROID__ */
const char *proc_file = "/proc/sys/kernel/randomize_va_space";
char *contents = rz_file_slurp(proc_file, NULL);
// best effort at reading current value
if (RZ_STR_ISNOTEMPTY(contents)) {
if (enable && (contents[0] == '1' || contents[0] == '2')) {
// already enabled.
return true;
} else if (!enable && contents[0] == '0') {
// already disabled.
return true;
}
}
free(contents);
char buf[8] = { 0 };
snprintf(buf, sizeof(buf), "%d\n", enable ? 2 : 0);
int fd = rz_sys_open(proc_file, O_WRONLY, 0644);
if (fd == -1) {
RZ_LOG_ERROR("Cannot open /proc/sys/kernel/randomize_va_space\n");
return false;
}
int n_bytes = write(fd, (ut8 *)buf, sizeof(buf));
close(fd);
if (n_bytes != sizeof(buf)) {
RZ_LOG_ERROR("Cannot write %s to /proc/sys/kernel/randomize_va_space\n", buf);
return false;
}
#if __FreeBSD__ || __NetBSD__ || __DragonFly__
int value = enable ? 1 : 0;
size_t vlen = sizeof(value);
#if __NetBSD__
const char *ctl_name = "security.pax.aslr.enabled";
#elif __DragonFly__
const char *ctl_name = "vm.randomize_mmap";
#elif __FreeBSD__ && __FreeBSD_version >= 1300000
const char *ctl_name = "kern.elf32.aslr.enable";
#if __LP64__
if (sysctlbyname("kern.elf64.aslr.enable", NULL, 0, &value, vlen) == -1) {
RZ_LOG_ERROR("Cannot set kern.elf64.aslr.enable\n");
return false;
}
#endif /* __LP64__ */
#else /* __FreeBSD__ */
const char *ctl_name = NULL;
RZ_LOG_DEBUG("ASLR support is not supported on this FreeBSD version\n");
#endif /* __FreeBSD__ && __FreeBSD_version >= 1300000 */
// allow to fail.
if (ctl_name && sysctlbyname(ctl_name, NULL, 0, &value, vlen) == -1) {
RZ_LOG_ERROR("Cannot set %s\n", ctl_name);
return false;
}
#if HAVE_DECL_PROCCTL_ASLR_CTL
int set_aslr = enable ? PROC_ASLR_FORCE_ENABLE : PROC_ASLR_FORCE_DISABLE;
if (procctl(P_PID, getpid(), PROC_ASLR_CTL, &set_aslr) == -1) {
return false;
}
#endif /* HAVE_DECL_PROCCTL_ASLR_CTL */
#endif /* __FreeBSD__ || __NetBSD__ || __DragonFly__ */
#elif __WINDOWS__
/*
* https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-process_mitigation_aslr_policy
* SetProcessMitigationPolicy allows to enable or disable ASLR on windows per process, but
* ASLR mitigation policies cannot be made less restrictive after they have been applied.
* so a process that has already ASLR enable, cannot disable ASLR programmatically.
* ASLR is also set in the PE flags called DLL Characteristics:
* - IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
* - IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA
*/
HMODULE module = GetModuleHandleA("kernel32.dll");
if (!module) {
RZ_LOG_ERROR("Cannot get kernel32.dll module pointer\n");
return false;
}
SetProcessMitigationPolicy_t setProcessMitigationPolicy = (SetProcessMitigationPolicy_t)GetProcAddress(module, "SetProcessMitigationPolicy");
GetProcessMitigationPolicy_t getProcessMitigationPolicy = (GetProcessMitigationPolicy_t)GetProcAddress(module, "GetProcessMitigationPolicy");
if (!setProcessMitigationPolicy || !getProcessMitigationPolicy) {
RZ_LOG_INFO("Get/SetProcessMitigationPolicy is not supported in this kernel32.dll, thus ASLR cannot be handled\n");
return true;
}
PROCESS_MITIGATION_ASLR_POLICY policy = { 0 };
HANDLE handle = GetCurrentProcess();
if (!getProcessMitigationPolicy(handle, ProcessASLRPolicy, &policy, sizeof(policy))) {
DWORD dw = GetLastError();
char *err = sys_windows_error_msg(dw);
RZ_LOG_ERROR("Cannot get ProcessASLRPolicy: %s (0x%x)\n", err, dw);
free(err);
return false;
}
if (enable && policy.EnableForceRelocateImages) {
// ASLR is already enabled.
RZ_LOG_INFO("ASLR is already enabled in the current process\n");
return true;
} else if (!enable && !policy.EnableForceRelocateImages) {
// ASLR is already disabled.
RZ_LOG_INFO("ASLR is already disabled in the current process\n");
return true;
} else if (!enable && policy.EnableForceRelocateImages) {
RZ_LOG_ERROR("On Windows, ASLR mitigation policies cannot be made less restrictive if they are already enabled.\n");
RZ_LOG_ERROR("It is possible to disable ASLR by modifying the PE header and removing the following flags:\n");
RZ_LOG_ERROR("- IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE\n");
RZ_LOG_ERROR("- IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA.\n");
RZ_LOG_ERROR("Or as Administrator from PowerShell by running: `Set-ProcessMitigation -Name file.exe -Disable ForceRelocateImages`\n");
return true;
}
memset(&policy, 0, sizeof(policy));
if (enable) {
policy.EnableBottomUpRandomization = 1;
policy.EnableForceRelocateImages = 1;
policy.EnableHighEntropy = 1;
policy.DisallowStrippedImages = 0;
}
if (!setProcessMitigationPolicy(ProcessASLRPolicy, &policy, sizeof(policy))) {
DWORD dw = GetLastError();
char *err = sys_windows_error_msg(dw);
RZ_LOG_ERROR("Cannot set ProcessASLRPolicy: %s (0x%x)\n", err, dw);
free(err);
return false;
}
#elif __APPLE__
/**
* Apple supports this by using the spawn api.
* https://opensource.apple.com/source/gdb/gdb-2831/src/gdb/macosx/macosx-nat-inferior.c.auto.html
* so we always return true.
*/
#else /* any other platform */
/* not supported on the platform */
RZ_LOG_INFO("ASLR support is not available on this platform.\n");
#endif /* __linux__ */
return true;
}
RZ_API int rz_sys_cmd_str_full(const char *cmd, const char *input, char **output, int *len, char **sterr) {
int argc;
char **argv = rz_str_argv(cmd, &argc);
if (!argv) {
return false;
}
RzSubprocessOpt opts = {
.file = argv[0],
.args = (const char **)&argv[1],
.args_size = argc - 1,
.envvars = NULL,
.envvals = NULL,
.env_size = 0,
.stdin_pipe = input ? RZ_SUBPROCESS_PIPE_CREATE : RZ_SUBPROCESS_PIPE_NONE,
.stdout_pipe = output ? RZ_SUBPROCESS_PIPE_CREATE : RZ_SUBPROCESS_PIPE_NONE,
.stderr_pipe = sterr ? RZ_SUBPROCESS_PIPE_CREATE : RZ_SUBPROCESS_PIPE_NONE,
.pty = NULL,
.make_raw = false,
};
if (!rz_subprocess_init()) {
RZ_LOG_ERROR("Failed to initialize subprocess\n");
return false;
}
RzSubprocess *subprocess = rz_subprocess_start_opt(&opts);
if (!subprocess) {
rz_subprocess_fini();
return false;
}
if (input) {
rz_subprocess_stdin_write(subprocess, (ut8 *)input, strlen(input) + 1);
}
rz_subprocess_wait(subprocess, UT64_MAX);
if (output) {
*output = (char *)rz_subprocess_out(subprocess, len);
}
if (sterr) {
*sterr = (char *)rz_subprocess_err(subprocess, NULL);
}
rz_subprocess_free(subprocess);
rz_subprocess_fini();
rz_str_argv_free(argv);
return true;
}
RZ_API int rz_sys_cmdf(const char *fmt, ...) {
int ret;
char cmd[4096];
va_list ap;
va_start(ap, fmt);
vsnprintf(cmd, sizeof(cmd), fmt, ap);
ret = rz_sys_system(cmd);
va_end(ap);
return ret;
}
RZ_API char *rz_sys_cmd_str(const char *cmd, const char *input, int *len) {
char *output = NULL;
if (rz_sys_cmd_str_full(cmd, input, &output, len, NULL)) {
return output;
}
free(output);
return NULL;
}
RZ_API bool rz_sys_mkdir(const char *dir) {
bool ret;
#if __WINDOWS__
wchar_t *dir_utf16 = rz_utf8_to_utf16(dir);
ret = _wmkdir(dir_utf16) != -1;
free(dir_utf16);
#else
ret = mkdir(dir, 0755) != -1;
#endif
return ret;
}
RZ_API bool rz_sys_mkdirp(const char *dir) {
bool ret = true;
char slash = RZ_SYS_DIR[0];
char *path = rz_str_dup(dir), *ptr = path;
if (!path) {
RZ_LOG_ERROR("rz_sys_mkdirp: Unable to allocate memory\n");
return false;
}
if (*ptr == slash) {
ptr++;
}
#if __WINDOWS__
{
char *p = strstr(ptr, ":\\");
if (p) {
ptr = p + 3;
}
}
#endif
for (;;) {
// find next slash
for (; *ptr; ptr++) {
if (*ptr == '/' || *ptr == '\\') {
slash = *ptr;
break;
}
}
if (!*ptr) {
break;
}
*ptr = 0;
if (!rz_sys_mkdir(path) && rz_sys_mkdir_failed()) {
RZ_LOG_ERROR("rz_sys_mkdirp: fail '%s' of '%s'\n", path, dir);
free(path);
return false;
}
*ptr = slash;
ptr++;
}
if (!rz_sys_mkdir(path) && rz_sys_mkdir_failed()) {
ret = false;
}
free(path);
return ret;
}
RZ_API void rz_sys_perror_str(const char *fun) {
#if __UNIX__
#pragma push_macro("perror")
#undef perror
perror(fun);
#pragma pop_macro("perror")
#elif __WINDOWS__
DWORD dw = GetLastError();
char *err = sys_windows_error_msg(dw);
if (err) {
eprintf("%s: (%#lx) %s\n", fun, dw, err);
free(err);
} else {
eprintf("%s\n", fun);
}
#endif /* __WINDOWS__ */
}
RZ_API int rz_sys_arch_id(const char *arch) {
for (size_t i = 0; i < RZ_ARRAY_SIZE(arch_bit_array); i++) {
if (!strcmp(arch, arch_bit_array[i].name)) {
return arch_bit_array[i].bit;
}
}
return 0;
}
RZ_API const char *rz_sys_arch_str(int arch) {
for (size_t i = 0; i < RZ_ARRAY_SIZE(arch_bit_array); i++) {
if (arch == arch_bit_array[i].bit) {
return arch_bit_array[i].name;
}
}
return "none";
}
#define USE_FORK 0
RZ_API int rz_sys_run(const ut8 *buf, int len) {
const int sz = 4096;
int pdelta, ret, (*cb)();
#if USE_FORK
int st, pid;
#endif
// TODO: define RZ_SYS_ALIGN_FORWARD in rz_util.h
ut8 *ptr, *p = malloc((sz + len) << 1);
ptr = p;
pdelta = ((size_t)(p)) & (4096 - 1);
if (pdelta) {
ptr += (4096 - pdelta);
}
if (!ptr || !buf) {
RZ_LOG_ERROR("rz_sys_run: Cannot run empty buffer\n");
free(p);
return false;
}
memcpy(ptr, buf, len);
rz_mem_protect(ptr, sz, "rx");
// rz_mem_protect (ptr, sz, "rwx"); // try, ignore if fail
cb = (int (*)())ptr;
#if USE_FORK
#if __UNIX__
pid = rz_sys_fork();
#else
pid = -1;
#endif
if (pid < 0) {
return cb();
}
if (!pid) {
ret = cb();
exit(ret);
return ret;
}
st = 0;
waitpid(pid, &st, 0);
if (WIFSIGNALED(st)) {
int num = WTERMSIG(st);
RZ_LOG_WARN("Got signal %d\n", num);
ret = num;
} else {
ret = WEXITSTATUS(st);
}
#else
ret = (*cb)();
#endif
free(p);
return ret;
}
RZ_API int rz_sys_run_rop(const ut8 *buf, int len) {
#if USE_FORK
int st;
#endif
// TODO: define RZ_SYS_ALIGN_FORWARD in rz_util.h
ut8 *bufptr = malloc(len);
if (!bufptr) {
RZ_LOG_ERROR("rz_sys_run_rop: Cannot allocate buffer\n");
return false;
}
if (!buf) {
RZ_LOG_ERROR("rz_sys_run_rop: Cannot execute empty rop chain\n");
free(bufptr);
return false;
}
memcpy(bufptr, buf, len);
#if USE_FORK
#if __UNIX__
pid_t pid = rz_sys_fork();
#else
pid = -1;
#endif
if (pid < 0) {
RZ_SYS_ASM_START_ROP();
} else {
RZ_SYS_ASM_START_ROP();
exit(0);
return 0;
}
st = 0;
if (waitpid(pid, &st, 0) == -1) {
RZ_LOG_ERROR("rz_sys_run_rop: waitpid failed\n");
free(bufptr);
return -1;
}
if (WIFSIGNALED(st)) {
int num = WTERMSIG(st);
RZ_LOG_WARN("Got signal %d\n", num);
ret = num;
} else {
ret = WEXITSTATUS(st);
}
#else
RZ_SYS_ASM_START_ROP();
#endif
free(bufptr);
return 0;
}
RZ_API bool rz_is_heap(void *p) {
void *q = malloc(8);
ut64 mask = UT64_MAX;
mask >>= 16;
mask <<= 16;
free(q);
return (((ut64)(size_t)p) == mask);
}
RZ_API char *rz_sys_pid_to_path(int pid) {
#if __WINDOWS__
// TODO: add maximum path length support
HANDLE processHandle;
const DWORD maxlength = MAX_PATH;
WCHAR filename[MAX_PATH];
char *result = NULL;
processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
if (!processHandle) {
RZ_LOG_ERROR("rz_sys_pid_to_path: Cannot open process.\n");
return NULL;
}
DWORD length = GetModuleFileNameExW(processHandle, NULL, filename, maxlength);
if (length == 0) {
// Upon failure fallback to GetProcessImageFileName
length = GetProcessImageFileNameW(processHandle, filename, maxlength);
CloseHandle(processHandle);
if (length == 0) {
RZ_LOG_ERROR("rz_sys_pid_to_path: Error calling GetProcessImageFileName\n");
return NULL;
}
// Convert NT path to win32 path
char *name = rz_utf16_to_utf8(filename);
if (!name) {
RZ_LOG_ERROR("rz_sys_pid_to_path: Error converting to utf8\n");
return NULL;
}
char *tmp = strchr(name + 1, '\\');
if (!tmp) {
free(name);
RZ_LOG_ERROR("rz_sys_pid_to_path: Malformed NT path\n");
return NULL;
}
tmp = strchr(tmp + 1, '\\');
if (!tmp) {
free(name);
RZ_LOG_ERROR("rz_sys_pid_to_path: Malformed NT path\n");
return NULL;
}
length = tmp - name;
tmp = malloc(length + 1);
if (!tmp) {
free(name);
RZ_LOG_ERROR("rz_sys_pid_to_path: Error allocating memory\n");
return NULL;
}
strncpy(tmp, name, length);
tmp[length] = '\0';
WCHAR device[MAX_PATH];
for (WCHAR drv[] = L"A:"; drv[0] <= L'Z'; drv[0]++) {
if (QueryDosDeviceW(drv, device, maxlength) > 0) {
char *dvc = rz_utf16_to_utf8(device);
if (!dvc) {
free(name);
free(tmp);
RZ_LOG_ERROR("rz_sys_pid_to_path: Error converting to utf8\n");
return NULL;
}
if (!strcmp(tmp, dvc)) {
free(tmp);
free(dvc);
char *d = rz_utf16_to_utf8(drv);
if (!d) {
free(name);
RZ_LOG_ERROR("rz_sys_pid_to_path: Error converting to utf8\n");
return NULL;
}
tmp = rz_str_newf("%s%s", d, &name[length]);
free(d);
if (!tmp) {
free(name);
RZ_LOG_ERROR("rz_sys_pid_to_path: Error calling rz_str_newf\n");
return NULL;
}
result = rz_str_dup(tmp);
break;
}
free(dvc);
}
}
free(name);
free(tmp);
} else {
CloseHandle(processHandle);
result = rz_utf16_to_utf8(filename);
}
return result;
#elif __APPLE__
char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
pathbuf[0] = 0;
int ret = proc_pidpath(pid, pathbuf, sizeof(pathbuf));
if (ret <= 0) {
return NULL;
}
return rz_str_dup(pathbuf);
#else
#if __FreeBSD__ || __DragonFly__
char pathbuf[PATH_MAX];
size_t pathbufl = sizeof(pathbuf);
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, pid };
int ret = sysctl(mib, 4, pathbuf, &pathbufl, NULL, 0);
if (ret != 0) {
return NULL;
}
#elif __OpenBSD__
// Taken from https://stackoverflow.com/questions/31494901/how-to-get-the-executable-path-on-openbsd
char pathbuf[PATH_MAX];
int mib[4] = { CTL_KERN, KERN_PROC_ARGS, pid, KERN_PROC_ARGV };
size_t len;
pathbuf[0] = '\0';
int ret = sysctl(mib, 4, NULL, &len, NULL, 0);
if (ret < 0) {
return NULL;
}
char **argv = malloc(len);
ret = sysctl(mib, 4, argv, &len, NULL, 0);
if (ret < 0) {
free(argv);
return NULL;
}
const char *comm = argv[0];
int ok = 0;
if (*comm == '/' || *comm == '.') {
if (!realpath(comm, pathbuf)) {
free(argv);
return NULL;
}
} else {
char *sp;
char *xpath = rz_str_dup(getenv("PATH"));
char *path = strtok_r(xpath, ":", &sp);
struct stat st;
if (!xpath) {
free(argv);
return NULL;
}
while (path) {
snprintf(pathbuf, PATH_MAX, "%s/%s", path, comm);
if (!stat(pathbuf, &st) && (st.st_mode & S_IXUSR)) {
ok = 1;
break;
}
path = strtok_r(NULL, ":", &sp);
}
free(xpath);
}
if (ok) {
char *p = strrchr(pathbuf, '/');
if (p) {
*p = '\0';
}
}
free(argv);
#elif __HAIKU__
char pathbuf[MAXPATHLEN];
int32_t group = 0;
image_info ii;
while (get_next_image_info((team_id)pid, &group, &ii) == B_OK) {
if (ii.type == B_APP_IMAGE) {
break;
}
}
if (ii.type == B_APP_IMAGE) {
rz_str_ncpy(pathbuf, ii.name, MAXPATHLEN);
} else {
pathbuf[0] = '\0';
}
#else
char buf[128], pathbuf[1024];
snprintf(buf, sizeof(buf), "/proc/%d/exe", pid);
int ret = readlink(buf, pathbuf, sizeof(pathbuf) - 1);
if (ret < 1) {
return NULL;
}
pathbuf[ret] = 0;
#endif
return rz_str_dup(pathbuf);
#endif
}
RZ_API void rz_sys_env_init(void) {
char **envp = rz_sys_get_environ();
if (envp) {
env = envp;
}
}
RZ_API char **rz_sys_get_environ(void) {
#if __APPLE__ && !HAVE_ENVIRON
env = *_NSGetEnviron();
#elif HAVE_ENVIRON
env = environ;
#endif
// return environ if available??
if (!env) {
env = rz_sys_dlsym(NULL, "environ");
}
return env;
}
RZ_API void rz_sys_set_environ(char **e) {
env = e;
#if __APPLE__ && !HAVE_ENVIRON
*_NSGetEnviron() = e;
#elif __WINDOWS__
char **oe = e;
rz_sys_clearenv();
while (*e) {
char *var = *e;
char *val = strchr(var, '=');
wchar_t *val_utf16 = NULL;
if (*val) {
*val++ = '\0';
}
rz_sys_setenv(var, val);
free(*e);
e++;
}
free(oe);
env = environ;
#elif HAVE_ENVIRON
environ = e;
#endif
}
RZ_API char *rz_sys_whoami(char *buf) {
char _buf[32];
int pid = getpid();
int hasbuf = (buf) ? 1 : 0;
if (!hasbuf) {
buf = _buf;
}
sprintf(buf, "pid%d", pid);
return hasbuf ? buf : rz_str_dup(buf);
}
RZ_API int rz_sys_getpid(void) {
#if __UNIX__
return getpid();
#elif __WINDOWS__
return GetCurrentProcessId();
#else
#warning rz_sys_getpid not implemented for this platform
return -1;
#endif
}
RZ_API RSysInfo *rz_sys_info(void) {
#if __UNIX__
struct utsname un = { { 0 } };
if (uname(&un) != -1) {
RSysInfo *si = RZ_NEW0(RSysInfo);
if (si) {
si->sysname = rz_str_dup(un.sysname);
si->nodename = rz_str_dup(un.nodename);
si->release = rz_str_dup(un.release);
si->version = rz_str_dup(un.version);
si->machine = rz_str_dup(un.machine);
return si;
}
}
#elif __WINDOWS__
HKEY key;
DWORD type;
DWORD size;
DWORD major;
DWORD minor;
char tmp[256] = { 0 };
RSysInfo *si = RZ_NEW0(RSysInfo);
if (!si) {
return NULL;
}
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0,
KEY_QUERY_VALUE, &key) != ERROR_SUCCESS) {
rz_sys_perror("rz_sys_info/RegOpenKeyExA");
rz_sys_info_free(si);
return NULL;
}
size = sizeof(tmp);
if (RegQueryValueExA(key, "ProductName", NULL, &type,
(LPBYTE)&tmp, &size) != ERROR_SUCCESS ||
type != REG_SZ) {
goto beach;
}
si->sysname = rz_str_dup(tmp);
size = sizeof(major);
if (RegQueryValueExA(key, "CurrentMajorVersionNumber", NULL, &type,
(LPBYTE)&major, &size) != ERROR_SUCCESS ||
type != REG_DWORD) {
goto beach;
}
size = sizeof(minor);
if (RegQueryValueExA(key, "CurrentMinorVersionNumber", NULL, &type,
(LPBYTE)&minor, &size) != ERROR_SUCCESS ||
type != REG_DWORD) {
goto beach;
}
size = sizeof(tmp);
if (RegQueryValueExA(key, "CurrentBuild", NULL, &type,
(LPBYTE)&tmp, &size) != ERROR_SUCCESS ||
type != REG_SZ) {
goto beach;
}
si->version = rz_str_newf("%lu.%lu.%s", major, minor, tmp);
size = sizeof(tmp);
if (RegQueryValueExA(key, "ReleaseId", NULL, &type,
(LPBYTE)tmp, &size) != ERROR_SUCCESS ||
type != REG_SZ) {
goto beach;
}
si->release = rz_str_dup(tmp);
beach:
RegCloseKey(key);
return si;
#endif
return NULL;
}
RZ_API void rz_sys_info_free(RSysInfo *si) {
free(si->sysname);
free(si->nodename);
free(si->release);
free(si->version);
free(si->machine);
free(si);
}
#if __UNIX__ && HAVE_PIPE2
#include <fcntl.h>
#include <unistd.h>
RZ_API int rz_sys_pipe(int pipefd[2], bool close_on_exec) {
return pipe2(pipefd, close_on_exec ? O_CLOEXEC : 0);
}
RZ_API int rz_sys_pipe_close(int fd) {
return close(fd);
}
#elif __UNIX__ && HAVE_PIPE && defined(O_CLOEXEC)
// Use this lock to wraps pipe, close, exec*, system to ensure all pipe file
// descriptors are either created AND set as CLOEXEC or not created at all.
static RzThreadLock *sys_pipe_mutex;
static bool is_child = false;
#ifdef RZ_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA
#pragma RZ_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(sys_pipe_constructor)
#endif
RZ_DEFINE_CONSTRUCTOR(sys_pipe_constructor)
static void sys_pipe_constructor(void) {
sys_pipe_mutex = rz_th_lock_new(true);
}
#ifdef RZ_DEFINE_DESTRUCTOR_NEEDS_PRAGMA
#pragma RZ_DEFINE_DESTRUCTOR_PRAGMA_ARGS(sys_pipe_destructor)
#endif
RZ_DEFINE_DESTRUCTOR(sys_pipe_destructor)
static void sys_pipe_destructor(void) {
rz_th_lock_free(sys_pipe_mutex);
}
static bool set_close_on_exec(int fd) {
int flags = fcntl(fd, F_GETFD);
if (flags == -1) {
return false;
}
flags |= FD_CLOEXEC;
return fcntl(fd, F_SETFD, flags) != -1;
}
static void parent_lock_enter(void) {
if (!is_child) {
rz_th_lock_enter(sys_pipe_mutex);
}
}
static void parent_lock_leave(void) {
if (!is_child) {
rz_th_lock_leave(sys_pipe_mutex);
}
}
/**
* \brief Create a pipe and use O_CLOEXEC flag when \p close_on_exec is true
*
* If \p rz_sys functions are used to exec/system the new process, no race
* condition happen even on systems that don't support atomic creation of pipes
* with O_CLOEXEC.
*/
RZ_API int rz_sys_pipe(int pipefd[2], bool close_on_exec) {
int res = -1;
parent_lock_enter();
if ((res = pipe(pipefd)) == -1) {
perror("pipe");
goto err;
}
if (close_on_exec && (!set_close_on_exec(pipefd[0]) || !set_close_on_exec(pipefd[1]))) {
perror("close-on-exec");
close(pipefd[0]);
close(pipefd[1]);
goto err;
}
err:
parent_lock_leave();
return res;
}
/**
* \brief Close a file descriptor previously created pipe \p rz_sys_pipe
*/
RZ_API int rz_sys_pipe_close(int fd) {
return close(fd);
}
#elif __UNIX__ && HAVE_PIPE
#include <rz_util/ht_uu.h>
static HtUU *fd2close;
// Use this lock to wraps pipe, close, exec*, system to ensure all pipe file
// descriptors are either created AND added to fd2close or not created at all.
static RzThreadLock *sys_pipe_mutex;
static bool is_child = false;
#ifdef RZ_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA
#pragma RZ_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(sys_pipe_constructor)
#endif
RZ_DEFINE_CONSTRUCTOR(sys_pipe_constructor)
static void sys_pipe_constructor(void) {
sys_pipe_mutex = rz_th_lock_new(false);
fd2close = ht_uu_new();
}
#ifdef RZ_DEFINE_DESTRUCTOR_NEEDS_PRAGMA
#pragma RZ_DEFINE_DESTRUCTOR_PRAGMA_ARGS(sys_pipe_destructor)
#endif
RZ_DEFINE_DESTRUCTOR(sys_pipe_destructor)
static void sys_pipe_destructor(void) {
ht_uu_free(fd2close);
rz_th_lock_free(sys_pipe_mutex);
}
static void parent_lock_enter(void) {
if (!is_child) {
rz_th_lock_enter(sys_pipe_mutex);
}
}
static void parent_lock_leave(void) {
if (!is_child) {
rz_th_lock_leave(sys_pipe_mutex);
}
}
static bool set_close_on_exec(int fd, bool close_on_exec) {
bool res = ht_uu_insert(fd2close, fd, close_on_exec);
rz_warn_if_fail(res);
return res;
}
static bool close_on_exec_fd_cb(void *user, const ut64 key, const ut64 val) {
bool close_on_exec = (bool)val;
if (close_on_exec) {
close((int)key);
}
return true;
}
static void close_fds(void) {
ht_uu_foreach(fd2close, close_on_exec_fd_cb, NULL);
}
/**
* \brief Create a pipe and simulates the O_CLOEXEC flag when \p close_on_exec is true
*
* If \p rz_sys functions are used to exec/system a new process, pipes created
* with this function would be closed before executing the new executable,
* making sure these file descriptors don't leak.
*/
RZ_API int rz_sys_pipe(int pipefd[2], bool close_on_exec) {
int res = -1;
parent_lock_enter();
if ((res = pipe(pipefd)) == -1) {
perror("pipe");
goto err;
}
if (!set_close_on_exec(pipefd[0], close_on_exec) || !set_close_on_exec(pipefd[1], close_on_exec)) {
perror("close-on-exec");
close(pipefd[0]);
close(pipefd[1]);
goto err;
}
err:
parent_lock_leave();
return res;
}
/**
* \brief Close a file descriptor previously created pipe \p rz_sys_pipe
*
* This is necessary to ensure that the file descriptor is not "closed again"
* when an \p rz_sys exec/system is executed later.
*/
RZ_API int rz_sys_pipe_close(int fd) {
parent_lock_enter();
bool deleted = ht_uu_delete(fd2close, fd);
rz_warn_if_fail(deleted);
int res = close(fd);
parent_lock_leave();
return res;
}
#elif HAVE_PIPE
RZ_API int rz_sys_pipe(int pipefd[2], bool close_on_exec) {
return pipe(pipefd);
}
RZ_API int rz_sys_pipe_close(int fd) {
return close(fd);
}
#elif __WINDOWS__
RZ_API int rz_sys_pipe(int pipefd[2], bool close_on_exec) {
return _pipe(pipefd, 0x1000, O_TEXT);
}
RZ_API int rz_sys_pipe_close(int fd) {
return _close(fd);
}
#else
RZ_API int rz_sys_pipe(int pipefd[2], bool close_on_exec) {
return -1;
}
RZ_API int rz_sys_pipe_close(int fd) {
return -1;
}
#endif
#if __UNIX__ && HAVE_EXECV && HAVE_PIPE && defined(O_CLOEXEC) && !HAVE_PIPE2
RZ_API int rz_sys_execv(RZ_NONNULL const char *pathname, RZ_NONNULL char *const argv[]) {
rz_return_val_if_fail(RZ_STR_ISNOTEMPTY(pathname) && argv && RZ_STR_ISNOTEMPTY(argv[0]), -1);
parent_lock_enter();
int res = execv(pathname, argv);
parent_lock_leave();
return res;
}
#elif __UNIX__ && HAVE_EXECV && HAVE_PIPE && !HAVE_PIPE2
RZ_API int rz_sys_execv(RZ_NONNULL const char *pathname, RZ_NONNULL char *const argv[]) {
rz_return_val_if_fail(RZ_STR_ISNOTEMPTY(pathname) && argv && RZ_STR_ISNOTEMPTY(argv[0]), -1);
parent_lock_enter();
close_fds();
int res = execv(pathname, argv);
parent_lock_leave();
return res;
}
#elif !HAVE_EXECV
/**
* \brief Executes a program with a given set of arguments.
* This is an implementation of execv on systems that do not have support for this function by using
* RzSubprocess as layer of compatibility.
*
* Beware that argv[0] must be always allocated and should match pathname.
*
* \param pathname The path of the new program to execute
* \param argv The array of pointers to null-terminated strings that represent the argument list available to the new program.
*
* \return Returns -1 on failure otherwise the exit(n) value returned by the executed program.
*/
RZ_API int rz_sys_execv(RZ_NONNULL const char *pathname, RZ_NONNULL char *const argv[]) {
rz_return_val_if_fail(RZ_STR_ISNOTEMPTY(pathname) && argv && RZ_STR_ISNOTEMPTY(argv[0]), -1);
if (!rz_subprocess_init()) {
RZ_LOG_ERROR("Cannot initialize subprocess in execv.\n");
return -1;
}
int argc = 0;
for (int i = 0; argv[i]; ++i) {
argc = i + 1;
}
RzSubprocessOpt opt = {
.file = pathname,
.args = (const char **)&argv[1],
.args_size = argc - 1,
.envvars = NULL,
.envvals = NULL,
.env_size = 0,
.stdin_pipe = RZ_SUBPROCESS_PIPE_NONE,
.stdout_pipe = RZ_SUBPROCESS_PIPE_NONE,
.stderr_pipe = RZ_SUBPROCESS_PIPE_NONE,
.pty = NULL,
.make_raw = false,
};
RzSubprocess *proc = rz_subprocess_start_opt(&opt);
if (!proc) {
RZ_LOG_ERROR("Cannot start subprocess in execv.\n");
rz_subprocess_fini();
return -1;
}
rz_subprocess_wait(proc, UT64_MAX);
int ret = rz_subprocess_ret(proc);
rz_subprocess_free(proc);
rz_subprocess_fini();
return ret;
}
#endif
#if __UNIX__ && HAVE_EXECVE && HAVE_PIPE && defined(O_CLOEXEC) && !HAVE_PIPE2
RZ_API int rz_sys_execve(const char *pathname, char *const argv[], char *const envp[]) {
parent_lock_enter();
int res = execve(pathname, argv, envp);
parent_lock_leave();
return res;
}
#elif __UNIX__ && HAVE_EXECVE && HAVE_PIPE && !HAVE_PIPE2
RZ_API int rz_sys_execve(const char *pathname, char *const argv[], char *const envp[]) {
parent_lock_enter();
close_fds();
int res = execve(pathname, argv, envp);
parent_lock_leave();
return res;
}
#elif !HAVE_EXECVE
RZ_API int rz_sys_execve(const char *pathname, char *const argv[], char *const envp[]) {
return -1;
}
#endif
#if __UNIX__ && HAVE_EXECVP && HAVE_PIPE && defined(O_CLOEXEC) && !HAVE_PIPE2
RZ_API int rz_sys_execvp(const char *file, char *const argv[]) {
parent_lock_enter();
int res = execvp(file, argv);
parent_lock_leave();
return res;
}
#elif __UNIX__ && HAVE_EXECVP && HAVE_PIPE && !HAVE_PIPE2
RZ_API int rz_sys_execvp(const char *file, char *const argv[]) {
parent_lock_enter();
close_fds();
int res = execvp(file, argv);
parent_lock_leave();
return res;
}
#elif !HAVE_EXECVP
RZ_API int rz_sys_execvp(const char *file, char *const argv[]) {
return -1;
}
#endif
#if __UNIX__ && HAVE_EXECL && HAVE_PIPE && defined(O_CLOEXEC) && !HAVE_PIPE2
RZ_API int rz_sys_execl(const char *pathname, const char *arg, ...) {
va_list count_args, args;
va_start(args, arg);
va_copy(count_args, args);
size_t i, argc = 0;
while (va_arg(count_args, char *) != NULL) {
argc++;
}
va_end(count_args);
char **argv = RZ_NEWS0(char *, argc + 2);
argv[0] = rz_str_dup(pathname);
for (i = 1; i <= argc; i++) {
argv[i] = va_arg(args, char *);
}
va_end(args);
parent_lock_enter();
int res = execv(pathname, argv);
parent_lock_leave();
return res;
}
#elif __UNIX__ && HAVE_EXECL && HAVE_PIPE && !HAVE_PIPE2
RZ_API int rz_sys_execl(const char *pathname, const char *arg, ...) {
va_list count_args, args;
va_start(args, arg);
va_copy(count_args, args);
size_t i, argc = 0;
while (va_arg(count_args, char *) != NULL) {
argc++;
}
va_end(count_args);
char **argv = RZ_NEWS0(char *, argc + 2);
argv[0] = rz_str_dup(pathname);
for (i = 1; i <= argc; i++) {
argv[i] = va_arg(args, char *);
}
va_end(args);
parent_lock_enter();
close_fds();
int res = execv(pathname, argv);
parent_lock_leave();
return res;
}
#elif !HAVE_EXECL
RZ_API int rz_sys_execl(const char *pathname, const char *arg, ...) {
return -1;
}
#endif
#if __UNIX__ && HAVE_SYSTEM && HAVE_PIPE && defined(O_CLOEXEC) && !HAVE_PIPE2
RZ_API int rz_sys_system(const char *command) {
parent_lock_enter();
int res = system(command);
parent_lock_leave();
return res;
}
#elif __UNIX__ && HAVE_SYSTEM && HAVE_PIPE && !HAVE_PIPE2
RZ_API int rz_sys_system(const char *command) {
parent_lock_enter();
close_fds();
int res = system(command);
parent_lock_leave();
return res;
}
#elif !HAVE_SYSTEM && APPLE_SDK_IPHONEOS
#include <spawn.h>
RZ_API int rz_sys_system(const char *command) {
int argc;
char *cmd = rz_str_dup(command);
char **argv = rz_str_argv(cmd, &argc);
if (argv) {
char *argv0 = rz_file_path(argv[0]);
pid_t pid = 0;
int r = posix_spawn(&pid, argv0, NULL, NULL, argv, NULL);
int status;
int s = waitpid(pid, &status, 0);
return WEXITSTATUS(s);
}
}
#elif !HAVE_SYSTEM && HAVE_FORK
#include <spawn.h>
RZ_API int rz_sys_system(const char *command) {
if (!strchr(command, '|')) {
char **argv, *cmd = rz_str_dup(command);
int rc, pid, argc;
char *isbg = strchr(cmd, '&');
// XXX this is hacky
if (isbg) {
*isbg = 0;
}
argv = rz_str_argv(cmd, &argc);
if (argv) {
char *argv0 = rz_file_path(argv[0]);
if (!argv0) {
RZ_LOG_ERROR("Cannot find '%s'\n", argv[0]);
return -1;
}
pid = 0;
posix_spawn(&pid, argv0, NULL, NULL, argv, NULL);
if (isbg) {
// XXX. wait for children
rc = 0;
} else {
rc = waitpid(pid, NULL, 0);
}
rz_str_argv_free(argv);
free(argv0);
return rc;
}
RZ_LOG_ERROR("Error parsing command arguments\n");
return -1;
}
int child = rz_sys_fork();
if (child == -1) {
return -1;
}
if (child) {
return waitpid(child, NULL, 0);
}
char *bin_sh = rz_file_binsh();
if (rz_sys_execl(bin_sh, "sh", "-c", command, (const char *)NULL) == -1) {
perror("execl");
}
free(bin_sh);
exit(1);
}
#elif !HAVE_SYSTEM
RZ_API int rz_sys_system(const char *command) {
return -1;
}
#endif
#if HAVE_FORK
RZ_API int rz_sys_fork(void) {
#if __UNIX__ && HAVE_PIPE && !HAVE_PIPE2
parent_lock_enter();
#endif
pid_t child = fork();
if (child == -1) {
perror("fork");
}
#if __UNIX__ && HAVE_PIPE && !HAVE_PIPE2
if (child == 0) {
is_child = true;
} else if (child > 0) {
parent_lock_leave();
}
#endif
return child;
}
#else
RZ_API int rz_sys_fork(void) {
return -1;
}
#endif
/**
* \brief Wrapper for forkpty(3)
*
* \param amaster The master end of the PTY is stored here
* \param name The name of the slave end of the PTY is stored here
* \param termp (const struct termios) The terminal attributes
* \param winp (const struct winsize) The window size attributes
*
* \return int (pid_t) PID of the forked process
*/
RZ_API /* pid_t */ int rz_sys_forkpty(int *amaster, char *name, void /* const struct termios */ *termp, void /* const struct winsize */ *winp) {
#if HAVE_OPENPTY && HAVE_FORKPTY && HAVE_LOGIN_TTY
pid_t ret = forkpty(amaster, name, termp, winp);
if (ret == -1) {
perror("forkpty");
}
return ret;
#else
RZ_LOG_ERROR("forkpty() not found\n");
return -1;
#endif
}
/**
* \brief Wrapper for openpty(3)
*
* \param amaster The master end of the PTY is stored here
* \param aslave The slave end of the PTY is stored here
* \param name The name of the slave end of the PTY is stored here
* \param termp (const struct termios) The terminal attributes
* \param winp (const struct winsize) The window size attributes
*
* \return int Return code
*/
RZ_API int rz_sys_openpty(int *amaster, int *aslave, char *name, void /* const struct termios */ *termp, void /* const struct winsize */ *winp) {
#if HAVE_OPENPTY && HAVE_FORKPTY && HAVE_LOGIN_TTY
int ret = openpty(amaster, aslave, name, termp, winp);
if (ret == -1) {
perror("openpty");
}
return ret;
#else
RZ_LOG_ERROR("openpty() not found\n");
return -1;
#endif
}
/**
* \brief Wrapper for login_tty(3)
*
* \param fd File descriptor for the slave end of the PTY; To be made the controlling terminal
* \return int Return code
*/
RZ_API int rz_sys_login_tty(int fd) {
#if HAVE_OPENPTY && HAVE_FORKPTY && HAVE_LOGIN_TTY
int ret = login_tty(fd);
if (ret == -1) {
perror("login_tty");
}
return ret;
#else
RZ_LOG_ERROR("login_tty() not found\n");
return -1;
#endif
}
RZ_API int rz_sys_truncate_fd(int fd, ut64 length) {
#ifdef _MSC_VER
return _chsize_s(fd, length);
#else
return ftruncate(fd, (off_t)length);
#endif
}
RZ_API int rz_sys_truncate(const char *file, int sz) {
#if __WINDOWS__
int fd = rz_sys_open(file, O_RDWR, 0644);
if (fd == -1) {
return false;
}
int r = rz_sys_truncate_fd(fd, sz);
if (r != 0) {
RZ_LOG_ERROR("Could not resize '%s' file\n", file);
close(fd);
return false;
}
close(fd);
return true;
#else
return truncate(file, sz) == 0;
#endif
}
/**
* \brief Convert rizin permissions (RZ_PERM_*) to posix permissions that can be passed to \b rz_sys_open .
*
* \b rz_sys_open accepts posix permissions for now, not the arch-independent
* ones provided by RZ_PERM_*. This function is an helper to convert from rizin
* permissions to posix ones.
*/
RZ_API int rz_sys_open_perms(int rizin_perms) {
int res = 0;
if ((rizin_perms & RZ_PERM_R) && (rizin_perms & RZ_PERM_W)) {
res |= O_RDWR;
// NOTE: O_CREAT is added here because Rizin for now assumes Write means
// ability to create a file as well.
res |= O_CREAT;
} else if (rizin_perms & RZ_PERM_R) {
res |= O_RDONLY;
} else if (rizin_perms & RZ_PERM_W) {
res |= O_WRONLY;
// NOTE: O_CREAT is added here because Rizin for now assumes Write means
// ability to create a file as well.
res |= O_CREAT;
}
if (rizin_perms & RZ_PERM_CREAT) {
res |= O_CREAT;
}
return res;
}
/* perm <-> mode */
RZ_API int rz_sys_open(const char *path, int perm, int mode) {
rz_return_val_if_fail(path, -1);
char *epath = rz_path_home_expand(path);
int ret = -1;
#if __WINDOWS__
if (!strcmp(path, "/dev/null")) {
path = "NUL";
}
{
DWORD flags = 0;
DWORD sharing = FILE_SHARE_READ;
if (perm & O_RANDOM) {
flags = FILE_FLAG_RANDOM_ACCESS;
} else if (perm & O_SEQUENTIAL) {
flags = FILE_FLAG_SEQUENTIAL_SCAN;
}
if (perm & O_TEMPORARY) {
flags |= FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY;
sharing |= FILE_SHARE_DELETE;
} else if (perm & _O_SHORT_LIVED) {
flags |= FILE_ATTRIBUTE_TEMPORARY;
} else {
flags |= FILE_ATTRIBUTE_NORMAL;
}
DWORD creation = 0;
bool read_only = false;
if (perm & O_CREAT) {
if (perm & O_EXCL) {
creation = CREATE_NEW;
} else {
creation = OPEN_ALWAYS;
}
if (mode & S_IREAD && !(mode & S_IWRITE)) {
flags = FILE_ATTRIBUTE_READONLY;
read_only = true;
}
} else if (perm & O_TRUNC) {
creation = TRUNCATE_EXISTING;
}
if (!creation || !strcasecmp("NUL", path)) {
creation = OPEN_EXISTING;
}
DWORD permission = 0;
if (perm & O_WRONLY) {
permission = GENERIC_WRITE;
} else if (perm & O_RDWR) {
permission = GENERIC_WRITE | GENERIC_READ;
} else {
permission = GENERIC_READ;
}
if (perm & O_APPEND) {
permission |= FILE_APPEND_DATA;
}
if (!read_only) {
sharing |= FILE_SHARE_WRITE;
}
wchar_t *wepath = rz_utf8_to_utf16(epath);
if (!wepath) {
free(epath);
return -1;
}
HANDLE h = CreateFileW(wepath, permission, sharing, NULL, creation, flags, NULL);
if (h != INVALID_HANDLE_VALUE) {
ret = _open_osfhandle((intptr_t)h, perm);
}
free(wepath);
}
#else // __WINDOWS__
ret = open(epath, perm, mode);
#endif // __WINDOWS__
free(epath);
return ret;
}
RZ_API FILE *rz_sys_fopen(const char *path, const char *mode) {
rz_return_val_if_fail(path && mode, NULL);
FILE *ret = NULL;
char *epath = rz_path_home_expand(path);
if ((strchr(mode, 'w') || strchr(mode, 'a') || rz_file_is_regular(epath))) {
#if __WINDOWS__
wchar_t *wepath = rz_utf8_to_utf16(epath);
if (!wepath) {
free(epath);
return ret;
}
wchar_t *wmode = rz_utf8_to_utf16(mode);
if (!wmode) {
free(wepath);
free(epath);
return ret;
}
ret = _wfopen(wepath, wmode);
free(wmode);
free(wepath);
#else // __WINDOWS__
ret = fopen(epath, mode);
#endif // __WINDOWS__
}
free(epath);
return ret;
}
/**
* \brief Send signal \p sig to process with pid \p pid.
*
* \param pid PID of the process to send the signal to
* \param sig Signal to send to the process.
* \return 0 on success, -1 on failure.
*/
RZ_API int rz_sys_kill(int pid, int sig) {
rz_return_val_if_fail(pid != -1, -1);
#if __UNIX__
return kill(pid, sig);
#else
return -1;
#endif
}
/**
* \brief Send SIGTSTP signal to every process in this process group
*
* \return true if at least one signal was sent, false otherwise
*/
RZ_API bool rz_sys_stop(void) {
#if __UNIX__
return !rz_sys_kill(0, SIGTSTP);
#else
return false;
#endif
}
/**
* \brief Implementation across systems to open a dynamic library
*/
RZ_API void *rz_sys_dlopen(RZ_NULLABLE const char *libname) {
void *ret = NULL;
#if WANT_DYLINK
#if __UNIX__
if (libname) {
ret = dlopen(libname, RTLD_GLOBAL | RTLD_LAZY);
} else {
ret = dlopen(NULL, RTLD_NOW);
}
if (!ret) {
RZ_LOG_ERROR("rz_sys_dlopen: error: %s (%s)\n", libname, dlerror());
}
#elif __WINDOWS__
LPTSTR libname_;
if (libname && *libname) {
libname_ = rz_sys_conv_utf8_to_win(libname);
} else {
libname_ = calloc(MAX_PATH, sizeof(TCHAR));
if (!libname_) {
RZ_LOG_ERROR("rz_sys_dlopen: failed to allocate memory.\n");
return NULL;
}
if (!GetModuleFileName(NULL, libname_, MAX_PATH)) {
libname_[0] = '\0';
}
}
ret = LoadLibrary(libname_);
free(libname_);
if (!ret) {
DWORD dw = GetLastError();
char *err = sys_windows_error_msg(dw);
RZ_LOG_ERROR("rz_sys_dlopen: error: %s (%s)\n", libname, err ? err : "unknown");
free(err);
}
#endif
#endif
return ret;
}
/**
* \brief Implementation across systems to get the address of a symbol in a
* dynamic library
*/
RZ_API void *rz_sys_dlsym(void *handler, const char *name) {
#if WANT_DYLINK
#if __UNIX__
return dlsym(handler, name);
#elif __WINDOWS__
return GetProcAddress(handler, name);
#else
return NULL;
#endif
#else
return NULL;
#endif
}
/**
* \brief Implementation across systems to close a previously opened dynamic
* library.
*/
RZ_API int rz_sys_dlclose(void *handler) {
#if __UNIX__
return dlclose(handler);
#else
return handler ? 0 : -1;
#endif
}