diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 60dfdc2..135e400 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,7 +57,7 @@ jobs: - name: Install dependencies run: | sudo apt -y update - sudo apt -y install docbook-xsl gcc libglib2.0-dev xsltproc meson clang valgrind + sudo apt -y install docbook-xsl gcc libglib2.0-dev libjson-glib-dev xsltproc meson clang valgrind - name: Build run: | @@ -80,7 +80,7 @@ jobs: - name: Install dependencies run: | sudo apt -y update - sudo apt -y install docbook-xsl gcc libglib2.0-dev xsltproc meson lcov + sudo apt -y install docbook-xsl gcc libglib2.0-dev libjson-glib-dev xsltproc meson lcov - name: Build run: | diff --git a/meson.build b/meson.build index 310d0d5..cc6b4aa 100644 --- a/meson.build +++ b/meson.build @@ -8,6 +8,7 @@ project('dfuzzer', 'c', ) libgio = dependency('gio-2.0', required : true) +json_glib = dependency('json-glib-1.0', required : true) xsltproc = find_program('xsltproc', required: false) subdir('src') @@ -15,7 +16,7 @@ subdir('src') executable( 'dfuzzer', dfuzzer_sources, - dependencies : [libgio], + dependencies : [libgio, json_glib], install : true ) diff --git a/src/dfuzzer.c b/src/dfuzzer.c index f7d353e..df3ba50 100644 --- a/src/dfuzzer.c +++ b/src/dfuzzer.c @@ -37,6 +37,8 @@ #include "rand.h" #include "util.h" +/* Shared global variables */ +char *df_log_dir; /** Structure containing D-Bus name, object path and interface of process */ static struct fuzzing_target target_proc = { "", "", "" }; @@ -71,7 +73,6 @@ static char *df_execute_cmd; * written to a [BUS_NAME.log] file */ static int df_full_log_flag; /** Path to directory containing output logs */ -static char *log_dir_name; static guint64 df_max_iterations = G_MAXUINT32; static guint64 df_min_iterations = 10; /** Pointer to a file for full logging */ @@ -94,7 +95,7 @@ int main(int argc, char **argv) df_parse_parameters(argc, argv); if (df_full_log_flag) { - log_file_name = strjoina(log_dir_name, "/", target_proc.name); + log_file_name = strjoina(df_log_dir, "/", target_proc.name); logfile = fopen(log_file_name, "a+"); if(!logfile) { df_fail("Error opening file %s; detailed logs will not be written\n", log_file_name); @@ -873,7 +874,7 @@ void df_parse_parameters(int argc, char **argv) " 'L'\n", argv[0], MAXLEN - 1); exit(1); } - log_dir_name = optarg; + df_log_dir = optarg; df_full_log_flag = 1; break; case 'x': diff --git a/src/dfuzzer.h b/src/dfuzzer.h index 3d9cc1c..70dbb2c 100644 --- a/src/dfuzzer.h +++ b/src/dfuzzer.h @@ -67,6 +67,8 @@ struct suppression_item { char *description; }; +extern char *df_log_dir; + int df_process_bus(GBusType bus_type); /** diff --git a/src/fuzz.c b/src/fuzz.c index 4c61350..8535cd5 100644 --- a/src/fuzz.c +++ b/src/fuzz.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -48,6 +49,9 @@ static char df_except_counter = 0; static void df_fuzz_write_log(const struct df_dbus_method *method, GVariant *value); static int df_exec_cmd_check(const char *cmd); static int df_fuzz_call_method(const struct df_dbus_method *method, GVariant *value); +static void df_fuzz_dump_reproducer(const char *bus, const char *object, const char *interface, + const char *method, const char *signature, GVariant *value, + const char *type); guint64 df_get_number_of_iterations(const char *signature) { guint64 iterations = 0; @@ -399,6 +403,9 @@ static int df_check_if_exited(const int pid) { assert(pid > 0); sprintf(proc_pid, "/proc/%d/status", pid); + /* FIXME: check correctly if the target responds before checking its PID, + * otherwise we might miss the crash */ + usleep(50000); f = fopen(proc_pid, "r"); if (!f) { @@ -533,6 +540,8 @@ int df_fuzz_test_method( df_fuzz_write_log(method, value); } + df_fuzz_dump_reproducer(name, obj, intf, method->name, method->signature, value, "fail"); + df_fail(" reproducer: %sdfuzzer -v -n %s -o %s -i %s -t %s", ansi_yellow(), name, obj, intf, method->name); if (buf_size_flg) @@ -625,3 +634,74 @@ static int df_fuzz_call_method(const struct df_dbus_method *method, GVariant *va return 0; } + +static void df_fuzz_dump_reproducer(const char *bus, const char *object, const char *interface, + const char *method, const char *signature, GVariant *value, + const char *type) +{ + g_autoptr(JsonBuilder) builder = NULL; + g_autoptr(JsonNode) root = NULL; + g_autoptr(JsonGenerator) generator = NULL; + g_autoptr (GError) error = NULL; + g_autofree gchar *filename = NULL; + + /* No log dir set, don't dump anything */ + if (!df_log_dir) + return; + + assert(bus); + assert(object); + assert(method); + assert(signature); + assert(value); + + /* Generate a JSON tree + * + * Current structure: + * { + * "bus": , + * "object": , + * "interface": , + * "method": , + * "payload": { + * "signature": , + * "data": + * } + * } + */ + builder = json_builder_new(); + json_builder_begin_object(builder); + + json_builder_set_member_name(builder, "bus"); + json_builder_add_string_value(builder, bus); + json_builder_set_member_name(builder, "object"); + json_builder_add_string_value(builder, object); + json_builder_set_member_name(builder, "interface"); + json_builder_add_string_value(builder, interface); + json_builder_set_member_name(builder, "method"); + json_builder_add_string_value(builder, method); + + json_builder_set_member_name(builder, "payload"); + json_builder_begin_object(builder); + json_builder_set_member_name(builder, "signature"); + json_builder_add_string_value(builder, signature); + json_builder_set_member_name(builder, "data"); + json_builder_add_value(builder, json_gvariant_serialize(value)); + json_builder_end_object(builder); + + json_builder_end_object(builder); + + /* Serialize the whole JSON tree */ + root = json_builder_get_root(builder); + generator = json_generator_new(); + json_generator_set_root(generator, root); + json_generator_set_pretty(generator, TRUE); + + /* Dump it into a file */ + filename = g_strdup_printf("%s/%s-%s-%"G_GINT64_FORMAT"-%s.json", df_log_dir, + bus, method, g_get_real_time(), type); + if (!json_generator_to_file(generator, filename, &error)) + df_fail("Failed to dump reproducer into file '%s': %s\n", filename, error->message); + + df_fail(" Wrote the reproducer into file '%s'\n", filename); +}