From 7db7d5b73368fde010a258a5b2c48faf0d3e82c8 Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Sat, 13 Jan 2018 19:51:07 -0500 Subject: [PATCH 1/5] fuzz: add initial fuzzing infrastructure The fuzzers will be used by oss-fuzz to automatically and continuously fuzz systemd. This commit includes the build tooling necessary to build fuzz targets, and a fuzzer for the DNS packet parser. --- meson.build | 51 ++++++++++++++++++++++++++++++-- meson_options.txt | 3 ++ scripts/oss-fuzz.sh | 36 ++++++++++++++++++++++ src/fuzz/fuzz-dns-packet.c | 43 +++++++++++++++++++++++++++ src/fuzz/fuzz-dns-packet.options | 2 ++ src/fuzz/fuzz-main.c | 51 ++++++++++++++++++++++++++++++++ src/fuzz/fuzz.h | 23 ++++++++++++++ src/fuzz/meson.build | 25 ++++++++++++++++ tools/meson-check-api-docs.sh | 0 9 files changed, 232 insertions(+), 2 deletions(-) create mode 100755 scripts/oss-fuzz.sh create mode 100644 src/fuzz/fuzz-dns-packet.c create mode 100644 src/fuzz/fuzz-dns-packet.options create mode 100644 src/fuzz/fuzz-main.c create mode 100644 src/fuzz/fuzz.h create mode 100644 src/fuzz/meson.build mode change 100644 => 100755 tools/meson-check-api-docs.sh diff --git a/meson.build b/meson.build index 5066ccf432..784a138f2f 100644 --- a/meson.build +++ b/meson.build @@ -268,6 +268,11 @@ if get_option('tests') != 'false' endif endif +ossfuzz = get_option('oss-fuzz') +if ossfuzz + fuzzing_engine = meson.get_compiler('cpp').find_library('FuzzingEngine') +endif + foreach arg : ['-Wextra', '-Werror=undef', '-Wlogical-op', @@ -302,7 +307,6 @@ foreach arg : ['-Wextra', '-fvisibility=hidden', '-fstack-protector', '-fstack-protector-strong', - '-fPIE', '--param=ssp-buffer-size=4', ] if cc.has_argument(arg) @@ -310,6 +314,14 @@ foreach arg : ['-Wextra', endif endforeach +# the oss-fuzz fuzzers are not built with -fPIE, so don't +# enable it when we are linking against them +if not ossfuzz + if cc.has_argument('-fPIE') + add_project_arguments('-fPIE', language : 'c') + endif +endif + # "negative" arguments: gcc on purpose does not return an error for "-Wno-" # arguments, just emits a warnings. So test for the "positive" version instead. foreach arg : ['unused-parameter', @@ -360,7 +372,7 @@ foreach arg : ['-Wl,-z,relro', cc.cmd_array(), '-x', 'c', arg, '-include', link_test_c).returncode() == 0 message('Linking with @0@ supported: @1@'.format(arg, have ? 'yes' : 'no')) - if have + if have and (arg != '-pie' or not ossfuzz) add_project_link_arguments(arg, language : 'c') endif endforeach @@ -1178,6 +1190,7 @@ endforeach want_tests = get_option('tests') install_tests = get_option('install-tests') tests = [] +fuzzers = [] conf.set10('SYSTEMD_SLOW_TESTS_DEFAULT', get_option('slow-tests')) @@ -1303,6 +1316,7 @@ subdir('src/vconsole') subdir('src/boot/efi') subdir('src/test') +subdir('src/fuzz') subdir('rules') subdir('test') @@ -2457,6 +2471,39 @@ test('test-libudev-sym', ############################################################ +fuzzer_exes = [] + +foreach tuple : fuzzers + sources = tuple[0] + link_with = tuple[1].length() > 0 ? tuple[1] : [libshared] + dependencies = tuple[2] + defs = tuple.length() >= 4 ? tuple[3] : [] + incs = tuple.length() >= 5 ? tuple[4] : includes + + if ossfuzz + dependencies += fuzzing_engine + else + sources += 'src/fuzz/fuzz-main.c' + endif + + name = sources[0].split('/')[-1].split('.')[0] + + fuzzer_exes += executable( + name, + sources, + include_directories : [incs, include_directories('src/fuzz')], + link_with : link_with, + dependencies : dependencies, + c_args : defs, + install : false) +endforeach + +run_target('fuzzers', + depends : fuzzer_exes, + command : ['true']) + +############################################################ + make_directive_index_py = find_program('tools/make-directive-index.py') make_man_index_py = find_program('tools/make-man-index.py') xml_helper_py = find_program('tools/xml_helper.py') diff --git a/meson_options.txt b/meson_options.txt index af8be576a1..9e7cad7471 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -291,3 +291,6 @@ option('slow-tests', type : 'boolean', value : 'false', description : 'run the slow tests by default') option('install-tests', type : 'boolean', value : 'false', description : 'install test executables') + +option('oss-fuzz', type : 'boolean', value : 'false', + description : 'build against oss-fuzz') diff --git a/scripts/oss-fuzz.sh b/scripts/oss-fuzz.sh new file mode 100755 index 0000000000..b871d744b9 --- /dev/null +++ b/scripts/oss-fuzz.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# SPDX-License-Identifier: LGPL-2.1+ +# +# Copyright 2017 Jonathan Rudenberg +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see . + +set -ex + +export LC_CTYPE=C.UTF-8 + +meson $WORK -Doss-fuzz=true -Db_lundef=false -Dseccomp=false -Dlz4=false -Dlibiptc=false -Dlibidn=false +ninja -C $WORK fuzzers + +# get DNS packet corpus +df=$WORK/dns-fuzzing +rm -rf $df +git clone --depth 1 https://github.com/CZ-NIC/dns-fuzzing $df +zip -jqr $OUT/fuzz-dns-packet_seed_corpus.zip $df/packet + +mkdir -p $OUT/src/shared +mv $WORK/src/shared/libsystemd-shared-*.so $OUT/src/shared + +find $WORK -maxdepth 1 -type f -executable -name "fuzz-*" -exec mv {} $OUT \; +mv $WORK/*.so src/fuzz/*.options $OUT diff --git a/src/fuzz/fuzz-dns-packet.c b/src/fuzz/fuzz-dns-packet.c new file mode 100644 index 0000000000..3d8d79a42d --- /dev/null +++ b/src/fuzz/fuzz-dns-packet.c @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +/*** + Copyright 2018 Jonathan Rudenberg + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "fuzz.h" +#include "resolved-dns-packet.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + int r; + + if (size > DNS_PACKET_SIZE_MAX) + return 0; + + r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0, DNS_PACKET_SIZE_MAX); + if (r < 0) + return 0; + p->size = 0; /* by default append starts after the header, undo that */ + dns_packet_append_blob(p, data, size, NULL); + if (size < DNS_PACKET_HEADER_SIZE) { + /* make sure we pad the packet back up to the minimum header size */ + assert(p->allocated >= DNS_PACKET_HEADER_SIZE); + memzero(DNS_PACKET_DATA(p) + size, DNS_PACKET_HEADER_SIZE - size); + p->size = DNS_PACKET_HEADER_SIZE; + } + dns_packet_extract(p); + + return 0; +} diff --git a/src/fuzz/fuzz-dns-packet.options b/src/fuzz/fuzz-dns-packet.options new file mode 100644 index 0000000000..0824b19fab --- /dev/null +++ b/src/fuzz/fuzz-dns-packet.options @@ -0,0 +1,2 @@ +[libfuzzer] +max_len = 65535 diff --git a/src/fuzz/fuzz-main.c b/src/fuzz/fuzz-main.c new file mode 100644 index 0000000000..ca9abe9068 --- /dev/null +++ b/src/fuzz/fuzz-main.c @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +/*** + Copyright 2018 Jonathan Rudenberg + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "alloc-util.h" +#include "log.h" +#include "fileio.h" +#include "fuzz.h" + +/* This is a test driver for the systemd fuzzers that provides main function + * for regression testing outside of oss-fuzz (https://github.com/google/oss-fuzz) + * + * It reads files named on the command line and passes them one by one into the + * fuzzer that it is compiled into. */ + +int main(int argc, char **argv) { + int i, r; + size_t size; + char *name; + + log_set_max_level(LOG_DEBUG); + for (i = 1; i < argc; i++) { + _cleanup_free_ char *buf = NULL; + + name = argv[i]; + r = read_full_file(name, &buf, &size); + if (r < 0) { + log_error_errno(r, "Failed to open '%s': %m", name); + return EXIT_FAILURE; + } + printf("%s... ", name); + fflush(stdout); + (void)LLVMFuzzerTestOneInput((uint8_t*)buf, size); + printf("ok\n"); + } + return EXIT_SUCCESS; +} diff --git a/src/fuzz/fuzz.h b/src/fuzz/fuzz.h new file mode 100644 index 0000000000..5293c5762f --- /dev/null +++ b/src/fuzz/fuzz.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +/*** + Copyright 2018 Jonathan Rudenberg + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +/* The entry point into the fuzzer */ +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); diff --git a/src/fuzz/meson.build b/src/fuzz/meson.build new file mode 100644 index 0000000000..66cdf13dbd --- /dev/null +++ b/src/fuzz/meson.build @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: LGPL-2.1+ +# Copyright 2018 Jonathan Rudenberg +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see . + +fuzzers += [ + [['src/fuzz/fuzz-dns-packet.c', + dns_type_headers], + [libsystemd_resolve_core, + libshared], + [libgcrypt, + libgpg_error, + libm]], +] diff --git a/tools/meson-check-api-docs.sh b/tools/meson-check-api-docs.sh old mode 100644 new mode 100755 From b4081f3ea2009f1c3b015237f2bcf12656ffa56f Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Mon, 15 Jan 2018 18:27:37 -0500 Subject: [PATCH 2/5] fuzz: disable all deps when building with oss-fuzz The fuzz targets are intended to be fast and only target systemd code, so they don't need to call out to any dependencies. They also shouldn't depend on shared libraries outside of libc, so we disable every dependency when compiling against oss-fuzz. This also simplifies the upstream build environment significantly. --- meson.build | 58 ++++++++++++++++++++++++--------------------- scripts/oss-fuzz.sh | 2 +- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/meson.build b/meson.build index 784a138f2f..df79a54026 100644 --- a/meson.build +++ b/meson.build @@ -779,10 +779,11 @@ if not libcap.found() endif libmount = dependency('mount', - version : '>= 2.30') + version : '>= 2.30', + required : not ossfuzz) want_seccomp = get_option('seccomp') -if want_seccomp != 'false' +if want_seccomp != 'false' and not ossfuzz libseccomp = dependency('libseccomp', version : '>= 2.3.1', required : want_seccomp == 'true') @@ -795,7 +796,7 @@ conf.set10('HAVE_SECCOMP', have) m4_defines += have ? ['-DHAVE_SECCOMP'] : [] want_selinux = get_option('selinux') -if want_selinux != 'false' +if want_selinux != 'false' and not ossfuzz libselinux = dependency('libselinux', version : '>= 2.1.9', required : want_selinux == 'true') @@ -808,7 +809,7 @@ conf.set10('HAVE_SELINUX', have) m4_defines += have ? ['-DHAVE_SELINUX'] : [] want_apparmor = get_option('apparmor') -if want_apparmor != 'false' +if want_apparmor != 'false' and not ossfuzz libapparmor = dependency('libapparmor', required : want_apparmor == 'true') have = libapparmor.found() @@ -828,7 +829,7 @@ endif want_polkit = get_option('polkit') install_polkit = false install_polkit_pkla = false -if want_polkit != 'false' +if want_polkit != 'false' and not ossfuzz install_polkit = true libpolkit = dependency('polkit-gobject-1', @@ -841,7 +842,7 @@ endif conf.set10('ENABLE_POLKIT', install_polkit) want_acl = get_option('acl') -if want_acl != 'false' +if want_acl != 'false' and not ossfuzz libacl = cc.find_library('acl', required : want_acl == 'true') have = libacl.found() else @@ -852,7 +853,7 @@ conf.set10('HAVE_ACL', have) m4_defines += have ? ['-DHAVE_ACL'] : [] want_audit = get_option('audit') -if want_audit != 'false' +if want_audit != 'false' and not ossfuzz libaudit = dependency('audit', required : want_audit == 'true') have = libaudit.found() else @@ -862,7 +863,7 @@ endif conf.set10('HAVE_AUDIT', have) want_blkid = get_option('blkid') -if want_blkid != 'false' +if want_blkid != 'false' and not ossfuzz libblkid = dependency('blkid', required : want_blkid == 'true') have = libblkid.found() else @@ -872,7 +873,7 @@ endif conf.set10('HAVE_BLKID', have) want_kmod = get_option('kmod') -if want_kmod != 'false' +if want_kmod != 'false' and not ossfuzz libkmod = dependency('libkmod', version : '>= 15', required : want_kmod == 'true') @@ -884,7 +885,7 @@ endif conf.set10('HAVE_KMOD', have) want_pam = get_option('pam') -if want_pam != 'false' +if want_pam != 'false' and not ossfuzz libpam = cc.find_library('pam', required : want_pam == 'true') libpam_misc = cc.find_library('pam_misc', required : want_pam == 'true') have = libpam.found() and libpam_misc.found() @@ -897,7 +898,7 @@ conf.set10('HAVE_PAM', have) m4_defines += have ? ['-DHAVE_PAM'] : [] want_microhttpd = get_option('microhttpd') -if want_microhttpd != 'false' +if want_microhttpd != 'false' and not ossfuzz libmicrohttpd = dependency('libmicrohttpd', version : '>= 0.9.33', required : want_microhttpd == 'true') @@ -910,7 +911,7 @@ conf.set10('HAVE_MICROHTTPD', have) m4_defines += have ? ['-DHAVE_MICROHTTPD'] : [] want_libcryptsetup = get_option('libcryptsetup') -if want_libcryptsetup != 'false' +if want_libcryptsetup != 'false' and not ossfuzz libcryptsetup = dependency('libcryptsetup', version : '>= 1.6.0', required : want_libcryptsetup == 'true') @@ -922,7 +923,7 @@ endif conf.set10('HAVE_LIBCRYPTSETUP', have) want_libcurl = get_option('libcurl') -if want_libcurl != 'false' +if want_libcurl != 'false' and not ossfuzz libcurl = dependency('libcurl', version : '>= 7.32.0', required : want_libcurl == 'true') @@ -940,7 +941,7 @@ if want_libidn == 'true' and want_libidn2 == 'true' error('libidn and libidn2 cannot be requested simultaneously') endif -if want_libidn != 'false' and want_libidn2 != 'true' +if want_libidn != 'false' and want_libidn2 != 'true' and not ossfuzz libidn = dependency('libidn', required : want_libidn == 'true') have = libidn.found() @@ -950,7 +951,7 @@ else endif conf.set10('HAVE_LIBIDN', have) m4_defines += have ? ['-DHAVE_LIBIDN'] : [] -if not have and want_libidn2 != 'false' +if not have and want_libidn2 != 'false' and not ossfuzz # libidn is used for both libidn and libidn2 objects libidn = dependency('libidn2', required : want_libidn2 == 'true') @@ -962,7 +963,7 @@ conf.set10('HAVE_LIBIDN2', have) m4_defines += have ? ['-DHAVE_LIBIDN2'] : [] want_libiptc = get_option('libiptc') -if want_libiptc != 'false' +if want_libiptc != 'false' and not ossfuzz libiptc = dependency('libiptc', required : want_libiptc == 'true') have = libiptc.found() @@ -974,7 +975,7 @@ conf.set10('HAVE_LIBIPTC', have) m4_defines += have ? ['-DHAVE_LIBIPTC'] : [] want_qrencode = get_option('qrencode') -if want_qrencode != 'false' +if want_qrencode != 'false' and not ossfuzz libqrencode = dependency('libqrencode', required : want_qrencode == 'true') have = libqrencode.found() @@ -985,7 +986,7 @@ endif conf.set10('HAVE_QRENCODE', have) want_gcrypt = get_option('gcrypt') -if want_gcrypt != 'false' +if want_gcrypt != 'false' and not ossfuzz libgcrypt = cc.find_library('gcrypt', required : want_gcrypt == 'true') libgpg_error = cc.find_library('gpg-error', required : want_gcrypt == 'true') have = libgcrypt.found() and libgpg_error.found() @@ -1000,7 +1001,7 @@ endif conf.set10('HAVE_GCRYPT', have) want_gnutls = get_option('gnutls') -if want_gnutls != 'false' +if want_gnutls != 'false' and not ossfuzz libgnutls = dependency('gnutls', version : '>= 3.1.4', required : want_gnutls == 'true') @@ -1012,7 +1013,7 @@ endif conf.set10('HAVE_GNUTLS', have) want_elfutils = get_option('elfutils') -if want_elfutils != 'false' +if want_elfutils != 'false' and not ossfuzz libdw = dependency('libdw', required : want_elfutils == 'true') have = libdw.found() @@ -1023,7 +1024,7 @@ endif conf.set10('HAVE_ELFUTILS', have) want_zlib = get_option('zlib') -if want_zlib != 'false' +if want_zlib != 'false' and not ossfuzz libz = dependency('zlib', required : want_zlib == 'true') have = libz.found() @@ -1034,7 +1035,7 @@ endif conf.set10('HAVE_ZLIB', have) want_bzip2 = get_option('bzip2') -if want_bzip2 != 'false' +if want_bzip2 != 'false' and not ossfuzz libbzip2 = cc.find_library('bz2', required : want_bzip2 == 'true') have = libbzip2.found() @@ -1045,7 +1046,7 @@ endif conf.set10('HAVE_BZIP2', have) want_xz = get_option('xz') -if want_xz != 'false' +if want_xz != 'false' and not ossfuzz libxz = dependency('liblzma', required : want_xz == 'true') have = libxz.found() @@ -1056,7 +1057,7 @@ endif conf.set10('HAVE_XZ', have) want_lz4 = get_option('lz4') -if want_lz4 != 'false' +if want_lz4 != 'false' and not ossfuzz liblz4 = dependency('liblz4', required : want_lz4 == 'true') have = liblz4.found() @@ -1067,7 +1068,7 @@ endif conf.set10('HAVE_LZ4', have) want_xkbcommon = get_option('xkbcommon') -if want_xkbcommon != 'false' +if want_xkbcommon != 'false' and not ossfuzz libxkbcommon = dependency('xkbcommon', version : '>= 0.3.0', required : want_xkbcommon == 'true') @@ -1079,7 +1080,7 @@ endif conf.set10('HAVE_XKBCOMMON', have) want_glib = get_option('glib') -if want_glib != 'false' +if want_glib != 'false' and not ossfuzz libglib = dependency('glib-2.0', version : '>= 2.22.0', required : want_glib == 'true') @@ -1098,7 +1099,7 @@ endif conf.set10('HAVE_GLIB', have) want_dbus = get_option('dbus') -if want_dbus != 'false' +if want_dbus != 'false' and not ossfuzz libdbus = dependency('dbus-1', version : '>= 1.3.2', required : want_dbus == 'true') @@ -1110,6 +1111,9 @@ endif conf.set10('HAVE_DBUS', have) default_dnssec = get_option('default-dnssec') +if ossfuzz + default_dnssec = 'no' +endif if default_dnssec != 'no' and conf.get('HAVE_GCRYPT') == 0 message('default-dnssec cannot be set to yes or allow-downgrade when gcrypt is disabled. Setting default-dnssec to no.') default_dnssec = 'no' diff --git a/scripts/oss-fuzz.sh b/scripts/oss-fuzz.sh index b871d744b9..b0cd404ade 100755 --- a/scripts/oss-fuzz.sh +++ b/scripts/oss-fuzz.sh @@ -20,7 +20,7 @@ set -ex export LC_CTYPE=C.UTF-8 -meson $WORK -Doss-fuzz=true -Db_lundef=false -Dseccomp=false -Dlz4=false -Dlibiptc=false -Dlibidn=false +meson $WORK -Doss-fuzz=true -Db_lundef=false ninja -C $WORK fuzzers # get DNS packet corpus From 7d941c0635e3b4a9f647ff15f7654ea13fa080b4 Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Tue, 16 Jan 2018 08:36:56 -0500 Subject: [PATCH 3/5] fuzz: rebuild everything during each oss-fuzz build This avoids failures while using the oss-fuzz local testing infrastructure. --- scripts/oss-fuzz.sh | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/scripts/oss-fuzz.sh b/scripts/oss-fuzz.sh index b0cd404ade..beb9d05b6f 100755 --- a/scripts/oss-fuzz.sh +++ b/scripts/oss-fuzz.sh @@ -20,17 +20,24 @@ set -ex export LC_CTYPE=C.UTF-8 -meson $WORK -Doss-fuzz=true -Db_lundef=false -ninja -C $WORK fuzzers +if [ -z "$WORK" ]; then + echo '$WORK must be set' + exit 1 +fi +build=$WORK/build +rm -rf $build +mkdir -p $build + +meson $build -Doss-fuzz=true -Db_lundef=false +ninja -C $build fuzzers # get DNS packet corpus -df=$WORK/dns-fuzzing -rm -rf $df +df=$build/dns-fuzzing git clone --depth 1 https://github.com/CZ-NIC/dns-fuzzing $df zip -jqr $OUT/fuzz-dns-packet_seed_corpus.zip $df/packet mkdir -p $OUT/src/shared -mv $WORK/src/shared/libsystemd-shared-*.so $OUT/src/shared +mv $build/src/shared/libsystemd-shared-*.so $OUT/src/shared -find $WORK -maxdepth 1 -type f -executable -name "fuzz-*" -exec mv {} $OUT \; -mv $WORK/*.so src/fuzz/*.options $OUT +find $build -maxdepth 1 -type f -executable -name "fuzz-*" -exec mv {} $OUT \; +mv $build/*.so src/fuzz/*.options $OUT From 31e57a35dc7f34556fe66c4a661729a37f966814 Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Tue, 16 Jan 2018 10:25:43 -0500 Subject: [PATCH 4/5] fuzz: allow building fuzzers outside of oss-fuzz Add a new -Dllvm-fuzz=true option that can be used to build against libFuzzer and update the oss-fuzz script to work outside of the oss-fuzz build environment. --- meson.build | 74 +++++++++++++++++++++++++-------------------- meson_options.txt | 2 ++ scripts/oss-fuzz.sh | 24 +++++++++++---- 3 files changed, 61 insertions(+), 39 deletions(-) diff --git a/meson.build b/meson.build index df79a54026..2e3898b3b1 100644 --- a/meson.build +++ b/meson.build @@ -268,8 +268,16 @@ if get_option('tests') != 'false' endif endif -ossfuzz = get_option('oss-fuzz') -if ossfuzz +want_ossfuzz = get_option('oss-fuzz') +want_libfuzzer = get_option('llvm-fuzz') +fuzzer_build = want_ossfuzz or want_libfuzzer +if want_ossfuzz and want_libfuzzer + error('only one of oss-fuzz and llvm-fuzz can be specified') +endif +if want_libfuzzer + fuzzing_engine = meson.get_compiler('cpp').find_library('Fuzzer') +endif +if want_ossfuzz fuzzing_engine = meson.get_compiler('cpp').find_library('FuzzingEngine') endif @@ -316,7 +324,7 @@ endforeach # the oss-fuzz fuzzers are not built with -fPIE, so don't # enable it when we are linking against them -if not ossfuzz +if not fuzzer_build if cc.has_argument('-fPIE') add_project_arguments('-fPIE', language : 'c') endif @@ -372,7 +380,7 @@ foreach arg : ['-Wl,-z,relro', cc.cmd_array(), '-x', 'c', arg, '-include', link_test_c).returncode() == 0 message('Linking with @0@ supported: @1@'.format(arg, have ? 'yes' : 'no')) - if have and (arg != '-pie' or not ossfuzz) + if have and (arg != '-pie' or not fuzzer_build) add_project_link_arguments(arg, language : 'c') endif endforeach @@ -780,10 +788,10 @@ endif libmount = dependency('mount', version : '>= 2.30', - required : not ossfuzz) + required : not fuzzer_build) want_seccomp = get_option('seccomp') -if want_seccomp != 'false' and not ossfuzz +if want_seccomp != 'false' and not fuzzer_build libseccomp = dependency('libseccomp', version : '>= 2.3.1', required : want_seccomp == 'true') @@ -796,7 +804,7 @@ conf.set10('HAVE_SECCOMP', have) m4_defines += have ? ['-DHAVE_SECCOMP'] : [] want_selinux = get_option('selinux') -if want_selinux != 'false' and not ossfuzz +if want_selinux != 'false' and not fuzzer_build libselinux = dependency('libselinux', version : '>= 2.1.9', required : want_selinux == 'true') @@ -809,7 +817,7 @@ conf.set10('HAVE_SELINUX', have) m4_defines += have ? ['-DHAVE_SELINUX'] : [] want_apparmor = get_option('apparmor') -if want_apparmor != 'false' and not ossfuzz +if want_apparmor != 'false' and not fuzzer_build libapparmor = dependency('libapparmor', required : want_apparmor == 'true') have = libapparmor.found() @@ -829,7 +837,7 @@ endif want_polkit = get_option('polkit') install_polkit = false install_polkit_pkla = false -if want_polkit != 'false' and not ossfuzz +if want_polkit != 'false' and not fuzzer_build install_polkit = true libpolkit = dependency('polkit-gobject-1', @@ -842,7 +850,7 @@ endif conf.set10('ENABLE_POLKIT', install_polkit) want_acl = get_option('acl') -if want_acl != 'false' and not ossfuzz +if want_acl != 'false' and not fuzzer_build libacl = cc.find_library('acl', required : want_acl == 'true') have = libacl.found() else @@ -853,7 +861,7 @@ conf.set10('HAVE_ACL', have) m4_defines += have ? ['-DHAVE_ACL'] : [] want_audit = get_option('audit') -if want_audit != 'false' and not ossfuzz +if want_audit != 'false' and not fuzzer_build libaudit = dependency('audit', required : want_audit == 'true') have = libaudit.found() else @@ -863,7 +871,7 @@ endif conf.set10('HAVE_AUDIT', have) want_blkid = get_option('blkid') -if want_blkid != 'false' and not ossfuzz +if want_blkid != 'false' and not fuzzer_build libblkid = dependency('blkid', required : want_blkid == 'true') have = libblkid.found() else @@ -873,7 +881,7 @@ endif conf.set10('HAVE_BLKID', have) want_kmod = get_option('kmod') -if want_kmod != 'false' and not ossfuzz +if want_kmod != 'false' and not fuzzer_build libkmod = dependency('libkmod', version : '>= 15', required : want_kmod == 'true') @@ -885,7 +893,7 @@ endif conf.set10('HAVE_KMOD', have) want_pam = get_option('pam') -if want_pam != 'false' and not ossfuzz +if want_pam != 'false' and not fuzzer_build libpam = cc.find_library('pam', required : want_pam == 'true') libpam_misc = cc.find_library('pam_misc', required : want_pam == 'true') have = libpam.found() and libpam_misc.found() @@ -898,7 +906,7 @@ conf.set10('HAVE_PAM', have) m4_defines += have ? ['-DHAVE_PAM'] : [] want_microhttpd = get_option('microhttpd') -if want_microhttpd != 'false' and not ossfuzz +if want_microhttpd != 'false' and not fuzzer_build libmicrohttpd = dependency('libmicrohttpd', version : '>= 0.9.33', required : want_microhttpd == 'true') @@ -911,7 +919,7 @@ conf.set10('HAVE_MICROHTTPD', have) m4_defines += have ? ['-DHAVE_MICROHTTPD'] : [] want_libcryptsetup = get_option('libcryptsetup') -if want_libcryptsetup != 'false' and not ossfuzz +if want_libcryptsetup != 'false' and not fuzzer_build libcryptsetup = dependency('libcryptsetup', version : '>= 1.6.0', required : want_libcryptsetup == 'true') @@ -923,7 +931,7 @@ endif conf.set10('HAVE_LIBCRYPTSETUP', have) want_libcurl = get_option('libcurl') -if want_libcurl != 'false' and not ossfuzz +if want_libcurl != 'false' and not fuzzer_build libcurl = dependency('libcurl', version : '>= 7.32.0', required : want_libcurl == 'true') @@ -941,7 +949,7 @@ if want_libidn == 'true' and want_libidn2 == 'true' error('libidn and libidn2 cannot be requested simultaneously') endif -if want_libidn != 'false' and want_libidn2 != 'true' and not ossfuzz +if want_libidn != 'false' and want_libidn2 != 'true' and not fuzzer_build libidn = dependency('libidn', required : want_libidn == 'true') have = libidn.found() @@ -951,7 +959,7 @@ else endif conf.set10('HAVE_LIBIDN', have) m4_defines += have ? ['-DHAVE_LIBIDN'] : [] -if not have and want_libidn2 != 'false' and not ossfuzz +if not have and want_libidn2 != 'false' and not fuzzer_build # libidn is used for both libidn and libidn2 objects libidn = dependency('libidn2', required : want_libidn2 == 'true') @@ -963,7 +971,7 @@ conf.set10('HAVE_LIBIDN2', have) m4_defines += have ? ['-DHAVE_LIBIDN2'] : [] want_libiptc = get_option('libiptc') -if want_libiptc != 'false' and not ossfuzz +if want_libiptc != 'false' and not fuzzer_build libiptc = dependency('libiptc', required : want_libiptc == 'true') have = libiptc.found() @@ -975,7 +983,7 @@ conf.set10('HAVE_LIBIPTC', have) m4_defines += have ? ['-DHAVE_LIBIPTC'] : [] want_qrencode = get_option('qrencode') -if want_qrencode != 'false' and not ossfuzz +if want_qrencode != 'false' and not fuzzer_build libqrencode = dependency('libqrencode', required : want_qrencode == 'true') have = libqrencode.found() @@ -986,7 +994,7 @@ endif conf.set10('HAVE_QRENCODE', have) want_gcrypt = get_option('gcrypt') -if want_gcrypt != 'false' and not ossfuzz +if want_gcrypt != 'false' and not fuzzer_build libgcrypt = cc.find_library('gcrypt', required : want_gcrypt == 'true') libgpg_error = cc.find_library('gpg-error', required : want_gcrypt == 'true') have = libgcrypt.found() and libgpg_error.found() @@ -1001,7 +1009,7 @@ endif conf.set10('HAVE_GCRYPT', have) want_gnutls = get_option('gnutls') -if want_gnutls != 'false' and not ossfuzz +if want_gnutls != 'false' and not fuzzer_build libgnutls = dependency('gnutls', version : '>= 3.1.4', required : want_gnutls == 'true') @@ -1013,7 +1021,7 @@ endif conf.set10('HAVE_GNUTLS', have) want_elfutils = get_option('elfutils') -if want_elfutils != 'false' and not ossfuzz +if want_elfutils != 'false' and not fuzzer_build libdw = dependency('libdw', required : want_elfutils == 'true') have = libdw.found() @@ -1024,7 +1032,7 @@ endif conf.set10('HAVE_ELFUTILS', have) want_zlib = get_option('zlib') -if want_zlib != 'false' and not ossfuzz +if want_zlib != 'false' and not fuzzer_build libz = dependency('zlib', required : want_zlib == 'true') have = libz.found() @@ -1035,7 +1043,7 @@ endif conf.set10('HAVE_ZLIB', have) want_bzip2 = get_option('bzip2') -if want_bzip2 != 'false' and not ossfuzz +if want_bzip2 != 'false' and not fuzzer_build libbzip2 = cc.find_library('bz2', required : want_bzip2 == 'true') have = libbzip2.found() @@ -1046,7 +1054,7 @@ endif conf.set10('HAVE_BZIP2', have) want_xz = get_option('xz') -if want_xz != 'false' and not ossfuzz +if want_xz != 'false' and not fuzzer_build libxz = dependency('liblzma', required : want_xz == 'true') have = libxz.found() @@ -1057,7 +1065,7 @@ endif conf.set10('HAVE_XZ', have) want_lz4 = get_option('lz4') -if want_lz4 != 'false' and not ossfuzz +if want_lz4 != 'false' and not fuzzer_build liblz4 = dependency('liblz4', required : want_lz4 == 'true') have = liblz4.found() @@ -1068,7 +1076,7 @@ endif conf.set10('HAVE_LZ4', have) want_xkbcommon = get_option('xkbcommon') -if want_xkbcommon != 'false' and not ossfuzz +if want_xkbcommon != 'false' and not fuzzer_build libxkbcommon = dependency('xkbcommon', version : '>= 0.3.0', required : want_xkbcommon == 'true') @@ -1080,7 +1088,7 @@ endif conf.set10('HAVE_XKBCOMMON', have) want_glib = get_option('glib') -if want_glib != 'false' and not ossfuzz +if want_glib != 'false' and not fuzzer_build libglib = dependency('glib-2.0', version : '>= 2.22.0', required : want_glib == 'true') @@ -1099,7 +1107,7 @@ endif conf.set10('HAVE_GLIB', have) want_dbus = get_option('dbus') -if want_dbus != 'false' and not ossfuzz +if want_dbus != 'false' and not fuzzer_build libdbus = dependency('dbus-1', version : '>= 1.3.2', required : want_dbus == 'true') @@ -1111,7 +1119,7 @@ endif conf.set10('HAVE_DBUS', have) default_dnssec = get_option('default-dnssec') -if ossfuzz +if fuzzer_build default_dnssec = 'no' endif if default_dnssec != 'no' and conf.get('HAVE_GCRYPT') == 0 @@ -2484,7 +2492,7 @@ foreach tuple : fuzzers defs = tuple.length() >= 4 ? tuple[3] : [] incs = tuple.length() >= 5 ? tuple[4] : includes - if ossfuzz + if fuzzer_build dependencies += fuzzing_engine else sources += 'src/fuzz/fuzz-main.c' diff --git a/meson_options.txt b/meson_options.txt index 9e7cad7471..eda5a579f2 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -294,3 +294,5 @@ option('install-tests', type : 'boolean', value : 'false', option('oss-fuzz', type : 'boolean', value : 'false', description : 'build against oss-fuzz') +option('llvm-fuzz', type : 'boolean', value : 'false', + description : 'build against LLVM libFuzzer') diff --git a/scripts/oss-fuzz.sh b/scripts/oss-fuzz.sh index beb9d05b6f..8ae7625e1f 100755 --- a/scripts/oss-fuzz.sh +++ b/scripts/oss-fuzz.sh @@ -20,15 +20,27 @@ set -ex export LC_CTYPE=C.UTF-8 -if [ -z "$WORK" ]; then - echo '$WORK must be set' - exit 1 -fi +SANITIZER=${SANITIZER:-address -fsanitize-address-use-after-scope} +flags="-O1 -fno-omit-frame-pointer -gline-tables-only -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fsanitize=$SANITIZER -fsanitize-coverage=trace-pc-guard,trace-cmp" + +export CFLAGS=${CFLAGS:-$flags} +export CXXFLAGS=${CXXFLAGS:-$flags} +export CC=${CC:-clang} +export CXX=${CXX:-clang++} +export WORK=${WORK:-$(pwd)} +export OUT=${OUT:-$(pwd)/out} +mkdir -p $OUT + build=$WORK/build rm -rf $build mkdir -p $build -meson $build -Doss-fuzz=true -Db_lundef=false +fuzzflag="oss-fuzz=true" +if [ -z "$FUZZING_ENGINE" ]; then + fuzzflag="llvm-fuzz=true" +fi + +meson $build -D$fuzzflag -Db_lundef=false ninja -C $build fuzzers # get DNS packet corpus @@ -40,4 +52,4 @@ mkdir -p $OUT/src/shared mv $build/src/shared/libsystemd-shared-*.so $OUT/src/shared find $build -maxdepth 1 -type f -executable -name "fuzz-*" -exec mv {} $OUT \; -mv $build/*.so src/fuzz/*.options $OUT +cp src/fuzz/*.options $OUT From 8b53eb4d47b47a49e922a1c765354fe94ff608c8 Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Tue, 16 Jan 2018 12:09:56 -0500 Subject: [PATCH 5/5] fuzz: add docs on creating fuzzer targets to HACKING --- HACKING | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/HACKING b/HACKING index 6267c58e22..551216bb45 100644 --- a/HACKING +++ b/HACKING @@ -79,3 +79,36 @@ for systemd (this example is for Fedora): And after that, head over to your repo on github and click "Compare & pull request" Happy hacking! + + +FUZZERS + +systemd includes fuzzers in src/fuzz that use libFuzzer and are automatically +run by OSS-Fuzz (https://github.com/google/oss-fuzz) with sanitizers. To add a +fuzz target, create a new src/fuzz/fuzz-foo.c file with a LLVMFuzzerTestOneInput +function and add it to the list in src/fuzz/meson.build. + +Whenever possible, a seed corpus and a dictionary should also be added with new +fuzz targets. The dictionary should be named src/fuzz/fuzz-foo.dict and the seed +corpus should be built and exported as $OUT/fuzz-foo_seed_corpus.zip in +scripts/oss-fuzz.sh. + +The fuzzers can be built locally if you have libFuzzer installed by running +scripts/oss-fuzz.sh. You should also confirm that the fuzzer runs in the +OSS-Fuzz environment by checking out the OSS-Fuzz repo, modifying the git clone +in projects/systemd/Dockerfile to point at your code (for example, a fork on +GitHub) and then running these commands: + + python infra/helper.py build_image systemd + python infra/helper.py build_fuzzers --sanitizer memory systemd + python infra/helper.py run_fuzzer systemd fuzz-foo + +For more details on building fuzzers and integrating with OSS-Fuzz, visit: + + https://github.com/google/oss-fuzz/blob/master/docs/new_project_guide.md + + https://llvm.org/docs/LibFuzzer.html + + https://github.com/google/fuzzer-test-suite/blob/master/tutorial/libFuzzerTutorial.md + + https://chromium.googlesource.com/chromium/src/testing/libfuzzer/+/HEAD/efficient_fuzzer.md