mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-07-27 04:13:53 -06:00

macOS's Cocoa event handling must be done on the initial (main) thread of the process. Furthermore, if library or application code uses libdispatch, the main dispatch queue must be handling events on the main thread as well. So far, this has affected Qemu in both the Cocoa and SDL UIs, although in different ways: the Cocoa UI replaces the default qemu_main function with one that spins Qemu's internal main event loop off onto a background thread. SDL (which uses Cocoa internally) on the other hand uses a polling approach within Qemu's main event loop. Events are polled during the SDL UI's dpy_refresh callback, which happens to run on the main thread by default. As UIs are mutually exclusive, this works OK as long as nothing else needs platform-native event handling. In the next patch, a new device is introduced based on the ParavirtualizedGraphics.framework in macOS. This uses libdispatch internally, and only works when events are being handled on the main runloop. With the current system, it works when using either the Cocoa or the SDL UI. However, it does not when running headless. Moreover, any attempt to install a similar scheme to the Cocoa UI's main thread replacement fails when combined with the SDL UI. This change tidies up main thread management to be more flexible. * The qemu_main global function pointer is a custom function for the main thread, and it may now be NULL. When it is, the main thread runs the main Qemu loop. This represents the traditional setup. * When non-null, spawning the main Qemu event loop on a separate thread is now done centrally rather than inside the Cocoa UI code. * For most platforms, qemu_main is indeed NULL by default, but on Darwin, it defaults to a function that runs the CFRunLoop. * The Cocoa UI sets qemu_main to a function which runs the NSApplication event handling runloop, as is usual for a Cocoa app. * The SDL UI overrides the qemu_main function to NULL, thus specifying that Qemu's main loop must run on the main thread. * The GTK UI also overrides the qemu_main function to NULL. * For other UIs, or in the absence of UIs, the platform's default behaviour is followed. This means that on macOS, the platform's runloop events are always handled, regardless of chosen UI. The new PV graphics device will thus work in all configurations. There is no functional change on other operating systems. Implementing this via a global function pointer variable is a bit ugly, but it's probably worth investigating the existing UI thread rule violations in the SDL (e.g. #2537) and GTK+ back-ends. Fixing those issues might precipitate requirements similar but not identical to those of the Cocoa UI; hopefully we'll see some kind of pattern emerge, which can then be used as a basis for an overhaul. (In fact, it may turn out to be simplest to split the UI/native platform event thread from the QEMU main event loop on all platforms, with any UI or even none at all.) Signed-off-by: Phil Dennis-Jordan <phil@philjordan.eu> Reviewed-by: Akihiko Odaki <akihiko.odaki@daynix.com> Tested-by: Akihiko Odaki <akihiko.odaki@daynix.com> Message-ID: <20241223221645.29911-2-phil@philjordan.eu> [PMD: Declare 'qemu_main' symbol in tests/qtest/fuzz/fuzz.c, add missing g_assert_not_reached() call in main()] Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org>
242 lines
6.8 KiB
C
242 lines
6.8 KiB
C
/*
|
|
* fuzzing driver
|
|
*
|
|
* Copyright Red Hat Inc., 2019
|
|
*
|
|
* Authors:
|
|
* Alexander Bulekov <alxndr@bu.edu>
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
|
* See the COPYING file in the top-level directory.
|
|
*
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
|
|
#include <wordexp.h>
|
|
|
|
#include "qemu/cutils.h"
|
|
#include "qemu/datadir.h"
|
|
#include "system/system.h"
|
|
#include "system/qtest.h"
|
|
#include "system/runstate.h"
|
|
#include "qemu/main-loop.h"
|
|
#include "qemu/rcu.h"
|
|
#include "tests/qtest/libqtest.h"
|
|
#include "tests/qtest/libqos/qgraph.h"
|
|
#include "fuzz.h"
|
|
|
|
#define MAX_EVENT_LOOPS 10
|
|
|
|
typedef struct FuzzTargetState {
|
|
FuzzTarget *target;
|
|
QSLIST_ENTRY(FuzzTargetState) target_list;
|
|
} FuzzTargetState;
|
|
|
|
typedef QSLIST_HEAD(, FuzzTargetState) FuzzTargetList;
|
|
|
|
static const char *fuzz_arch = TARGET_NAME;
|
|
|
|
static FuzzTargetList *fuzz_target_list;
|
|
static FuzzTarget *fuzz_target;
|
|
static QTestState *fuzz_qts;
|
|
|
|
int (*qemu_main)(void);
|
|
|
|
|
|
void flush_events(QTestState *s)
|
|
{
|
|
int i = MAX_EVENT_LOOPS;
|
|
while (g_main_context_pending(NULL) && i-- > 0) {
|
|
main_loop_wait(false);
|
|
}
|
|
}
|
|
|
|
void fuzz_reset(QTestState *s)
|
|
{
|
|
qemu_system_reset(SHUTDOWN_CAUSE_GUEST_RESET);
|
|
main_loop_wait(true);
|
|
}
|
|
|
|
static QTestState *qtest_setup(void)
|
|
{
|
|
qtest_server_set_send_handler(&qtest_client_inproc_recv, &fuzz_qts);
|
|
return qtest_inproc_init(&fuzz_qts, false, fuzz_arch,
|
|
&qtest_server_inproc_recv);
|
|
}
|
|
|
|
void fuzz_add_target(const FuzzTarget *target)
|
|
{
|
|
FuzzTargetState *tmp;
|
|
FuzzTargetState *target_state;
|
|
if (!fuzz_target_list) {
|
|
fuzz_target_list = g_new0(FuzzTargetList, 1);
|
|
}
|
|
|
|
QSLIST_FOREACH(tmp, fuzz_target_list, target_list) {
|
|
if (g_strcmp0(tmp->target->name, target->name) == 0) {
|
|
fprintf(stderr, "Error: Fuzz target name %s already in use\n",
|
|
target->name);
|
|
abort();
|
|
}
|
|
}
|
|
target_state = g_new0(FuzzTargetState, 1);
|
|
target_state->target = g_new0(FuzzTarget, 1);
|
|
*(target_state->target) = *target;
|
|
QSLIST_INSERT_HEAD(fuzz_target_list, target_state, target_list);
|
|
}
|
|
|
|
|
|
|
|
static void usage(char *path)
|
|
{
|
|
printf("Usage: %s --fuzz-target=FUZZ_TARGET [LIBFUZZER ARGUMENTS]\n", path);
|
|
printf("where FUZZ_TARGET is one of:\n");
|
|
FuzzTargetState *tmp;
|
|
if (!fuzz_target_list) {
|
|
fprintf(stderr, "Fuzz target list not initialized\n");
|
|
abort();
|
|
}
|
|
QSLIST_FOREACH(tmp, fuzz_target_list, target_list) {
|
|
printf(" * %s : %s\n", tmp->target->name,
|
|
tmp->target->description);
|
|
}
|
|
printf("Alternatively, add -target-FUZZ_TARGET to the executable name\n\n"
|
|
"Set the environment variable FUZZ_SERIALIZE_QTEST=1 to serialize\n"
|
|
"QTest commands into an ASCII protocol. Useful for building crash\n"
|
|
"reproducers, but slows down execution.\n\n"
|
|
"Set the environment variable QTEST_LOG=1 to log all qtest commands"
|
|
"\n");
|
|
exit(0);
|
|
}
|
|
|
|
static FuzzTarget *fuzz_get_target(char* name)
|
|
{
|
|
FuzzTargetState *tmp;
|
|
if (!fuzz_target_list) {
|
|
fprintf(stderr, "Fuzz target list not initialized\n");
|
|
abort();
|
|
}
|
|
|
|
QSLIST_FOREACH(tmp, fuzz_target_list, target_list) {
|
|
if (strcmp(tmp->target->name, name) == 0) {
|
|
return tmp->target;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* Sometimes called by libfuzzer to mutate two inputs into one */
|
|
size_t LLVMFuzzerCustomCrossOver(const uint8_t *data1, size_t size1,
|
|
const uint8_t *data2, size_t size2,
|
|
uint8_t *out, size_t max_out_size,
|
|
unsigned int seed)
|
|
{
|
|
if (fuzz_target->crossover) {
|
|
return fuzz_target->crossover(data1, size1, data2, size2, out,
|
|
max_out_size, seed);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Executed for each fuzzing-input */
|
|
int LLVMFuzzerTestOneInput(const unsigned char *Data, size_t Size)
|
|
{
|
|
/*
|
|
* Do the pre-fuzz-initialization before the first fuzzing iteration,
|
|
* instead of before the actual fuzz loop. This is needed since libfuzzer
|
|
* may fork off additional workers, prior to the fuzzing loop, and if
|
|
* pre_fuzz() sets up e.g. shared memory, this should be done for the
|
|
* individual worker processes
|
|
*/
|
|
static int pre_fuzz_done;
|
|
if (!pre_fuzz_done && fuzz_target->pre_fuzz) {
|
|
fuzz_target->pre_fuzz(fuzz_qts);
|
|
pre_fuzz_done = true;
|
|
}
|
|
|
|
fuzz_target->fuzz(fuzz_qts, Data, Size);
|
|
return 0;
|
|
}
|
|
|
|
/* Executed once, prior to fuzzing */
|
|
int LLVMFuzzerInitialize(int *argc, char ***argv, char ***envp)
|
|
{
|
|
|
|
char *target_name;
|
|
GString *cmd_line;
|
|
gchar *pretty_cmd_line;
|
|
bool serialize = false;
|
|
|
|
/* Initialize qgraph and modules */
|
|
qos_graph_init();
|
|
module_call_init(MODULE_INIT_FUZZ_TARGET);
|
|
module_call_init(MODULE_INIT_QOM);
|
|
module_call_init(MODULE_INIT_LIBQOS);
|
|
|
|
qemu_init_exec_dir(**argv);
|
|
target_name = strstr(**argv, "-target-");
|
|
if (target_name) { /* The binary name specifies the target */
|
|
target_name += strlen("-target-");
|
|
} else if (*argc > 1) { /* The target is specified as an argument */
|
|
target_name = (*argv)[1];
|
|
if (!strstr(target_name, "--fuzz-target=")) {
|
|
usage(**argv);
|
|
}
|
|
target_name += strlen("--fuzz-target=");
|
|
} else {
|
|
usage(**argv);
|
|
}
|
|
|
|
/* Should we always serialize qtest commands? */
|
|
if (getenv("FUZZ_SERIALIZE_QTEST")) {
|
|
serialize = true;
|
|
}
|
|
|
|
fuzz_qtest_set_serialize(serialize);
|
|
|
|
/* Identify the fuzz target */
|
|
fuzz_target = fuzz_get_target(target_name);
|
|
if (!fuzz_target) {
|
|
usage(**argv);
|
|
}
|
|
|
|
fuzz_qts = qtest_setup();
|
|
|
|
if (fuzz_target->pre_vm_init) {
|
|
fuzz_target->pre_vm_init();
|
|
}
|
|
|
|
/* Run QEMU's system main with the fuzz-target dependent arguments */
|
|
cmd_line = fuzz_target->get_init_cmdline(fuzz_target);
|
|
g_string_append_printf(cmd_line, " %s -qtest /dev/null ",
|
|
getenv("QTEST_LOG") ? "" : "-qtest-log none");
|
|
|
|
/* Split the runcmd into an argv and argc */
|
|
wordexp_t result;
|
|
wordexp(cmd_line->str, &result, 0);
|
|
g_string_free(cmd_line, true);
|
|
|
|
if (getenv("QTEST_LOG")) {
|
|
pretty_cmd_line = g_strjoinv(" ", result.we_wordv + 1);
|
|
printf("Starting %s with Arguments: %s\n",
|
|
result.we_wordv[0], pretty_cmd_line);
|
|
g_free(pretty_cmd_line);
|
|
}
|
|
|
|
qemu_init(result.we_wordc, result.we_wordv);
|
|
|
|
/* re-enable the rcu atfork, which was previously disabled in qemu_init */
|
|
rcu_enable_atfork();
|
|
|
|
/*
|
|
* Disable QEMU's signal handlers, since we manually control the main_loop,
|
|
* and don't check for main_loop_should_exit
|
|
*/
|
|
signal(SIGINT, SIG_DFL);
|
|
signal(SIGHUP, SIG_DFL);
|
|
signal(SIGTERM, SIG_DFL);
|
|
|
|
return 0;
|
|
}
|