From cb452f814f7d62fc85bb5b15d04a307a47615d19 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sun, 28 Nov 2021 10:32:06 +0100 Subject: [PATCH] Support UBSan for local fuzzing With Jazzer supporting full UBSan as of https://github.com/CodeIntelligenceTesting/jazzer/pull/169 as well as a much simpler way to link the UBSan C++ runtime via the flag used in #186, UBSan can now be supported in local mode without introducing additional complexity. The list of enabled UBSan checks is taken from OSS-Fuzz. The commit also adds tests to verify that both C++ and Java fuzz tests support the UBSan C++ checks without linker errors. --- .bazelrc | 17 +++++++++ README.md | 2 + docs/guide.md | 17 +++++++++ examples/BUILD | 16 ++++++++ examples/java/BUILD | 20 ++++++++++ .../example/NativeUbsanFuncPtrFuzzTest.cpp | 37 +++++++++++++++++++ .../com/example/NativeUbsanFuncPtrFuzzTest.h | 34 +++++++++++++++++ .../example/NativeUbsanFuncPtrFuzzTest.java | 35 ++++++++++++++++++ examples/ubsan_function_ptr_fuzz_test.cc | 36 ++++++++++++++++++ examples/ubsan_int_overflow_fuzz_test.cc | 29 +++++++++++++++ fuzzing/BUILD | 3 ++ fuzzing/instrum_opts.bzl | 1 + fuzzing/private/BUILD | 7 ++++ fuzzing/private/fuzz_test.bzl | 6 ++- fuzzing/private/instrum_opts.bzl | 21 +++++++++++ 15 files changed, 279 insertions(+), 2 deletions(-) create mode 100644 examples/java/com/example/NativeUbsanFuncPtrFuzzTest.cpp create mode 100644 examples/java/com/example/NativeUbsanFuncPtrFuzzTest.h create mode 100644 examples/java/com/example/NativeUbsanFuncPtrFuzzTest.java create mode 100644 examples/ubsan_function_ptr_fuzz_test.cc create mode 100644 examples/ubsan_int_overflow_fuzz_test.cc diff --git a/.bazelrc b/.bazelrc index 0083a81..10430bd 100644 --- a/.bazelrc +++ b/.bazelrc @@ -38,6 +38,11 @@ build:msan-libfuzzer-repro --//fuzzing:cc_engine=//fuzzing/engines:libfuzzer build:msan-libfuzzer-repro --@rules_fuzzing//fuzzing:cc_engine_instrumentation=libfuzzer build:msan-libfuzzer-repro --@rules_fuzzing//fuzzing:cc_engine_sanitizer=msan-origin-tracking +# LibFuzzer + UBSAN +build:ubsan-libfuzzer --//fuzzing:cc_engine=//fuzzing/engines:libfuzzer +build:ubsan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine_instrumentation=libfuzzer +build:ubsan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine_sanitizer=ubsan + # Honggfuzz + ASAN build:asan-honggfuzz --//fuzzing:cc_engine=//fuzzing/engines:honggfuzz build:asan-honggfuzz --@rules_fuzzing//fuzzing:cc_engine_instrumentation=honggfuzz @@ -48,6 +53,11 @@ build:msan-honggfuzz --//fuzzing:cc_engine=//fuzzing/engines:honggfuzz build:msan-honggfuzz --@rules_fuzzing//fuzzing:cc_engine_instrumentation=honggfuzz build:msan-honggfuzz --@rules_fuzzing//fuzzing:cc_engine_sanitizer=msan +# Honggfuzz + UBSAN +build:ubsan-honggfuzz --//fuzzing:cc_engine=//fuzzing/engines:honggfuzz +build:ubsan-honggfuzz --@rules_fuzzing//fuzzing:cc_engine_instrumentation=honggfuzz +build:ubsan-honggfuzz --@rules_fuzzing//fuzzing:cc_engine_sanitizer=ubsan + # Replay + ASAN build:asan-replay --//fuzzing:cc_engine=//fuzzing/engines:replay build:asan-replay --@rules_fuzzing//fuzzing:cc_engine_instrumentation=none @@ -70,3 +80,10 @@ build:asan-jazzer --@rules_fuzzing//fuzzing:cc_engine_instrumentation=jazzer build:asan-jazzer --@rules_fuzzing//fuzzing:cc_engine_sanitizer=asan # Workaround for https://github.com/bazelbuild/bazel/issues/11128 build:asan-jazzer --//fuzzing:cc_engine_sanitizer=asan + +# Jazzer + UBSAN +build:ubsan-jazzer --//fuzzing:java_engine=//fuzzing/engines:jazzer +build:ubsan-jazzer --@rules_fuzzing//fuzzing:cc_engine_instrumentation=jazzer +build:ubsan-jazzer --@rules_fuzzing//fuzzing:cc_engine_sanitizer=ubsan +# Workaround for https://github.com/bazelbuild/bazel/issues/11128 +build:ubsan-jazzer --//fuzzing:cc_engine_sanitizer=ubsan diff --git a/README.md b/README.md index 3e5b540..1c9e343 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ The rule library currently provides support for C++ and Java fuzz tests. Support * Multiple sanitizer configurations: * [Address Sanitizer][asan-doc] * [Memory Sanitizer][msan-doc] + * [Undefined Behavior Sanitizer][ubsan-doc] * Corpora and dictionaries. * Simple "bazel run/test" commands to build and run the fuzz tests. * No need to understand the details of each fuzzing engine. @@ -258,3 +259,4 @@ Check out the [`examples/`](examples/) directory, which showcases additional fea [libfuzzer-doc]: https://llvm.org/docs/LibFuzzer.html [jazzer-doc]: https://github.com/CodeIntelligenceTesting/jazzer [msan-doc]: https://clang.llvm.org/docs/MemorySanitizer.html +[ubsan-doc]: https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html diff --git a/docs/guide.md b/docs/guide.md index f352827..42868d8 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -144,6 +144,7 @@ The fuzzing rules library defines the following Bazel configuration flags that a * `asan`: [Address Sanitizer (ASAN)][asan-doc]. * `msan`: [Memory Sanitizer (MSAN)][msan-doc]. * `msan-origin-tracking`: MSAN with [origin tracking][msan-origin-tracking] enabled (useful for debugging crash reproducers; available separately due to it being 1.5-2x slower). + * `ubsan`: [Undefined Behavior Sanitizer (UBSAN)][ubsan-doc]. * `--@rules_fuzzing//fuzzing:cc_fuzzing_build_mode` is a bool flag that specifies whether the special [`FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION` macro][fuzzing-build-mode] is defined during the build. This is turned on by default and most users should not need to change this flag. @@ -180,6 +181,11 @@ build:msan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine=@rules_fuzzing//fuzzing build:msan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine_instrumentation=libfuzzer build:msan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine_sanitizer=msan +# --config=ubsan-libfuzzer +build:ubsan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine=@rules_fuzzing//fuzzing/engines:libfuzzer +build:ubsan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine_instrumentation=libfuzzer +build:ubsan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine_sanitizer=ubsan + # --config=asan-honggfuzz build:asan-honggfuzz --@rules_fuzzing//fuzzing:cc_engine=@rules_fuzzing//fuzzing/engines:honggfuzz build:asan-honggfuzz --@rules_fuzzing//fuzzing:cc_engine_instrumentation=honggfuzz @@ -190,6 +196,11 @@ build:msan-honggfuzz --@rules_fuzzing//fuzzing:cc_engine=@rules_fuzzing//fuzzing build:msan-honggfuzz --@rules_fuzzing//fuzzing:cc_engine_instrumentation=honggfuzz build:msan-honggfuzz --@rules_fuzzing//fuzzing:cc_engine_sanitizer=msan +# --config=ubsan-honggfuzz +build:ubsan-honggfuzz --@rules_fuzzing//fuzzing:cc_engine=@rules_fuzzing//fuzzing/engines:honggfuzz +build:ubsan-honggfuzz --@rules_fuzzing//fuzzing:cc_engine_instrumentation=honggfuzz +build:ubsan-honggfuzz --@rules_fuzzing//fuzzing:cc_engine_sanitizer=ubsan + # --config=asan-replay build:asan-replay --@rules_fuzzing//fuzzing:cc_engine=@rules_fuzzing//fuzzing/engines:replay build:asan-replay --@rules_fuzzing//fuzzing:cc_engine_instrumentation=none @@ -204,6 +215,11 @@ build:jazzer --@rules_fuzzing//fuzzing:cc_engine_sanitizer=none build:asan-jazzer --@rules_fuzzing//fuzzing:java_engine=@rules_fuzzing//fuzzing/engines:jazzer build:asan-jazzer --@rules_fuzzing//fuzzing:cc_engine_instrumentation=jazzer build:asan-jazzer --@rules_fuzzing//fuzzing:cc_engine_sanitizer=asan + +# --config=ubsan-jazzer +build:ubsan-jazzer --@rules_fuzzing//fuzzing:java_engine=@rules_fuzzing//fuzzing/engines:jazzer +build:ubsan-jazzer --@rules_fuzzing//fuzzing:cc_engine_instrumentation=jazzer +build:ubsan-jazzer --@rules_fuzzing//fuzzing:cc_engine_sanitizer=ubsan ``` ## Advanced topics @@ -241,3 +257,4 @@ A fuzzing engine launcher script receives configuration through the following en [msan-doc]: https://clang.llvm.org/docs/MemorySanitizer.html [msan-origin-tracking]: https://clang.llvm.org/docs/MemorySanitizer.html#origin-tracking [seed-corpus]: https://github.com/google/fuzzing/blob/master/docs/good-fuzz-target.md#seed-corpus +[ubsan-doc]: https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html diff --git a/examples/BUILD b/examples/BUILD index b8afd15..65219ca 100644 --- a/examples/BUILD +++ b/examples/BUILD @@ -126,3 +126,19 @@ cc_fuzz_test( "@bazel_tools//tools/cpp/runfiles", ], ) + +cc_fuzz_test( + name = "ubsan_int_overflow_fuzz_test", + srcs = ["ubsan_int_overflow_fuzz_test.cc"], + tags = [ + "no-oss-fuzz", + ], +) + +cc_fuzz_test( + name = "ubsan_function_ptr_fuzz_test", + srcs = ["ubsan_function_ptr_fuzz_test.cc"], + tags = [ + "no-oss-fuzz", + ], +) diff --git a/examples/java/BUILD b/examples/java/BUILD index 4dc1352..5c23abe 100644 --- a/examples/java/BUILD +++ b/examples/java/BUILD @@ -69,6 +69,14 @@ java_fuzz_test( ], ) +java_fuzz_test( + name = "NativeUbsanFuncPtrFuzzTest", + srcs = ["com/example/NativeUbsanFuncPtrFuzzTest.java"], + deps = [ + ":native_ubsan_func_ptr", + ], +) + # A native library that interfaces with Java through the JNI. cc_binary( name = "native", @@ -99,3 +107,15 @@ cc_binary( "@bazel_tools//tools/jdk:jni", ], ) + +cc_binary( + name = "native_ubsan_func_ptr", + srcs = [ + "com/example/NativeUbsanFuncPtrFuzzTest.cpp", + "com/example/NativeUbsanFuncPtrFuzzTest.h", + ], + linkshared = True, + deps = [ + "@bazel_tools//tools/jdk:jni", + ], +) diff --git a/examples/java/com/example/NativeUbsanFuncPtrFuzzTest.cpp b/examples/java/com/example/NativeUbsanFuncPtrFuzzTest.cpp new file mode 100644 index 0000000..2735f7b --- /dev/null +++ b/examples/java/com/example/NativeUbsanFuncPtrFuzzTest.cpp @@ -0,0 +1,37 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// parse calls a function through a pointer of a mismatched type. This is +// detected by UBSAN's function check. + +#include "NativeUbsanFuncPtrFuzzTest.h" + +#include +#include + +int parse_data(const uint16_t *data) { + return data[0] + data[1]; +} + +int (*mistyped_function_pointer)(const char *data); + +JNIEXPORT int JNICALL Java_com_example_NativeUbsanFuncPtrFuzzTest_parse( + JNIEnv *env, jobject o, jstring bytes) { + const char *input(env->GetStringUTFChars(bytes, nullptr)); + mistyped_function_pointer = + reinterpret_cast(&parse_data); + int result = mistyped_function_pointer(input); + env->ReleaseStringUTFChars(bytes, input); + return result; +} diff --git a/examples/java/com/example/NativeUbsanFuncPtrFuzzTest.h b/examples/java/com/example/NativeUbsanFuncPtrFuzzTest.h new file mode 100644 index 0000000..7950ee0 --- /dev/null +++ b/examples/java/com/example/NativeUbsanFuncPtrFuzzTest.h @@ -0,0 +1,34 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +/* Header for class com_example_NativeUbsanFuncPtrFuzzTest */ + +#ifndef EXAMPLES_JAVA_COM_EXAMPLE_NATIVEUBSANFUNCPTRFUZZTEST_H_ +#define EXAMPLES_JAVA_COM_EXAMPLE_NATIVEUBSANFUNCPTRFUZZTEST_H_ +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_example_NativeUbsanFuncPtrFuzzTest + * Method: parse + * Signature: (Ljava/lang/String;)I + */ +JNIEXPORT int JNICALL +Java_com_example_NativeUbsanFuncPtrFuzzTest_parse(JNIEnv *, jobject, jstring); + +#ifdef __cplusplus +} +#endif +#endif // EXAMPLES_JAVA_COM_EXAMPLE_NATIVEUBSANFUNCPTRFUZZTEST_H_ diff --git a/examples/java/com/example/NativeUbsanFuncPtrFuzzTest.java b/examples/java/com/example/NativeUbsanFuncPtrFuzzTest.java new file mode 100644 index 0000000..1f53464 --- /dev/null +++ b/examples/java/com/example/NativeUbsanFuncPtrFuzzTest.java @@ -0,0 +1,35 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.example; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; + +// The native function parse calls a function through a pointer of a mismatched +// type. This is detected by UBSAN's function check. +public class NativeUbsanFuncPtrFuzzTest { + + static { + System.loadLibrary("native_ubsan_func_ptr"); + } + + public static void fuzzerTestOneInput(FuzzedDataProvider data) { + String stringData = data.consumeRemainingAsString(); + if (stringData.length() > 10) { + parse(stringData); + } + } + + private static native void parse(String data); +} diff --git a/examples/ubsan_function_ptr_fuzz_test.cc b/examples/ubsan_function_ptr_fuzz_test.cc new file mode 100644 index 0000000..9adfc88 --- /dev/null +++ b/examples/ubsan_function_ptr_fuzz_test.cc @@ -0,0 +1,36 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// A fuzz target that calls a function through a pointer of a mismatched type. +// This is detected by UBSAN's function check and requires the UBSAN C++ +// runtime. + +#include +#include + +int parse_data(const uint16_t *data) { + return data[0] + data[1]; +} + +int (*mistyped_function_pointer)(const uint8_t *data); + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < 2) { + return 0; + } + mistyped_function_pointer = + reinterpret_cast(parse_data); + mistyped_function_pointer(data); + return 0; +} diff --git a/examples/ubsan_int_overflow_fuzz_test.cc b/examples/ubsan_int_overflow_fuzz_test.cc new file mode 100644 index 0000000..aa6ca66 --- /dev/null +++ b/examples/ubsan_int_overflow_fuzz_test.cc @@ -0,0 +1,29 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// A fuzz target that triggers a signed integer overflow, which is undefined +// behavior and detected by UBSAN's signed-integer-overflow check. + +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size == 0) { + return 0; + } + int k = 0x7fffffff; + k += data[0]; + // Use k. + return k & 0; +} diff --git a/fuzzing/BUILD b/fuzzing/BUILD index 224e47a..d7087ec 100644 --- a/fuzzing/BUILD +++ b/fuzzing/BUILD @@ -54,6 +54,9 @@ string_flag( # MSAN + origin tracking enabled. # Useful for debugging crash reproducers, 1.5-2x slower. "msan-origin-tracking", + # Undefined Behavior sanitizer (UBSAN). + # See https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html + "ubsan", ], visibility = ["//visibility:public"], ) diff --git a/fuzzing/instrum_opts.bzl b/fuzzing/instrum_opts.bzl index f6be905..b497155 100644 --- a/fuzzing/instrum_opts.bzl +++ b/fuzzing/instrum_opts.bzl @@ -44,4 +44,5 @@ sanitizer_configs = { "asan": instrum_defaults.asan, "msan": instrum_defaults.msan, "msan-origin-tracking": instrum_defaults.msan_origin_tracking, + "ubsan": instrum_defaults.ubsan, } diff --git a/fuzzing/private/BUILD b/fuzzing/private/BUILD index 53f4886..3eb5186 100644 --- a/fuzzing/private/BUILD +++ b/fuzzing/private/BUILD @@ -44,6 +44,13 @@ config_setting( }, ) +config_setting( + name = "use_sanitizer_ubsan", + flag_values = { + "@rules_fuzzing//fuzzing:cc_engine_sanitizer": "ubsan", + }, +) + config_setting( name = "use_oss_fuzz", flag_values = { diff --git a/fuzzing/private/fuzz_test.bzl b/fuzzing/private/fuzz_test.bzl index 4a97b35..70e0b63 100644 --- a/fuzzing/private/fuzz_test.bzl +++ b/fuzzing/private/fuzz_test.bzl @@ -274,12 +274,14 @@ def java_fuzz_test( "@rules_fuzzing//fuzzing/private:use_oss_fuzz": "@rules_fuzzing_oss_fuzz//:jazzer_driver", "@rules_fuzzing//fuzzing/private:use_sanitizer_none": "@jazzer//driver:jazzer_driver", "@rules_fuzzing//fuzzing/private:use_sanitizer_asan": "@jazzer//driver:jazzer_driver_asan", - }, no_match_error = "Jazzer only supports the sanitizer settings \"none\" and \"asan\""), + "@rules_fuzzing//fuzzing/private:use_sanitizer_ubsan": "@jazzer//driver:jazzer_driver_ubsan", + }, no_match_error = "Jazzer only supports the sanitizer settings: \"none\", \"asan\", \"ubsan\""), driver_with_native = select({ "@rules_fuzzing//fuzzing/private:use_oss_fuzz": "@rules_fuzzing_oss_fuzz//:jazzer_driver_with_sanitizer", "@rules_fuzzing//fuzzing/private:use_sanitizer_none": "@jazzer//driver:jazzer_driver", "@rules_fuzzing//fuzzing/private:use_sanitizer_asan": "@jazzer//driver:jazzer_driver_asan", - }, no_match_error = "Jazzer only supports the sanitizer settings \"none\" and \"asan\""), + "@rules_fuzzing//fuzzing/private:use_sanitizer_ubsan": "@jazzer//driver:jazzer_driver_ubsan", + }, no_match_error = "Jazzer only supports the sanitizer settings: \"none\", \"asan\", \"ubsan\""), sanitizer_options = select({ "@rules_fuzzing//fuzzing/private:use_oss_fuzz": "@rules_fuzzing//fuzzing/private:oss_fuzz_jazzer_sanitizer_options.sh", "//conditions:default": "@rules_fuzzing//fuzzing/private:local_jazzer_sanitizer_options.sh", diff --git a/fuzzing/private/instrum_opts.bzl b/fuzzing/private/instrum_opts.bzl index 1b04184..a2f21a9 100644 --- a/fuzzing/private/instrum_opts.bzl +++ b/fuzzing/private/instrum_opts.bzl @@ -120,4 +120,25 @@ instrum_defaults = struct( ], linkopts = ["-fsanitize=memory"], ), + ubsan = _make_opts( + copts = [ + "-fsanitize=undefined", + # Enable most of the checks enabled in OSS-Fuzz: + # https://github.com/google/oss-fuzz/blob/a896ee749769bd236299041461784f483649fe80/infra/base-images/base-builder/Dockerfile#L77 + # The only exception is unsigned-integer-overflow, which is not UB, + # but enabled in OSS-Fuzz in silent mode as an additional coverage + # signal. We do not do this here as it would introduce additional + # complexity (setting UBSAN_OPTIONS) to the local mode. + "-fsanitize=array-bounds,bool,builtin,enum,float-divide-by-zero,function,integer-divide-by-zero,null,object-size,return,returns-nonnull-attribute,shift,signed-integer-overflow,unreachable,vla-bound,vptr", + "-fno-sanitize-recover=all", + ], + linkopts = [ + "-fsanitize=undefined", + # Bazel uses clang, not clang++, as the linker, which does not link + # the C++ UBSan runtime library by default, but can be instructed to + # do so with a flag. + # https://github.com/bazelbuild/bazel/issues/11122#issuecomment-896613570 + "-fsanitize-link-c++-runtime", + ], + ), )