qemu/tests/qtest/fuzz/fuzz.c
Phil Dennis-Jordan f5ab12caba ui & main loop: Redesign of system-specific main thread event handling
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>
2024-12-31 21:21:34 +01:00

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;
}