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 diff --git a/meson.build b/meson.build index 5066ccf432..2e3898b3b1 100644 --- a/meson.build +++ b/meson.build @@ -268,6 +268,19 @@ if get_option('tests') != 'false' endif endif +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 + foreach arg : ['-Wextra', '-Werror=undef', '-Wlogical-op', @@ -302,7 +315,6 @@ foreach arg : ['-Wextra', '-fvisibility=hidden', '-fstack-protector', '-fstack-protector-strong', - '-fPIE', '--param=ssp-buffer-size=4', ] if cc.has_argument(arg) @@ -310,6 +322,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 fuzzer_build + 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 +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 + if have and (arg != '-pie' or not fuzzer_build) add_project_link_arguments(arg, language : 'c') endif endforeach @@ -767,10 +787,11 @@ if not libcap.found() endif libmount = dependency('mount', - version : '>= 2.30') + version : '>= 2.30', + required : not fuzzer_build) want_seccomp = get_option('seccomp') -if want_seccomp != 'false' +if want_seccomp != 'false' and not fuzzer_build libseccomp = dependency('libseccomp', version : '>= 2.3.1', required : want_seccomp == 'true') @@ -783,7 +804,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 fuzzer_build libselinux = dependency('libselinux', version : '>= 2.1.9', required : want_selinux == 'true') @@ -796,7 +817,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 fuzzer_build libapparmor = dependency('libapparmor', required : want_apparmor == 'true') have = libapparmor.found() @@ -816,7 +837,7 @@ endif want_polkit = get_option('polkit') install_polkit = false install_polkit_pkla = false -if want_polkit != 'false' +if want_polkit != 'false' and not fuzzer_build install_polkit = true libpolkit = dependency('polkit-gobject-1', @@ -829,7 +850,7 @@ endif conf.set10('ENABLE_POLKIT', install_polkit) want_acl = get_option('acl') -if want_acl != 'false' +if want_acl != 'false' and not fuzzer_build libacl = cc.find_library('acl', required : want_acl == 'true') have = libacl.found() else @@ -840,7 +861,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 fuzzer_build libaudit = dependency('audit', required : want_audit == 'true') have = libaudit.found() else @@ -850,7 +871,7 @@ endif conf.set10('HAVE_AUDIT', have) want_blkid = get_option('blkid') -if want_blkid != 'false' +if want_blkid != 'false' and not fuzzer_build libblkid = dependency('blkid', required : want_blkid == 'true') have = libblkid.found() else @@ -860,7 +881,7 @@ endif conf.set10('HAVE_BLKID', have) want_kmod = get_option('kmod') -if want_kmod != 'false' +if want_kmod != 'false' and not fuzzer_build libkmod = dependency('libkmod', version : '>= 15', required : want_kmod == 'true') @@ -872,7 +893,7 @@ endif conf.set10('HAVE_KMOD', have) want_pam = get_option('pam') -if want_pam != 'false' +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() @@ -885,7 +906,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 fuzzer_build libmicrohttpd = dependency('libmicrohttpd', version : '>= 0.9.33', required : want_microhttpd == 'true') @@ -898,7 +919,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 fuzzer_build libcryptsetup = dependency('libcryptsetup', version : '>= 1.6.0', required : want_libcryptsetup == 'true') @@ -910,7 +931,7 @@ endif conf.set10('HAVE_LIBCRYPTSETUP', have) want_libcurl = get_option('libcurl') -if want_libcurl != 'false' +if want_libcurl != 'false' and not fuzzer_build libcurl = dependency('libcurl', version : '>= 7.32.0', required : want_libcurl == 'true') @@ -928,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' +if want_libidn != 'false' and want_libidn2 != 'true' and not fuzzer_build libidn = dependency('libidn', required : want_libidn == 'true') have = libidn.found() @@ -938,7 +959,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 fuzzer_build # libidn is used for both libidn and libidn2 objects libidn = dependency('libidn2', required : want_libidn2 == 'true') @@ -950,7 +971,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 fuzzer_build libiptc = dependency('libiptc', required : want_libiptc == 'true') have = libiptc.found() @@ -962,7 +983,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 fuzzer_build libqrencode = dependency('libqrencode', required : want_qrencode == 'true') have = libqrencode.found() @@ -973,7 +994,7 @@ endif conf.set10('HAVE_QRENCODE', have) want_gcrypt = get_option('gcrypt') -if want_gcrypt != 'false' +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() @@ -988,7 +1009,7 @@ endif conf.set10('HAVE_GCRYPT', have) want_gnutls = get_option('gnutls') -if want_gnutls != 'false' +if want_gnutls != 'false' and not fuzzer_build libgnutls = dependency('gnutls', version : '>= 3.1.4', required : want_gnutls == 'true') @@ -1000,7 +1021,7 @@ endif conf.set10('HAVE_GNUTLS', have) want_elfutils = get_option('elfutils') -if want_elfutils != 'false' +if want_elfutils != 'false' and not fuzzer_build libdw = dependency('libdw', required : want_elfutils == 'true') have = libdw.found() @@ -1011,7 +1032,7 @@ endif conf.set10('HAVE_ELFUTILS', have) want_zlib = get_option('zlib') -if want_zlib != 'false' +if want_zlib != 'false' and not fuzzer_build libz = dependency('zlib', required : want_zlib == 'true') have = libz.found() @@ -1022,7 +1043,7 @@ endif conf.set10('HAVE_ZLIB', have) want_bzip2 = get_option('bzip2') -if want_bzip2 != 'false' +if want_bzip2 != 'false' and not fuzzer_build libbzip2 = cc.find_library('bz2', required : want_bzip2 == 'true') have = libbzip2.found() @@ -1033,7 +1054,7 @@ endif conf.set10('HAVE_BZIP2', have) want_xz = get_option('xz') -if want_xz != 'false' +if want_xz != 'false' and not fuzzer_build libxz = dependency('liblzma', required : want_xz == 'true') have = libxz.found() @@ -1044,7 +1065,7 @@ endif conf.set10('HAVE_XZ', have) want_lz4 = get_option('lz4') -if want_lz4 != 'false' +if want_lz4 != 'false' and not fuzzer_build liblz4 = dependency('liblz4', required : want_lz4 == 'true') have = liblz4.found() @@ -1055,7 +1076,7 @@ endif conf.set10('HAVE_LZ4', have) want_xkbcommon = get_option('xkbcommon') -if want_xkbcommon != 'false' +if want_xkbcommon != 'false' and not fuzzer_build libxkbcommon = dependency('xkbcommon', version : '>= 0.3.0', required : want_xkbcommon == 'true') @@ -1067,7 +1088,7 @@ endif conf.set10('HAVE_XKBCOMMON', have) want_glib = get_option('glib') -if want_glib != 'false' +if want_glib != 'false' and not fuzzer_build libglib = dependency('glib-2.0', version : '>= 2.22.0', required : want_glib == 'true') @@ -1086,7 +1107,7 @@ endif conf.set10('HAVE_GLIB', have) want_dbus = get_option('dbus') -if want_dbus != 'false' +if want_dbus != 'false' and not fuzzer_build libdbus = dependency('dbus-1', version : '>= 1.3.2', required : want_dbus == 'true') @@ -1098,6 +1119,9 @@ endif conf.set10('HAVE_DBUS', have) default_dnssec = get_option('default-dnssec') +if fuzzer_build + 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' @@ -1178,6 +1202,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 +1328,7 @@ subdir('src/vconsole') subdir('src/boot/efi') subdir('src/test') +subdir('src/fuzz') subdir('rules') subdir('test') @@ -2457,6 +2483,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 fuzzer_build + 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..eda5a579f2 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -291,3 +291,8 @@ 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') +option('llvm-fuzz', type : 'boolean', value : 'false', + description : 'build against LLVM libFuzzer') diff --git a/scripts/oss-fuzz.sh b/scripts/oss-fuzz.sh new file mode 100755 index 0000000000..8ae7625e1f --- /dev/null +++ b/scripts/oss-fuzz.sh @@ -0,0 +1,55 @@ +#!/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 + +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 + +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 +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 $build/src/shared/libsystemd-shared-*.so $OUT/src/shared + +find $build -maxdepth 1 -type f -executable -name "fuzz-*" -exec mv {} $OUT \; +cp 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