Support UBSan for local fuzzing (#187)

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.
diff --git a/.bazelrc b/.bazelrc
index 0083a81..10430bd 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -38,6 +38,11 @@
 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 --@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_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 @@
 * 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 @@
 [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 @@
    * `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_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_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: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 @@
 [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 @@
         "@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(
+    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 @@
         "@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 <cstddef>
+#include <cstdint>
+
+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<int (*)(const char *)>(&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 <jni.h>
+/* 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 <cstddef>
+#include <cstdint>
+
+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<int (*)(const uint8_t *)>(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 <cstddef>
+#include <cstdint>
+
+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 @@
         # 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 @@
     "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
@@ -45,6 +45,13 @@
 )
 
 config_setting(
+    name = "use_sanitizer_ubsan",
+    flag_values = {
+        "@rules_fuzzing//fuzzing:cc_engine_sanitizer": "ubsan",
+    },
+)
+
+config_setting(
     name = "use_oss_fuzz",
     flag_values = {
         "@rules_fuzzing//fuzzing:cc_engine": "@rules_fuzzing_oss_fuzz//:oss_fuzz_engine",
diff --git a/fuzzing/private/fuzz_test.bzl b/fuzzing/private/fuzz_test.bzl
index a3376ea..96f9c74 100644
--- a/fuzzing/private/fuzz_test.bzl
+++ b/fuzzing/private/fuzz_test.bzl
@@ -291,12 +291,14 @@
             "@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 @@
         ],
         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",
+        ],
+    ),
 )