Commit c4ca8e25 authored by Richard Hughes's avatar Richard Hughes
Browse files

Use honggfuzz to fuzz firmware rather than AFL

This has better multi-core performance and can run in persistent mode -- which
allows us to construct a test harness of all the parsers (which takes time) and
then just reuse the process for lots of different data.
parent 531b8b41
......@@ -133,9 +133,9 @@ Fuzzing
There are several automated fuzzing tests in fwupd. These take some time to run:
CC=afl-gcc meson --default-library=static ../
AFL_HARDEN=1 ninja
ninja fuzz-synaptics-rmi
CC=hfuzz-clang meson --default-library=static -Dtmpdir=/tmp -Dsystemd_root_prefix=/tmp ../
ninja install
ninja fuzz-firmware
ninja fuzz-smbios
ninja fuzz-efidbx
ninja fuzz-tpm-eventlog
#!/usr/bin/python3
# SPDX-License-Identifier: LGPL-2.1+
import argparse
import sys
import subprocess
import os
def main():
parser = argparse.ArgumentParser(description='Run afl-fuzz on all cores')
parser.add_argument('--input', '-i', help='fuzzing input directory')
parser.add_argument('--output', '-o', help='findings output directory')
parser.add_argument('--command', type=str, help='fuzzer tool command')
parser.add_argument('path', type=str, help='the fuzzer tool')
args = parser.parse_args()
if not args.input and not args.output:
print('-i and -o required')
return 1
if not args.path:
print('tool name required')
return 1
# create if not already exists
if not os.path.exists(args.output):
os.makedirs(args.output)
# run the main instance
envp = None
argv = [
'afl-fuzz',
'-m300',
'-i',
args.input,
'-o',
args.output,
'-M',
'fuzzer00',
args.path,
]
if args.command:
argv.append(args.command)
argv.append('@@')
print(argv)
p = subprocess.Popen(argv, env=envp)
# run the secondary instances
cs = []
for i in range(1, os.cpu_count()):
argv = [
'afl-fuzz',
'-m300',
'-i',
args.input,
'-o',
args.output,
'-S',
'fuzzer%02i' % i,
args.path,
]
if args.command:
argv.append(args.command)
argv.append('@@')
print(argv)
cs.append(subprocess.Popen(argv, env=envp, stdout=subprocess.DEVNULL))
# wait for the main instance
try:
p.wait()
except KeyboardInterrupt as _:
pass
for c in cs:
c.terminate()
return 0
if __name__ == '__main__':
sys.exit(main())
......@@ -73,7 +73,6 @@ warning_flags = [
'-Wformat-signedness',
'-Wignored-qualifiers',
'-Wimplicit-function-declaration',
'-Wincompatible-pointer-types-discards-qualifiers',
'-Winit-self',
'-Wlogical-op',
'-Wmaybe-uninitialized',
......@@ -113,6 +112,12 @@ warning_flags = [
cc = meson.get_compiler('c')
add_project_arguments(cc.get_supported_arguments(warning_flags), language : 'c')
# use honggfuzz for parallel persistent fuzzing
honggfuzz = find_program('honggfuzz', required: cc.has_function('HF_ITER'))
if honggfuzz.found()
conf.set('HAVE_HF_ITER', '1')
endif
if not meson.is_cross_build()
add_project_arguments('-fstack-protector-strong', language : 'c')
endif
......
Fuzzing
=======
CC=afl-gcc meson --default-library=static ../
AFL_HARDEN=1 ninja
afl-fuzz -m 300 -i fuzzing -o findings ./plugins/dfu/dfu-tool --force dump @@
run_target('fuzz-optionrom',
command: [
join_paths(meson.source_root(), 'contrib/afl-fuzz.py'),
'--command', 'rom',
'-i', meson.current_source_dir(),
'-o', join_paths(meson.current_build_dir(), '..', 'findings'),
optionrom_tool,
],
)
......@@ -79,5 +79,17 @@ if get_option('tests')
install_dir : installed_test_bindir,
)
test('optionrom-self-test', e, env : testdatadirs) # added to installed-tests
subdir('fuzzing')
endif
if honggfuzz.found()
run_target('fuzz-optionrom',
command: [
honggfuzz,
'--input', join_paths(meson.current_source_dir(), 'fuzzing'),
'--output', join_paths(meson.current_build_dir(), 'fuzzing-corpus'),
'--workspace', join_paths(meson.current_build_dir(), 'fuzzing-findings'),
'--verifier',
'--', optionrom_tool, 'rom', '___FILE___',
],
)
endif
synaptics_example0x = custom_target('example0x.img',
output: 'example0x.img',
command: [synaptics_rmi_dump, 'gen0x', '@OUTPUT@'],
)
synaptics_example10 = custom_target('example10.img',
output: 'example10.img',
command: [synaptics_rmi_dump, 'gen10', '@OUTPUT@'],
)
run_target('fuzz-synaptics-rmi',
command: [
join_paths(meson.source_root(), 'contrib/afl-fuzz.py'),
'-i', meson.current_build_dir(),
'-o', join_paths(meson.current_build_dir(), '..', 'findings'),
synaptics_rmi_dump,
],
depends: [
synaptics_example0x,
synaptics_example10,
],
)
......@@ -56,5 +56,4 @@ if get_option('tests')
],
c_args : cargs
)
subdir('fuzzing')
endif
......@@ -98,11 +98,15 @@ if get_option('man')
)
endif
run_target('fuzz-tpm-eventlog',
command: [
join_paths(meson.source_root(), 'contrib/afl-fuzz.py'),
'-i', join_paths(meson.current_source_dir(), 'tests'),
'-o', join_paths(meson.current_build_dir(), 'findings'),
fwupdtpmevlog,
],
)
if honggfuzz.found()
run_target('fuzz-tpm-eventlog',
command: [
honggfuzz,
'--input', join_paths(meson.current_source_dir(), 'fuzzing'),
'--output', join_paths(meson.current_build_dir(), 'fuzzing-corpus'),
'--workspace', join_paths(meson.current_build_dir(), 'fuzzing-findings'),
'--verifier',
'--', fwupdtpmevlog, '___FILE___',
],
)
endif
......@@ -137,12 +137,15 @@ if get_option('man')
)
endif
run_target('fuzz-efidbx',
command: [
join_paths(meson.source_root(), 'contrib/afl-fuzz.py'),
'-i', join_paths(meson.current_source_dir(), 'fuzzing'),
'-o', join_paths(meson.current_build_dir(), 'findings'),
'--command', uefi_dbx_fuzzer,
fwupdtool,
],
)
if honggfuzz.found()
run_target('fuzz-efidbx',
command: [
honggfuzz,
'--input', join_paths(meson.current_source_dir(), 'fuzzing'),
'--output', join_paths(meson.current_build_dir(), 'fuzzing-corpus'),
'--workspace', join_paths(meson.current_build_dir(), 'fuzzing-findings'),
'--verifier',
'--', uefi_dbx_fuzzer, '___FILE___',
],
)
endif
/*
* Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
* Copyright (C) 2019-2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-common.h"
#include "fu-srec-firmware.h"
#include "fu-ihex-firmware.h"
#include <glib/gi18n.h>
#include <locale.h>
#include <stdlib.h>
#include "fu-engine.h"
typedef struct {
gboolean verbose;
gint timeout; /* ms */
GPtrArray *array; /* element-type FuFirmware */
FuEngine *engine;
} FuUtil;
extern void HF_ITER(guint8 **buf, gsize *len);
static gboolean
fu_firmware_dump_parse (FuUtil *self,
FuFirmware *firmware,
GBytes *fw,
GError **error)
{
gboolean ret;
gdouble elapsed_ms;
g_autoptr(GError) error_local = NULL;
g_autoptr(GTimer) timer = g_timer_new ();
/* parse, relaxing all the restrictions */
ret = fu_firmware_parse (firmware, fw,
FWUPD_INSTALL_FLAG_NO_SEARCH |
FWUPD_INSTALL_FLAG_IGNORE_VID_PID |
FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM,
&error_local);
/* a timeout is more important than the actual parse failure */
elapsed_ms = g_timer_elapsed (timer, NULL) * 1000;
if (self->timeout > 0 &&
elapsed_ms > self->timeout) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_TIMED_OUT,
"%s took %.1fms (more than limit of %ims)",
G_OBJECT_TYPE_NAME (firmware),
elapsed_ms,
self->timeout);
return FALSE;
}
/* success? */
if (!ret) {
g_propagate_prefixed_error (error,
g_steal_pointer (&error_local),
"%s failed in %.0lfms: ",
G_OBJECT_TYPE_NAME (firmware),
elapsed_ms);
return FALSE;
}
return TRUE;
}
static gboolean
fu_firmware_dump_iter (FuUtil *self, GBytes *blob, GError **error)
{
gboolean any_okay = FALSE;
for (guint i = 0; i < self->array->len; i++) {
FuFirmware *firmware = g_ptr_array_index (self->array, i);
g_autoptr(GError) error_local = NULL;
g_autofree gchar *str = NULL;
if (!fu_firmware_dump_parse (self, firmware, blob, &error_local)) {
/* timeout so bail */
if (g_error_matches (error_local,
G_IO_ERROR,
G_IO_ERROR_TIMED_OUT)) {
g_propagate_error (error, g_steal_pointer (&error_local));
return FALSE;
}
g_printerr ("%s\n", error_local->message);
continue;
}
str = fu_firmware_to_string (firmware);
g_print ("%s", str);
any_okay = TRUE;
}
if (!any_okay) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to parse");
return FALSE;
}
return TRUE;
}
static void
fu_firmware_dump_log_cb (const gchar *log_domain,
GLogLevelFlags log_level,
const gchar *message,
gpointer user_data)
{
FuUtil *self = (FuUtil *) user_data;
if (log_level == G_LOG_LEVEL_CRITICAL) {
g_printerr ("CRITICAL: %s\n", message);
g_assert_not_reached ();
}
if (self->verbose)
g_printerr ("DEBUG: %s\n", message);
}
static void
fu_util_private_free (FuUtil *self)
{
if (self->array != NULL)
g_ptr_array_unref (self->array);
if (self->engine != NULL)
g_object_unref (self->engine);
g_free (self);
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuUtil, fu_util_private_free)
#pragma clang diagnostic pop
int
main (int argc, char **argv)
{
g_autofree gchar *str = NULL;
g_autoptr(FuFirmware) firmware = NULL;
g_autoptr(GBytes) blob = NULL;
g_autoptr(GError) error = NULL;
gsize sz = 0;
const guint8 *buf;
g_autoptr(GPtrArray) firmware_types = NULL;
g_autoptr(GOptionContext) context = NULL;
g_autoptr(FuUtil) self = g_new0 (FuUtil, 1);
const GOptionEntry options[] = {
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &self->verbose,
/* TRANSLATORS: command line option */
_("Show extra debugging information"), NULL },
{ "timeout", 't', 0, G_OPTION_ARG_INT, &self->timeout,
/* TRANSLATORS: command line option */
_("Timeout in milliseconds for each parse"), NULL },
{ NULL}
};
/* no args */
if (argc != 2) {
g_printerr ("firmware filename required\n");
return 2;
setlocale (LC_ALL, "");
bindtextdomain (GETTEXT_PACKAGE, FWUPD_LOCALEDIR);
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
textdomain (GETTEXT_PACKAGE);
context = g_option_context_new (NULL);
g_option_context_add_main_entries (context, options, NULL);
if (!g_option_context_parse (context, &argc, &argv, &error)) {
/* TRANSLATORS: the user didn't read the man page */
g_printerr ("%s: %s\n", _("Failed to parse arguments"),
error->message);
return EXIT_FAILURE;
}
/* load firmware */
blob = fu_common_get_contents_bytes (argv[1], &error);
if (blob == NULL) {
g_printerr ("failed to load file: %s\n", error->message);
/* args */
if (self->verbose) {
g_setenv ("G_MESSAGES_DEBUG", "all", FALSE);
g_setenv ("FWUPD_VERBOSE", "1", FALSE);
}
/* crashy mccrash face */
g_log_set_default_handler (fu_firmware_dump_log_cb, self);
/* load engine */
self->engine = fu_engine_new (FU_APP_FLAGS_NO_IDLE_SOURCES);
if (!fu_engine_load (self->engine, FU_ENGINE_LOAD_FLAG_READONLY, &error)) {
g_printerr ("Failed to load engine: %s\n", error->message);
return 1;
}
buf = g_bytes_get_data (blob, &sz);
if (sz < 2) {
g_printerr ("firmware invalid\n");
return 2;
}
if (buf[0] == 'S' && buf[1] == '0') {
firmware = fu_srec_firmware_new ();
} else if (buf[0] == ':') {
firmware = fu_ihex_firmware_new ();
} else {
g_printerr ("firmware invalid type, expected .srec or .hex\n");
return 2;
}
if (!fu_firmware_parse (firmware, blob,
FWUPD_INSTALL_FLAG_IGNORE_VID_PID |
FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM,
&error)) {
g_printerr ("failed to parse file: %s\n", error->message);
return 3;
}
str = fu_firmware_to_string (firmware);
g_print ("%s", str);
/* get all parser objects */
firmware_types = fu_engine_get_firmware_gtype_ids (self->engine);
self->array = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
for (guint i = 0; i < firmware_types->len; i++) {
const gchar *id = g_ptr_array_index (firmware_types, i);
GType gtype = fu_engine_get_firmware_gtype_by_id (self->engine, id);
g_ptr_array_add (self->array, g_object_new (gtype, NULL));
}
/* no args */
if (argc >= 2) {
gint rc = 0;
for (gint i = 1; i < argc; i++) {
g_autoptr(GBytes) blob = NULL;
g_autoptr(GError) error_local = NULL;
blob = fu_common_get_contents_bytes (argv[i], &error_local);
if (blob == NULL) {
g_printerr ("failed to load file %s: %s\n",
argv[i], error_local->message);
rc = 2;
continue;
}
if (!fu_firmware_dump_iter (self, blob, &error_local)) {
g_printerr ("failed to parse file %s: %s\n",
argv[i], error_local->message);
if (g_error_matches (error_local,
G_IO_ERROR,
G_IO_ERROR_TIMED_OUT)) {
return 4;
}
rc = 3;
continue;
}
}
return rc;
}
for (;;) {
gsize len = 0;
guint8 *buf = NULL;
g_autoptr(GBytes) blob = NULL;
#ifdef HAVE_HF_ITER
HF_ITER(&buf, &len);
#endif
blob = g_bytes_new_static (buf, len);
for (guint i = 0; i < self->array->len; i++) {
FuFirmware *firmware = g_ptr_array_index (self->array, i);
g_autoptr(GError) error_local = NULL;
if (!fu_firmware_dump_parse (self, firmware, blob, &error_local)) {
if (g_error_matches (error_local,
G_IO_ERROR,
G_IO_ERROR_TIMED_OUT)) {
g_error ("%s", error_local->message);
}
g_assert (error_local != NULL);
continue;
}
}
}
return 0;
}
<firmware gtype="FuBcm57xxFirmware">
<version>1.2.3</version>
<image gtype="FuBcm57xxStage1Image">
<version>7.8.9</version>
<id>stage1</id>
<idx>0x01</idx>
<filename>bcm57xx-stage1.bin</filename>
</image>
<image gtype="FuBcm57xxStage2Image">
<id>stage2</id>
<data/> <!-- empty! -->
</image>
<image gtype="FuBcm57xxDictImage">
<id>ape</id>
<addr>0x7</addr>
<data>aGVsbG8gd29ybGQ=</data> <!-- base64 -->
</image>
</firmware>
<firmware gtype="FuCrosEcFirmware">
<base>0x3000</base>
<image>
<id>RO_FRID</id>
<data>Y2hlZXNlX3YxLjEuMTc1NS00ZGE5NTIwICAgICAgICA=</data>
</image>
<image>
<id>RW_FWID</id>
<data>Y2hlZXNlX3YxLjEuMTc1NS00ZGE5NTIwICAgICAgICA=</data>
</image>
<image>
<id>EC_RO</id>
<data>Y2hlZXNlX3YxLjEuMTc1NS00ZGE5NTIwICAgICAgICA=</data>
</image>
<image>
<id>EC_RW</id>
<data>Y2hlZXNlX3YxLjEuMTc1NS00ZGE5NTIwICAgICAgICA=</data>
</image>
</firmware>
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment