mirror of
https://github.com/AFLplusplus/AFLplusplus.git
synced 2025-06-14 19:08:08 +00:00
Merge pull request #1702 from schumilo/dev
add Nyx support in afl-showmap, afl-tmin, afl-cmin and afl-analyze
This commit is contained in:
14
afl-cmin
14
afl-cmin
@ -109,6 +109,7 @@ function usage() {
|
||||
" -O - use binary-only instrumentation (FRIDA mode)\n" \
|
||||
" -Q - use binary-only instrumentation (QEMU mode)\n" \
|
||||
" -U - use unicorn-based instrumentation (unicorn mode)\n" \
|
||||
" -X - use Nyx mode\n" \
|
||||
"\n" \
|
||||
"Minimization settings:\n" \
|
||||
" -A - allow crashes and timeouts (not recommended)\n" \
|
||||
@ -156,7 +157,7 @@ BEGIN {
|
||||
# process options
|
||||
Opterr = 1 # default is to diagnose
|
||||
Optind = 1 # skip ARGV[0]
|
||||
while ((_go_c = getopt(ARGC, ARGV, "hi:o:f:m:t:eACOQU?")) != -1) {
|
||||
while ((_go_c = getopt(ARGC, ARGV, "hi:o:f:m:t:eACOQUX?")) != -1) {
|
||||
if (_go_c == "i") {
|
||||
if (!Optarg) usage()
|
||||
if (in_dir) { print "Option "_go_c" is only allowed once" > "/dev/stderr"}
|
||||
@ -218,6 +219,12 @@ BEGIN {
|
||||
unicorn_mode = 1
|
||||
continue
|
||||
} else
|
||||
if (_go_c == "X") {
|
||||
if (nyx_mode) { print "Option "_go_c" is only allowed once" > "/dev/stderr"}
|
||||
extra_par = extra_par " -X"
|
||||
nyx_mode = 1
|
||||
continue
|
||||
} else
|
||||
if (_go_c == "?") {
|
||||
exit 1
|
||||
} else
|
||||
@ -291,7 +298,8 @@ BEGIN {
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (target_bin && !exists_and_is_executable(target_bin)) {
|
||||
|
||||
if (!nyx_mode && target_bin && !exists_and_is_executable(target_bin)) {
|
||||
|
||||
"command -v "target_bin" 2>/dev/null" | getline tnew
|
||||
if (!tnew || !exists_and_is_executable(tnew)) {
|
||||
@ -311,7 +319,7 @@ BEGIN {
|
||||
}
|
||||
}
|
||||
|
||||
if (!ENVIRON["AFL_SKIP_BIN_CHECK"] && !qemu_mode && !frida_mode && !unicorn_mode) {
|
||||
if (!ENVIRON["AFL_SKIP_BIN_CHECK"] && !qemu_mode && !frida_mode && !unicorn_mode && !nyx_mode) {
|
||||
if (0 != system( "grep -q __AFL_SHM_ID "target_bin )) {
|
||||
print "[-] Error: binary '"target_bin"' doesn't appear to be instrumented." > "/dev/stderr"
|
||||
exit 1
|
||||
|
@ -147,5 +147,11 @@ s32 create_file(u8 *fn);
|
||||
void *afl_memmem(const void *haystack, size_t haystacklen, const void *needle,
|
||||
size_t needlelen);
|
||||
|
||||
#ifdef __linux__
|
||||
/* Nyx helper functions to create and remove tmp workdirs */
|
||||
char* create_nyx_tmp_workdir(void);
|
||||
void remove_nyx_tmp_workdir(afl_forkserver_t *fsrv, char* nyx_out_dir_path);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -51,16 +51,23 @@ typedef enum NyxReturnValue {
|
||||
|
||||
} NyxReturnValue;
|
||||
|
||||
typedef enum NyxProcessRole {
|
||||
StandAlone,
|
||||
Parent,
|
||||
Child,
|
||||
} NyxProcessRole;
|
||||
|
||||
typedef struct {
|
||||
|
||||
void *(*nyx_new)(const char *sharedir, const char *workdir, uint32_t cpu_id,
|
||||
uint32_t input_buffer_size,
|
||||
bool input_buffer_write_protection);
|
||||
void *(*nyx_new_parent)(const char *sharedir, const char *workdir,
|
||||
uint32_t cpu_id, uint32_t input_buffer_size,
|
||||
bool input_buffer_write_protection);
|
||||
void *(*nyx_new_child)(const char *sharedir, const char *workdir,
|
||||
uint32_t cpu_id, uint32_t worker_id);
|
||||
void *(*nyx_config_load)(const char *sharedir);
|
||||
void (*nyx_config_set_workdir_path)(void *config, const char *workdir);
|
||||
void (*nyx_config_set_input_buffer_size)(void *config, uint32_t input_buffer_size);
|
||||
void (*nyx_config_set_input_buffer_write_protection)(void *config, bool input_buffer_write_protection);
|
||||
void (*nyx_config_set_hprintf_fd)(void *config, int32_t hprintf_fd);
|
||||
void (*nyx_config_set_process_role)(void *config, enum NyxProcessRole role);
|
||||
void (*nyx_config_set_reuse_snapshot_path)(void *config, const char *reuse_snapshot_path);
|
||||
|
||||
void *(*nyx_new)(void *config, uint32_t worker_id);
|
||||
void (*nyx_shutdown)(void *qemu_process);
|
||||
void (*nyx_option_set_reload_mode)(void *qemu_process, bool enable);
|
||||
void (*nyx_option_set_timeout)(void *qemu_process, uint8_t timeout_sec,
|
||||
@ -73,8 +80,13 @@ typedef struct {
|
||||
uint32_t (*nyx_get_aux_string)(void *nyx_process, uint8_t *buffer,
|
||||
uint32_t size);
|
||||
|
||||
bool (*nyx_remove_work_dir)(const char *workdir);
|
||||
|
||||
} nyx_plugin_handler_t;
|
||||
|
||||
/* Imports helper functions to enable Nyx mode (Linux only )*/
|
||||
nyx_plugin_handler_t *afl_load_libnyx_plugin(u8 *libnyx_binary);
|
||||
|
||||
#endif
|
||||
|
||||
typedef struct afl_forkserver {
|
||||
@ -178,6 +190,8 @@ typedef struct afl_forkserver {
|
||||
u32 nyx_id; /* nyx runner id (0 -> master) */
|
||||
u32 nyx_bind_cpu_id; /* nyx runner cpu id */
|
||||
char *nyx_aux_string;
|
||||
bool nyx_use_tmp_workdir;
|
||||
char *nyx_tmp_workdir_path;
|
||||
#endif
|
||||
|
||||
} afl_forkserver_t;
|
||||
|
@ -1 +1 @@
|
||||
acaf7f6
|
||||
2da7f08
|
||||
|
@ -1 +1 @@
|
||||
86b159b
|
||||
202bace
|
||||
|
Submodule nyx_mode/QEMU-Nyx updated: 5c8cf793ec...60c216bc9e
@ -1 +1 @@
|
||||
5c8cf793ec
|
||||
60c216bc9e
|
||||
|
@ -116,11 +116,35 @@ afl-fuzz -i in -o out -Y -S 2 -- ./PACKAGE-DIRECTORY
|
||||
|
||||
## AFL++ companion tools (afl-showmap etc.)
|
||||
|
||||
Please note that AFL++ companion tools like afl-cmin, afl-showmap, etc. are
|
||||
not supported with Nyx mode, only afl-fuzz.
|
||||
AFL++ companion tools support Nyx mode and can be used to analyze or minimize one specific input or an entire output corpus. These tools work similarly to `afl-fuzz`.
|
||||
|
||||
For source based instrumentation just use these tools normally, for
|
||||
binary-only targets use with -Q for qemu_mode.
|
||||
To run a target with one of these tools, add the `-X` parameter to the command line to enable Nyx mode, and pass the path to a Nyx package directory:
|
||||
|
||||
```shell
|
||||
afl-tmin -i in_file -o out_file -X -- ./PACKAGE-DIRECTORY
|
||||
```
|
||||
|
||||
```shell
|
||||
afl-analyze -i in_file -X -- ./PACKAGE-DIRECTORY
|
||||
```
|
||||
|
||||
```shell
|
||||
afl-showmap -i in_dir -o out_file -X -- ./PACKAGE-DIRECTORY
|
||||
```
|
||||
|
||||
```shell
|
||||
afl-cmin -i in_dir -o out_dir -X -- ./PACKAGE-DIRECTORY
|
||||
```
|
||||
|
||||
On each program startup of one the AFL++ tools in Nyx mode, a Nyx VM is spawned, and a bootstrapping procedure is performed inside the VM to prepare the target environment. As a consequence, due to the bootstrapping procedure, the launch performance is much slower compared to other modes. However, this can be optimized by reusing an existing fuzzing snapshot to avoid the slow re-execution of the bootstrap procedure.
|
||||
|
||||
A fuzzing snapshot is automatically created and stored in the output directory at `out_dir/workdir/snapshot/` by the first parent process of `afl-fuzz` if parallel mode is used. To enable this feature, set the path to an existing snapshot directory in the `NYX_REUSE_SNAPSHOT` environment variable and use the tools as usual:
|
||||
|
||||
```shell
|
||||
afl-fuzz -i ./in_dir -o ./out_dir -Y -M 0 ./PACKAGE-DIRECTORY
|
||||
|
||||
NYX_REUSE_SNAPSHOT=./out_dir/workdir/snapshot/ afl-analyze -i in_file -X -- ./PACKAGE-DIRECTORY
|
||||
```
|
||||
|
||||
## Real-world examples
|
||||
|
||||
|
@ -60,11 +60,6 @@ fi
|
||||
|
||||
echo "[*] Checking QEMU-Nyx ..."
|
||||
if [ ! -f "QEMU-Nyx/x86_64-softmmu/qemu-system-x86_64" ]; then
|
||||
|
||||
if ! dpkg -s gtk3-devel > /dev/null 2>&1; then
|
||||
echo "[-] Disabling GTK because gtk3-devel is not installed."
|
||||
sed -i 's/--enable-gtk//g' QEMU-Nyx/compile_qemu_nyx.sh
|
||||
fi
|
||||
(cd QEMU-Nyx && ./compile_qemu_nyx.sh static)
|
||||
fi
|
||||
|
||||
|
Submodule nyx_mode/libnyx updated: acaf7f6346...2da7f08b6e
Submodule nyx_mode/packer updated: 86b159bafc...202bace888
@ -121,9 +121,9 @@ static void kill_child() {
|
||||
|
||||
}
|
||||
|
||||
static void classify_counts(u8 *mem) {
|
||||
static void classify_counts(u8 *mem, u32 mem_size) {
|
||||
|
||||
u32 i = map_size;
|
||||
u32 i = mem_size;
|
||||
|
||||
if (edges_only) {
|
||||
|
||||
@ -222,7 +222,7 @@ static u64 analyze_run_target(u8 *mem, u32 len, u8 first_run) {
|
||||
|
||||
}
|
||||
|
||||
classify_counts(fsrv.trace_bits);
|
||||
classify_counts(fsrv.trace_bits, fsrv.map_size);
|
||||
total_execs++;
|
||||
|
||||
if (stop_soon) {
|
||||
@ -768,6 +768,7 @@ static void usage(u8 *argv0) {
|
||||
" -U - use unicorn-based instrumentation (Unicorn mode)\n"
|
||||
" -W - use qemu-based instrumentation with Wine (Wine "
|
||||
"mode)\n"
|
||||
" -X - use Nyx mode\n"
|
||||
#endif
|
||||
"\n"
|
||||
|
||||
@ -814,7 +815,7 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
|
||||
afl_fsrv_init(&fsrv);
|
||||
|
||||
while ((opt = getopt(argc, argv, "+i:f:m:t:eAOQUWh")) > 0) {
|
||||
while ((opt = getopt(argc, argv, "+i:f:m:t:eAOQUWXh")) > 0) {
|
||||
|
||||
switch (opt) {
|
||||
|
||||
@ -966,6 +967,22 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
|
||||
break;
|
||||
|
||||
#ifdef __linux__
|
||||
case 'X': /* NYX mode */
|
||||
|
||||
if (fsrv.nyx_mode) { FATAL("Multiple -X options not supported"); }
|
||||
|
||||
fsrv.nyx_mode = 1;
|
||||
fsrv.nyx_parent = true;
|
||||
fsrv.nyx_standalone = true;
|
||||
|
||||
break;
|
||||
#else
|
||||
case 'X':
|
||||
FATAL("Nyx mode is only availabe on linux...");
|
||||
break;
|
||||
#endif
|
||||
|
||||
case 'h':
|
||||
usage(argv[0]);
|
||||
return -1;
|
||||
@ -997,7 +1014,17 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
|
||||
set_up_environment(argv);
|
||||
|
||||
#ifdef __linux__
|
||||
if(!fsrv.nyx_mode){
|
||||
fsrv.target_path = find_binary(argv[optind]);
|
||||
}
|
||||
else{
|
||||
fsrv.target_path = ck_strdup(argv[optind]);
|
||||
}
|
||||
#else
|
||||
fsrv.target_path = find_binary(argv[optind]);
|
||||
#endif
|
||||
|
||||
fsrv.trace_bits = afl_shm_init(&shm, map_size, 0);
|
||||
detect_file_args(argv + optind, fsrv.out_file, &use_stdin);
|
||||
signal(SIGALRM, kill_child);
|
||||
@ -1020,6 +1047,23 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
|
||||
use_argv = get_cs_argv(argv[0], &target_path, argc - optind, argv + optind);
|
||||
|
||||
#ifdef __linux__
|
||||
} else if (fsrv.nyx_mode) {
|
||||
|
||||
fsrv.nyx_id = 0;
|
||||
|
||||
u8 *libnyx_binary = find_afl_binary(argv[0], "libnyx.so");
|
||||
fsrv.nyx_handlers = afl_load_libnyx_plugin(libnyx_binary);
|
||||
if (fsrv.nyx_handlers == NULL) {
|
||||
FATAL("failed to initialize libnyx.so...");
|
||||
}
|
||||
|
||||
fsrv.nyx_use_tmp_workdir = true;
|
||||
fsrv.nyx_bind_cpu_id = 0;
|
||||
|
||||
use_argv = argv + optind;
|
||||
#endif
|
||||
|
||||
} else {
|
||||
|
||||
use_argv = argv + optind;
|
||||
@ -1045,7 +1089,13 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
&fsrv, NULL, NULL, (fsrv.qemu_mode || unicorn_mode) ? SIGKILL : SIGTERM);
|
||||
|
||||
read_initial_file();
|
||||
#ifdef __linux__
|
||||
if(!fsrv.nyx_mode){
|
||||
(void)check_binary_signatures(fsrv.target_path);
|
||||
}
|
||||
#else
|
||||
(void)check_binary_signatures(fsrv.target_path);
|
||||
#endif
|
||||
|
||||
ACTF("Performing dry run (mem limit = %llu MB, timeout = %u ms%s)...",
|
||||
mem_limit, exec_tmout, edges_only ? ", edges only" : "");
|
||||
@ -1069,6 +1119,7 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
|
||||
OKF("We're done here. Have a nice day!\n");
|
||||
|
||||
|
||||
afl_shm_deinit(&shm);
|
||||
afl_fsrv_deinit(&fsrv);
|
||||
if (fsrv.target_path) { ck_free(fsrv.target_path); }
|
||||
|
@ -1359,3 +1359,42 @@ s32 create_file(u8 *fn) {
|
||||
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
/* Nyx requires a tmp workdir to access specific files (such as mmapped files,
|
||||
* etc.). This helper function basically creates both a path to a tmp workdir
|
||||
* and the workdir itself. If the environment variable TMPDIR is set, we use
|
||||
* that as the base directory, otherwise we use /tmp. */
|
||||
char* create_nyx_tmp_workdir(void) {
|
||||
|
||||
char *tmpdir = getenv("TMPDIR");
|
||||
|
||||
if (!tmpdir) { tmpdir = "/tmp"; }
|
||||
|
||||
char* nyx_out_dir_path = alloc_printf("%s/.nyx_tmp_%d/", tmpdir, (u32)getpid());
|
||||
|
||||
if (mkdir(nyx_out_dir_path, 0700)) {
|
||||
PFATAL("Unable to create nyx workdir");
|
||||
}
|
||||
|
||||
return nyx_out_dir_path;
|
||||
}
|
||||
|
||||
/* Vice versa, we remove the tmp workdir for nyx with this helper function. */
|
||||
void remove_nyx_tmp_workdir(afl_forkserver_t *fsrv, char* nyx_out_dir_path) {
|
||||
char* workdir_path = alloc_printf("%s/workdir", nyx_out_dir_path);
|
||||
|
||||
if (access(workdir_path, R_OK) == 0) {
|
||||
if(fsrv->nyx_handlers->nyx_remove_work_dir(workdir_path) != true) {
|
||||
WARNF("Unable to remove nyx workdir (%s)", workdir_path);
|
||||
}
|
||||
}
|
||||
|
||||
if (rmdir(nyx_out_dir_path)) {
|
||||
WARNF("Unable to remove nyx workdir (%s)", nyx_out_dir_path);
|
||||
}
|
||||
|
||||
ck_free(workdir_path);
|
||||
ck_free(nyx_out_dir_path);
|
||||
}
|
||||
#endif
|
||||
|
@ -49,6 +49,118 @@
|
||||
#include <sys/select.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <dlfcn.h>
|
||||
|
||||
/* function to load nyx_helper function from libnyx.so */
|
||||
|
||||
nyx_plugin_handler_t *afl_load_libnyx_plugin(u8 *libnyx_binary) {
|
||||
|
||||
void *handle;
|
||||
nyx_plugin_handler_t *plugin = calloc(1, sizeof(nyx_plugin_handler_t));
|
||||
|
||||
ACTF("Trying to load libnyx.so plugin...");
|
||||
handle = dlopen((char *)libnyx_binary, RTLD_NOW);
|
||||
if (!handle) { goto fail; }
|
||||
|
||||
plugin->nyx_config_load = dlsym(handle, "nyx_config_load");
|
||||
if (plugin->nyx_config_load == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_config_set_workdir_path = dlsym(handle, "nyx_config_set_workdir_path");
|
||||
if (plugin->nyx_config_set_workdir_path == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_config_set_input_buffer_size = dlsym(handle, "nyx_config_set_input_buffer_size");
|
||||
if (plugin->nyx_config_set_input_buffer_size == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_config_set_input_buffer_write_protection = dlsym(handle, "nyx_config_set_input_buffer_write_protection");
|
||||
if (plugin->nyx_config_set_input_buffer_write_protection == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_config_set_hprintf_fd = dlsym(handle, "nyx_config_set_hprintf_fd");
|
||||
if (plugin->nyx_config_set_hprintf_fd == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_config_set_process_role = dlsym(handle, "nyx_config_set_process_role");
|
||||
if (plugin->nyx_config_set_process_role == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_config_set_reuse_snapshot_path = dlsym(handle, "nyx_config_set_reuse_snapshot_path");
|
||||
if (plugin->nyx_config_set_reuse_snapshot_path == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_new = dlsym(handle, "nyx_new");
|
||||
if (plugin->nyx_new == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_shutdown = dlsym(handle, "nyx_shutdown");
|
||||
if (plugin->nyx_shutdown == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_option_set_reload_mode =
|
||||
dlsym(handle, "nyx_option_set_reload_mode");
|
||||
if (plugin->nyx_option_set_reload_mode == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_option_set_timeout = dlsym(handle, "nyx_option_set_timeout");
|
||||
if (plugin->nyx_option_set_timeout == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_option_apply = dlsym(handle, "nyx_option_apply");
|
||||
if (plugin->nyx_option_apply == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_set_afl_input = dlsym(handle, "nyx_set_afl_input");
|
||||
if (plugin->nyx_set_afl_input == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_exec = dlsym(handle, "nyx_exec");
|
||||
if (plugin->nyx_exec == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_get_bitmap_buffer = dlsym(handle, "nyx_get_bitmap_buffer");
|
||||
if (plugin->nyx_get_bitmap_buffer == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_get_bitmap_buffer_size =
|
||||
dlsym(handle, "nyx_get_bitmap_buffer_size");
|
||||
if (plugin->nyx_get_bitmap_buffer_size == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_get_aux_string = dlsym(handle, "nyx_get_aux_string");
|
||||
if (plugin->nyx_get_aux_string == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_remove_work_dir = dlsym(handle, "nyx_remove_work_dir");
|
||||
if (plugin->nyx_remove_work_dir == NULL) { goto fail; }
|
||||
|
||||
|
||||
OKF("libnyx plugin is ready!");
|
||||
return plugin;
|
||||
|
||||
fail:
|
||||
|
||||
FATAL("failed to load libnyx: %s\n", dlerror());
|
||||
ck_free(plugin);
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
void afl_nyx_runner_kill(afl_forkserver_t *fsrv){
|
||||
if (fsrv->nyx_mode) {
|
||||
|
||||
if (fsrv->nyx_aux_string){
|
||||
ck_free(fsrv->nyx_aux_string);
|
||||
}
|
||||
|
||||
/* check if we actually got a valid nyx runner */
|
||||
if (fsrv->nyx_runner) {
|
||||
fsrv->nyx_handlers->nyx_shutdown(fsrv->nyx_runner);
|
||||
}
|
||||
|
||||
/* if we have use a tmp work dir we need to remove it */
|
||||
if (fsrv->nyx_use_tmp_workdir && fsrv->nyx_tmp_workdir_path) {
|
||||
remove_nyx_tmp_workdir(fsrv, fsrv->nyx_tmp_workdir_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Wrapper for FATAL() that kills the nyx runner (and removes all created tmp
|
||||
* files) before exiting. Used before "afl_fsrv_killall()" is registered as
|
||||
* an atexit() handler. */
|
||||
#define NYX_PRE_FATAL(fsrv, x...) \
|
||||
do { \
|
||||
afl_nyx_runner_kill(fsrv); \
|
||||
FATAL(x); \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The correct fds for reading and writing pipes
|
||||
*/
|
||||
@ -84,6 +196,8 @@ void afl_fsrv_init(afl_forkserver_t *fsrv) {
|
||||
fsrv->nyx_runner = NULL;
|
||||
fsrv->nyx_id = 0xFFFFFFFF;
|
||||
fsrv->nyx_bind_cpu_id = 0xFFFFFFFF;
|
||||
fsrv->nyx_use_tmp_workdir = false;
|
||||
fsrv->nyx_tmp_workdir_path = NULL;
|
||||
#endif
|
||||
|
||||
// this structure needs default so we initialize it if this was not done
|
||||
@ -397,40 +511,73 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv,
|
||||
|
||||
if (!be_quiet) { ACTF("Spinning up the NYX backend..."); }
|
||||
|
||||
if (fsrv->out_dir_path == NULL) { FATAL("Nyx workdir path not found..."); }
|
||||
if (fsrv->nyx_use_tmp_workdir){
|
||||
fsrv->nyx_tmp_workdir_path = create_nyx_tmp_workdir();
|
||||
fsrv->out_dir_path = fsrv->nyx_tmp_workdir_path;
|
||||
} else {
|
||||
if (fsrv->out_dir_path == NULL) { NYX_PRE_FATAL(fsrv, "Nyx workdir path not found..."); }
|
||||
}
|
||||
|
||||
char *x = alloc_printf("%s/workdir", fsrv->out_dir_path);
|
||||
/* libnyx expects an absolute path */
|
||||
char* outdir_path_absolute = realpath(fsrv->out_dir_path, NULL);
|
||||
if (outdir_path_absolute == NULL) { NYX_PRE_FATAL(fsrv, "Nyx workdir path cannot be resolved ..."); }
|
||||
|
||||
if (fsrv->nyx_id == 0xFFFFFFFF) { FATAL("Nyx ID is not set..."); }
|
||||
char *workdir_path = alloc_printf("%s/workdir", outdir_path_absolute);
|
||||
|
||||
if (fsrv->nyx_id == 0xFFFFFFFF) {NYX_PRE_FATAL(fsrv, "Nyx ID is not set..."); }
|
||||
|
||||
if (fsrv->nyx_bind_cpu_id == 0xFFFFFFFF) {
|
||||
|
||||
FATAL("Nyx CPU ID is not set...");
|
||||
|
||||
NYX_PRE_FATAL(fsrv, "Nyx CPU ID is not set...");
|
||||
}
|
||||
|
||||
void* nyx_config = fsrv->nyx_handlers->nyx_config_load(fsrv->target_path);
|
||||
|
||||
fsrv->nyx_handlers->nyx_config_set_workdir_path(nyx_config, workdir_path);
|
||||
fsrv->nyx_handlers->nyx_config_set_input_buffer_size(nyx_config, MAX_FILE);
|
||||
fsrv->nyx_handlers->nyx_config_set_input_buffer_write_protection(nyx_config, true);
|
||||
|
||||
if (fsrv->nyx_standalone) {
|
||||
|
||||
fsrv->nyx_runner = fsrv->nyx_handlers->nyx_new(
|
||||
fsrv->target_path, x, fsrv->nyx_bind_cpu_id, MAX_FILE, true);
|
||||
|
||||
fsrv->nyx_handlers->nyx_config_set_process_role(nyx_config, StandAlone);
|
||||
} else {
|
||||
|
||||
if (fsrv->nyx_parent) {
|
||||
|
||||
fsrv->nyx_runner = fsrv->nyx_handlers->nyx_new_parent(
|
||||
fsrv->target_path, x, fsrv->nyx_bind_cpu_id, MAX_FILE, true);
|
||||
|
||||
fsrv->nyx_handlers->nyx_config_set_process_role(nyx_config, Parent);
|
||||
} else {
|
||||
|
||||
fsrv->nyx_runner = fsrv->nyx_handlers->nyx_new_child(
|
||||
fsrv->target_path, x, fsrv->nyx_bind_cpu_id, fsrv->nyx_id);
|
||||
|
||||
fsrv->nyx_handlers->nyx_config_set_process_role(nyx_config, Child);
|
||||
}
|
||||
}
|
||||
|
||||
if (getenv("NYX_REUSE_SNAPSHOT") != NULL){
|
||||
|
||||
if (access(getenv("NYX_REUSE_SNAPSHOT"), F_OK) == -1) {
|
||||
NYX_PRE_FATAL(fsrv, "NYX_REUSE_SNAPSHOT path does not exist");
|
||||
}
|
||||
|
||||
ck_free(x);
|
||||
/* stupid sanity check to avoid passing an empty or invalid snapshot directory */
|
||||
char* snapshot_file_path = alloc_printf("%s/global.state", getenv("NYX_REUSE_SNAPSHOT"));
|
||||
if (access(snapshot_file_path, R_OK) == -1) {
|
||||
NYX_PRE_FATAL(fsrv, "NYX_REUSE_SNAPSHOT path does not contain a valid Nyx snapshot");
|
||||
}
|
||||
ck_free(snapshot_file_path);
|
||||
|
||||
/* another sanity check to avoid passing a snapshot directory that is
|
||||
* located in the current workdir (the workdir will be wiped by libnyx on startup) */
|
||||
char* workdir_snapshot_path = alloc_printf("%s/workdir/snapshot", outdir_path_absolute);
|
||||
char* reuse_snapshot_path_real = realpath(getenv("NYX_REUSE_SNAPSHOT"), NULL);
|
||||
|
||||
if (strcmp(workdir_snapshot_path, reuse_snapshot_path_real) == 0){
|
||||
NYX_PRE_FATAL(fsrv, "NYX_REUSE_SNAPSHOT path is located in current workdir (use another output directory)");
|
||||
}
|
||||
|
||||
ck_free(reuse_snapshot_path_real);
|
||||
ck_free(workdir_snapshot_path);
|
||||
|
||||
fsrv->nyx_handlers->nyx_config_set_reuse_snapshot_path(nyx_config, getenv("NYX_REUSE_SNAPSHOT"));
|
||||
}
|
||||
|
||||
fsrv->nyx_runner = fsrv->nyx_handlers->nyx_new(nyx_config, fsrv->nyx_bind_cpu_id);
|
||||
|
||||
ck_free(workdir_path);
|
||||
ck_free(outdir_path_absolute);
|
||||
|
||||
if (fsrv->nyx_runner == NULL) { FATAL("Something went wrong ..."); }
|
||||
|
||||
@ -458,15 +605,13 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv,
|
||||
switch (fsrv->nyx_handlers->nyx_exec(fsrv->nyx_runner)) {
|
||||
|
||||
case Abort:
|
||||
fsrv->nyx_handlers->nyx_shutdown(fsrv->nyx_runner);
|
||||
FATAL("Error: Nyx abort occured...");
|
||||
NYX_PRE_FATAL(fsrv, "Error: Nyx abort occured...");
|
||||
break;
|
||||
case IoError:
|
||||
FATAL("Error: QEMU-Nyx has died...");
|
||||
NYX_PRE_FATAL(fsrv, "Error: QEMU-Nyx has died...");
|
||||
break;
|
||||
case Error:
|
||||
fsrv->nyx_handlers->nyx_shutdown(fsrv->nyx_runner);
|
||||
FATAL("Error: Nyx runtime error has occured...");
|
||||
NYX_PRE_FATAL(fsrv, "Error: Nyx runtime error has occured...");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -476,7 +621,7 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv,
|
||||
/* autodict in Nyx mode */
|
||||
if (!ignore_autodict) {
|
||||
|
||||
x = alloc_printf("%s/workdir/dump/afl_autodict.txt", fsrv->out_dir_path);
|
||||
char* x = alloc_printf("%s/workdir/dump/afl_autodict.txt", fsrv->out_dir_path);
|
||||
int nyx_autodict_fd = open(x, O_RDONLY);
|
||||
ck_free(x);
|
||||
|
||||
@ -489,7 +634,7 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv,
|
||||
u8 *dict = ck_alloc(f_len);
|
||||
if (dict == NULL) {
|
||||
|
||||
FATAL("Could not allocate %u bytes of autodictionary memory",
|
||||
NYX_PRE_FATAL(fsrv, "Could not allocate %u bytes of autodictionary memory",
|
||||
f_len);
|
||||
|
||||
}
|
||||
@ -507,7 +652,7 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv,
|
||||
|
||||
} else {
|
||||
|
||||
FATAL(
|
||||
NYX_PRE_FATAL(fsrv,
|
||||
"Reading autodictionary fail at position %u with %u bytes "
|
||||
"left.",
|
||||
offset, len);
|
||||
@ -1194,13 +1339,7 @@ void afl_fsrv_kill(afl_forkserver_t *fsrv) {
|
||||
fsrv->child_pid = -1;
|
||||
|
||||
#ifdef __linux__
|
||||
if (fsrv->nyx_mode) {
|
||||
|
||||
free(fsrv->nyx_aux_string);
|
||||
fsrv->nyx_handlers->nyx_shutdown(fsrv->nyx_runner);
|
||||
|
||||
}
|
||||
|
||||
afl_nyx_runner_kill(fsrv);
|
||||
#endif
|
||||
|
||||
}
|
||||
@ -1377,7 +1516,6 @@ afl_fsrv_run_target(afl_forkserver_t *fsrv, u32 timeout,
|
||||
FATAL("FixMe: Nyx InvalidWriteToPayload handler is missing");
|
||||
break;
|
||||
case Abort:
|
||||
fsrv->nyx_handlers->nyx_shutdown(fsrv->nyx_runner);
|
||||
FATAL("Error: Nyx abort occured...");
|
||||
case IoError:
|
||||
if (*stop_soon_p) {
|
||||
|
@ -435,69 +435,6 @@ static void fasan_check_afl_preload(char *afl_preload) {
|
||||
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
#include <dlfcn.h>
|
||||
|
||||
nyx_plugin_handler_t *afl_load_libnyx_plugin(u8 *libnyx_binary) {
|
||||
|
||||
void *handle;
|
||||
nyx_plugin_handler_t *plugin = calloc(1, sizeof(nyx_plugin_handler_t));
|
||||
|
||||
ACTF("Trying to load libnyx.so plugin...");
|
||||
handle = dlopen((char *)libnyx_binary, RTLD_NOW);
|
||||
if (!handle) { goto fail; }
|
||||
|
||||
plugin->nyx_new = dlsym(handle, "nyx_new");
|
||||
if (plugin->nyx_new == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_new_parent = dlsym(handle, "nyx_new_parent");
|
||||
if (plugin->nyx_new_parent == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_new_child = dlsym(handle, "nyx_new_child");
|
||||
if (plugin->nyx_new_child == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_shutdown = dlsym(handle, "nyx_shutdown");
|
||||
if (plugin->nyx_shutdown == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_option_set_reload_mode =
|
||||
dlsym(handle, "nyx_option_set_reload_mode");
|
||||
if (plugin->nyx_option_set_reload_mode == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_option_set_timeout = dlsym(handle, "nyx_option_set_timeout");
|
||||
if (plugin->nyx_option_set_timeout == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_option_apply = dlsym(handle, "nyx_option_apply");
|
||||
if (plugin->nyx_option_apply == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_set_afl_input = dlsym(handle, "nyx_set_afl_input");
|
||||
if (plugin->nyx_set_afl_input == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_exec = dlsym(handle, "nyx_exec");
|
||||
if (plugin->nyx_exec == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_get_bitmap_buffer = dlsym(handle, "nyx_get_bitmap_buffer");
|
||||
if (plugin->nyx_get_bitmap_buffer == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_get_bitmap_buffer_size =
|
||||
dlsym(handle, "nyx_get_bitmap_buffer_size");
|
||||
if (plugin->nyx_get_bitmap_buffer_size == NULL) { goto fail; }
|
||||
|
||||
plugin->nyx_get_aux_string = dlsym(handle, "nyx_get_aux_string");
|
||||
if (plugin->nyx_get_aux_string == NULL) { goto fail; }
|
||||
|
||||
OKF("libnyx plugin is ready!");
|
||||
return plugin;
|
||||
|
||||
fail:
|
||||
|
||||
FATAL("failed to load libnyx: %s\n", dlerror());
|
||||
free(plugin);
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* Main entry point */
|
||||
|
||||
int main(int argc, char **argv_orig, char **envp) {
|
||||
@ -2249,14 +2186,6 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
|
||||
if (!afl->pending_not_fuzzed || !valid_seeds) {
|
||||
|
||||
#ifdef __linux__
|
||||
if (afl->fsrv.nyx_mode) {
|
||||
|
||||
afl->fsrv.nyx_handlers->nyx_shutdown(afl->fsrv.nyx_runner);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
FATAL("We need at least one valid input seed that does not crash!");
|
||||
|
||||
}
|
||||
|
@ -434,6 +434,20 @@ static u32 read_file(u8 *in_file) {
|
||||
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
/* Execute the target application with an empty input (in Nyx mode). */
|
||||
static void showmap_run_target_nyx_mode(afl_forkserver_t *fsrv) {
|
||||
|
||||
afl_fsrv_write_to_testcase(fsrv, NULL, 0);
|
||||
|
||||
if (afl_fsrv_run_target(fsrv, fsrv->exec_tmout, &stop_soon) ==
|
||||
FSRV_RUN_ERROR) {
|
||||
|
||||
FATAL("Error running target in Nyx mode");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Execute target application. */
|
||||
|
||||
static void showmap_run_target(afl_forkserver_t *fsrv, char **argv) {
|
||||
@ -797,6 +811,7 @@ static void usage(u8 *argv0) {
|
||||
" -W - use qemu-based instrumentation with Wine (Wine mode)\n"
|
||||
" (Not necessary, here for consistency with other afl-* "
|
||||
"tools)\n"
|
||||
" -X - use Nyx mode\n"
|
||||
#endif
|
||||
"\n"
|
||||
"Other settings:\n"
|
||||
@ -875,7 +890,7 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
|
||||
if (getenv("AFL_QUIET") != NULL) { be_quiet = true; }
|
||||
|
||||
while ((opt = getopt(argc, argv, "+i:o:f:m:t:AeqCZOH:QUWbcrsh")) > 0) {
|
||||
while ((opt = getopt(argc, argv, "+i:o:f:m:t:AeqCZOH:QUWbcrshX")) > 0) {
|
||||
|
||||
switch (opt) {
|
||||
|
||||
@ -1063,6 +1078,22 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
|
||||
break;
|
||||
|
||||
#ifdef __linux__
|
||||
case 'X': /* NYX mode */
|
||||
|
||||
if (fsrv->nyx_mode) { FATAL("Multiple -X options not supported"); }
|
||||
|
||||
fsrv->nyx_mode = 1;
|
||||
fsrv->nyx_parent = true;
|
||||
fsrv->nyx_standalone = true;
|
||||
|
||||
break;
|
||||
#else
|
||||
case 'X':
|
||||
FATAL("Nyx mode is only availabe on linux...");
|
||||
break;
|
||||
#endif
|
||||
|
||||
case 'b':
|
||||
|
||||
/* Secret undocumented mode. Writes output in raw binary format
|
||||
@ -1134,7 +1165,17 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
|
||||
set_up_environment(fsrv, argv);
|
||||
|
||||
#ifdef __linux__
|
||||
if(!fsrv->nyx_mode){
|
||||
fsrv->target_path = find_binary(argv[optind]);
|
||||
}
|
||||
else{
|
||||
fsrv->target_path = ck_strdup(argv[optind]);
|
||||
}
|
||||
#else
|
||||
fsrv->target_path = find_binary(argv[optind]);
|
||||
#endif
|
||||
|
||||
fsrv->trace_bits = afl_shm_init(&shm, map_size, 0);
|
||||
|
||||
if (!quiet_mode) {
|
||||
@ -1190,6 +1231,26 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
use_argv =
|
||||
get_cs_argv(argv[0], &fsrv->target_path, argc - optind, argv + optind);
|
||||
|
||||
#ifdef __linux__
|
||||
} else if (fsrv->nyx_mode) {
|
||||
|
||||
use_argv = ck_alloc(sizeof(char *) * (1));
|
||||
use_argv[0] = argv[0];
|
||||
|
||||
fsrv->nyx_id = 0;
|
||||
|
||||
u8 *libnyx_binary = find_afl_binary(use_argv[0], "libnyx.so");
|
||||
fsrv->nyx_handlers = afl_load_libnyx_plugin(libnyx_binary);
|
||||
if (fsrv->nyx_handlers == NULL) {
|
||||
|
||||
FATAL("failed to initialize libnyx.so...");
|
||||
|
||||
}
|
||||
|
||||
fsrv->nyx_use_tmp_workdir = true;
|
||||
fsrv->nyx_bind_cpu_id = 0;
|
||||
#endif
|
||||
|
||||
} else {
|
||||
|
||||
use_argv = argv + optind;
|
||||
@ -1226,7 +1287,13 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
if(!fsrv->nyx_mode && in_dir){
|
||||
(void)check_binary_signatures(fsrv->target_path);
|
||||
}
|
||||
#else
|
||||
if (in_dir) { (void)check_binary_signatures(fsrv->target_path); }
|
||||
#endif
|
||||
|
||||
shm_fuzz = ck_alloc(sizeof(sharedmem_t));
|
||||
|
||||
@ -1247,7 +1314,13 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
fsrv->shmem_fuzz = map + sizeof(u32);
|
||||
|
||||
configure_afl_kill_signals(
|
||||
fsrv, NULL, NULL, (fsrv->qemu_mode || unicorn_mode) ? SIGKILL : SIGTERM);
|
||||
fsrv, NULL, NULL, (fsrv->qemu_mode || unicorn_mode
|
||||
#ifdef __linux__
|
||||
|| fsrv->nyx_mode
|
||||
#endif
|
||||
)
|
||||
? SIGKILL
|
||||
: SIGTERM);
|
||||
|
||||
if (!fsrv->cs_mode && !fsrv->qemu_mode && !unicorn_mode) {
|
||||
|
||||
@ -1390,7 +1463,15 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
if (fsrv->support_shmem_fuzz && !fsrv->use_shmem_fuzz)
|
||||
shm_fuzz = deinit_shmem(fsrv, shm_fuzz);
|
||||
|
||||
#ifdef __linux__
|
||||
if(!fsrv->nyx_mode){
|
||||
#endif
|
||||
showmap_run_target(fsrv, use_argv);
|
||||
#ifdef __linux__
|
||||
} else {
|
||||
showmap_run_target_nyx_mode(fsrv);
|
||||
}
|
||||
#endif
|
||||
tcnt = write_results_to_file(fsrv, out_file);
|
||||
if (!quiet_mode) {
|
||||
|
||||
@ -1441,6 +1522,7 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
|
||||
if (fsrv->target_path) { ck_free(fsrv->target_path); }
|
||||
|
||||
|
||||
afl_fsrv_deinit(fsrv);
|
||||
|
||||
if (stdin_file) { ck_free(stdin_file); }
|
||||
|
@ -789,6 +789,7 @@ static void usage(u8 *argv0) {
|
||||
"mode)\n"
|
||||
" (Not necessary, here for consistency with other afl-* "
|
||||
"tools)\n"
|
||||
" -X - use Nyx mode\n"
|
||||
#endif
|
||||
"\n"
|
||||
|
||||
@ -845,7 +846,7 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
|
||||
SAYF(cCYA "afl-tmin" VERSION cRST " by Michal Zalewski\n");
|
||||
|
||||
while ((opt = getopt(argc, argv, "+i:o:f:m:t:B:xeAOQUWHh")) > 0) {
|
||||
while ((opt = getopt(argc, argv, "+i:o:f:m:t:B:xeAOQUWXHh")) > 0) {
|
||||
|
||||
switch (opt) {
|
||||
|
||||
@ -1003,6 +1004,22 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
|
||||
break;
|
||||
|
||||
#ifdef __linux__
|
||||
case 'X': /* NYX mode */
|
||||
|
||||
if (fsrv->nyx_mode) { FATAL("Multiple -X options not supported"); }
|
||||
|
||||
fsrv->nyx_mode = 1;
|
||||
fsrv->nyx_parent = true;
|
||||
fsrv->nyx_standalone = true;
|
||||
|
||||
break;
|
||||
#else
|
||||
case 'X':
|
||||
FATAL("Nyx mode is only availabe on linux...");
|
||||
break;
|
||||
#endif
|
||||
|
||||
case 'H': /* Hang Mode */
|
||||
|
||||
/* Minimizes a testcase to the minimum that still times out */
|
||||
@ -1068,7 +1085,17 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
|
||||
set_up_environment(fsrv, argv);
|
||||
|
||||
#ifdef __linux__
|
||||
if(!fsrv->nyx_mode){
|
||||
fsrv->target_path = find_binary(argv[optind]);
|
||||
}
|
||||
else{
|
||||
fsrv->target_path = ck_strdup(argv[optind]);
|
||||
}
|
||||
#else
|
||||
fsrv->target_path = find_binary(argv[optind]);
|
||||
#endif
|
||||
|
||||
fsrv->trace_bits = afl_shm_init(&shm, map_size, 0);
|
||||
detect_file_args(argv + optind, out_file, &fsrv->use_stdin);
|
||||
signal(SIGALRM, kill_child);
|
||||
@ -1092,6 +1119,23 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
use_argv =
|
||||
get_cs_argv(argv[0], &fsrv->target_path, argc - optind, argv + optind);
|
||||
|
||||
#ifdef __linux__
|
||||
} else if (fsrv->nyx_mode) {
|
||||
|
||||
fsrv->nyx_id = 0;
|
||||
|
||||
u8 *libnyx_binary = find_afl_binary(argv[0], "libnyx.so");
|
||||
fsrv->nyx_handlers = afl_load_libnyx_plugin(libnyx_binary);
|
||||
if (fsrv->nyx_handlers == NULL) {
|
||||
FATAL("failed to initialize libnyx.so...");
|
||||
}
|
||||
|
||||
fsrv->nyx_use_tmp_workdir = true;
|
||||
fsrv->nyx_bind_cpu_id = 0;
|
||||
|
||||
use_argv = argv + optind;
|
||||
#endif
|
||||
|
||||
} else {
|
||||
|
||||
use_argv = argv + optind;
|
||||
@ -1161,7 +1205,14 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
fsrv->shmem_fuzz = map + sizeof(u32);
|
||||
|
||||
read_initial_file();
|
||||
|
||||
#ifdef __linux__
|
||||
if(!fsrv->nyx_mode){
|
||||
(void)check_binary_signatures(fsrv->target_path);
|
||||
}
|
||||
#else
|
||||
(void)check_binary_signatures(fsrv->target_path);
|
||||
#endif
|
||||
|
||||
if (!fsrv->qemu_mode && !unicorn_mode) {
|
||||
|
||||
@ -1265,6 +1316,7 @@ int main(int argc, char **argv_orig, char **envp) {
|
||||
|
||||
OKF("We're done here. Have a nice day!\n");
|
||||
|
||||
|
||||
remove_shm = 0;
|
||||
afl_shm_deinit(&shm);
|
||||
if (fsrv->use_shmem_fuzz) shm_fuzz = deinit_shmem(fsrv, shm_fuzz);
|
||||
|
Reference in New Issue
Block a user