Skip to content

Commit 1cfb489

Browse files
committed
driver: Add support for native fuzzing without launcher
Fuzzing native libraries with ASan and UBSan using the Java driver is made possible by the generated launcher script, which this commit uses to preload the Jazzer hooks and native sanitizer runtime libraries into a JVM running Jazzer started as a subprocess. Since the sanitizer instrumentation applied at compile time and the runtime libraries must be kept in sync, the preload approach allows users to fuzz native libraries without building the Jazzer launcher from source for the first time. Jazzer will automatically pick the libraries corresponding to `CC` (if set) or `clang` in `PATH`. While the existing native_fuzzer_hooks.c can be reused almost unchanged for this purpose on Linux, macOS requires mimicking the interposing logic used by ASan to hook libc funtions at runtime. This commit extracts these platform-specific hooking techniques into preprocessor macros. Further complications arise because macOS codesigning prevents library insertion into `/bin/sh` via `DYLD_INSERT_LIBRARIES` and generally requires all libraries to be preloaded into the `java` process to be codesigned - users may have to click through Gatekeeper warnings to allow this. This requires removing the signatures in CI, where we can't simulate these clicks. Tests fail on macOS 11 for unclear reasons, so we raise the minimum version to 12, which hase been out for almost a year by now. They also fail with JDK 8, which can't be reproduced locally - these tests are skipped in CI.
1 parent 1fa9cef commit 1cfb489

File tree

27 files changed

+612
-839
lines changed

27 files changed

+612
-839
lines changed

.bazelrc

+7-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ build -c opt
77

88
# C/C++
99
common --repo_env=CC=clang
10+
# We want stock LLVM to be configured on macOS rather than Xcode's Apple Clang
11+
# toolchain.
12+
common --repo_env=BAZEL_USE_CPP_ONLY_TOOLCHAIN=1
1013
build --incompatible_enable_cc_toolchain_resolution
1114
# Requires a relatively modern clang.
1215
build:ci --features=layering_check
@@ -31,11 +34,14 @@ run:windows --noincompatible_strict_action_env
3134
build:macos --extra_toolchains=//:java_non_prebuilt_definition
3235
build:macos --java_runtime_version=11
3336
build:macos --tool_java_runtime_version=11
37+
# Allow Jazzer to find LLVM rather than Apple clang. The former is not
38+
# contained in the default PATH set by Bazel.
39+
test:macos --test_env=PATH
40+
run:macos --test_env=PATH
3441

3542
# Toolchain
3643
# Since the toolchain is conditional on OS and architecture, set it on the particular GitHub Action.
3744
build:toolchain --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1
38-
build:toolchain --//third_party:toolchain
3945

4046
# Forward debug variables to tests
4147
test --test_env=JAZZER_AUTOFUZZ_DEBUG

.github/BUILD.bazel

-26
This file was deleted.

.github/workflows/oss-fuzz.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ jobs:
2323

2424
- name: Build Jazzer
2525
# Keep in sync with https://github.com/google/oss-fuzz/blob/221b39181a372ff16c0c813c5963a08aa58f19e2/infra/base-images/base-builder/install_java.sh#L33.
26-
run: bazel build --java_runtime_version=local_jdk_15 -c opt --cxxopt="-stdlib=libc++" --linkopt=-lc++ //agent:jazzer_agent_deploy.jar //launcher:jazzer //launcher:jazzer_asan //launcher:jazzer_ubsan //agent:jazzer_api_deploy.jar
26+
run: bazel build --java_runtime_version=local_jdk_15 -c opt --cxxopt="-stdlib=libc++" --linkopt=-lc++ //agent:jazzer_agent_deploy.jar //launcher:jazzer //agent:jazzer_api_deploy.jar
2727

2828
- name: Test Jazzer build
2929
# Keep in sync with https://github.com/google/oss-fuzz/blob/221b39181a372ff16c0c813c5963a08aa58f19e2/infra/base-images/base-builder/install_java.sh#L35-L36.
30-
run: "test -f bazel-bin/agent/jazzer_agent_deploy.jar && test -f bazel-bin/launcher/jazzer && test -f bazel-bin/launcher/jazzer_asan && test -f bazel-bin/launcher/jazzer_ubsan && test -f bazel-bin/agent/jazzer_api_deploy.jar"
30+
run: "test -f bazel-bin/agent/jazzer_agent_deploy.jar && test -f bazel-bin/launcher/jazzer && test -f bazel-bin/agent/jazzer_api_deploy.jar"

.github/workflows/release.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
bazel_args: "--config=toolchain --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-linux"
1717
- os: macos-11
1818
arch: "macos-x86_64"
19-
bazel_args: "--config=toolchain --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-darwin --xcode_version_config=//.github:host_xcodes"
19+
bazel_args: "--config=toolchain --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-darwin"
2020
- os: windows-2019
2121
arch: "windows"
2222
bazel_args: ""

.github/workflows/run-all-tests.yml

+25-6
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@ jobs:
1414
runs-on: ${{ matrix.os }}
1515
strategy:
1616
matrix:
17-
os: [ubuntu-20.04, macos-11, windows-2019]
17+
os: [ubuntu-20.04, macos-12, windows-2019]
1818
jdk: [8, 17]
1919
include:
2020
- os: ubuntu-20.04
2121
arch: "linux"
2222
cache: "/home/runner/.cache/bazel-disk"
23-
- os: macos-11
23+
- os: macos-12
2424
arch: "macos-x86_64"
25-
# Always use the toolchain as UBSan produces linker errors with Apple LLVM 13.
26-
bazel_args: "--config=toolchain --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-darwin --xcode_version_config=//.github:host_xcodes"
25+
# Inherit CC from the environment so that LLVM clang is picked over Apple clang.
26+
# Also work around https://github.com/bazelbuild/bazel/issues/13944#issuecomment-1271745466.
27+
bazel_args: "--repo_env=CC --features=-layering_check"
2728
cache: "/private/var/tmp/bazel-disk"
2829
- os: windows-2019
2930
arch: "windows"
@@ -33,10 +34,17 @@ jobs:
3334
- uses: actions/checkout@v2
3435

3536
- name: Set up JDK
36-
uses: actions/setup-java@v1
37+
uses: actions/setup-java@v3
3738
with:
39+
distribution: zulu
3840
java-version: ${{ matrix.jdk }}
3941

42+
# The java binary has the necessary entitlements to allow tests to pass, but that requires
43+
# user interaction (clicking through Gatekeeper warnings) that we can't simulate in CI.
44+
- name: Remove codesign signature on java binary
45+
if: contains(matrix.os, 'mac')
46+
run: codesign --remove-signature "$JAVA_HOME"/bin/java
47+
4048
- name: Set up MSYS2 to work around bazelbuild/bazel#15919
4149
if: contains(matrix.os, 'windows')
4250
shell: cmd
@@ -55,11 +63,22 @@ jobs:
5563
run: .github/scripts/echoBuildBuddyConfig.sh ${{ secrets.BUILDBUDDY_API_KEY }} >> $GITHUB_ENV
5664
shell: bash
5765

66+
- name: Set CC to LLVM clang on macOS
67+
if: contains(matrix.os, 'mac')
68+
run: |
69+
export CC="$(brew --prefix llvm@14)/bin/clang"
70+
which "$CC"
71+
"$CC" --version
72+
"$CC" -print-resource-dir
73+
"$CC" --print-file-name libclang_rt.asan_osx_dynamic.dylib
74+
echo "CC=$CC" >> $GITHUB_ENV
75+
shell: bash
76+
5877
- name: Build
5978
run: bazelisk build ${{env.BUILD_BUDDY_CONFIG}} --java_runtime_version=local_jdk_${{ matrix.jdk }} --disk_cache=${{ matrix.cache }} ${{ matrix.bazel_args }} //...
6079

6180
- name: Test
62-
run: bazelisk test ${{env.BUILD_BUDDY_CONFIG}} --java_runtime_version=local_jdk_${{ matrix.jdk }} --disk_cache=${{ matrix.cache }} ${{ matrix.bazel_args }} //...
81+
run: bazelisk test ${{env.BUILD_BUDDY_CONFIG}} --java_runtime_version=local_jdk_${{ matrix.jdk }} --disk_cache=${{ matrix.cache }} ${{ matrix.bazel_args }} --test_tag_filters=-no-${{ matrix.arch }}-jdk${{ matrix.jdk }} //...
6382

6483
- name: Upload test logs
6584
if: always()

BUILD.bazel

-10
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,6 @@ alias(
4545
actual = "//launcher:jazzer",
4646
)
4747

48-
alias(
49-
name = "jazzer_asan",
50-
actual = "//launcher:jazzer_asan",
51-
)
52-
53-
alias(
54-
name = "jazzer_ubsan",
55-
actual = "//launcher:jazzer_ubsan",
56-
)
57-
5848
exports_files([
5949
"jazzer-api.pom",
6050
])

README.md

+8-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ native libraries.
1414

1515
Jazzer currently supports the following platforms:
1616
* Linux x86_64
17-
* macOS 10.15+ x86_64 (experimental support for arm64)
17+
* macOS 12+ x86_64 & arm64
1818
* Windows x86_64
1919

2020
## News: Jazzer available in OSS-Fuzz
@@ -504,9 +504,14 @@ the native libraries. The required compilation flags for native libraries are as
504504
- *AddressSanitizer*: `-fsanitize=fuzzer-no-link,address`
505505
- *UndefinedBehaviorSanitizer*: `-fsanitize=fuzzer-no-link,undefined` (add `-fno-sanitize-recover=all` to crash on UBSan reports)
506506

507-
Then, use the appropriate launcher `//:jazzer_asan` or `//:jazzer_ubsan`.
507+
Then, start Jazzer with `--asan` and/or `--ubsan` to automatically preload the sanitizer runtimes.
508+
Jazzer defaults to using the runtimes associated with `clang` on the `PATH`.
509+
If you used a different compiler to compile the native libraries, specify it with `CC` to override this default.
508510

509-
**Note:** Sanitizers other than AddressSanitizer and UndefinedBehaviorSanitizer are not yet supported.
511+
**Note:** On macOS, you may see Gatekeeper warnings when using `--asan` and/or `--ubsan` since these flags cause the
512+
native sanitizer libraries to be preloaded into the codesigned `java` executable via `DYLD_INSERT_LIBRARIES`.
513+
514+
Sanitizers other than AddressSanitizer and UndefinedBehaviorSanitizer are not yet supported.
510515
Furthermore, due to the nature of the JVM's GC, LeakSanitizer reports too many false positives to be useful and is thus disabled.
511516

512517
The fuzz targets `ExampleFuzzerWithNativeASan` and `ExampleFuzzerWithNativeUBSan` in the `examples/` directory contain

WORKSPACE.bazel

-6
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,6 @@ filegroup(
2424

2525
http_archive(
2626
name = "com_grail_bazel_toolchain",
27-
patches = [
28-
# There is no static runtime library for ASan on macOS, so when using
29-
# the toolchain in the CI, we have to explicitly depend on the dylib and
30-
# add it to the runfiles for clang/ld.
31-
"//third_party:bazel-toolchain-export-dynamic-macos-asan.patch",
32-
],
3327
sha256 = "da607faed78c4cb5a5637ef74a36fdd2286f85ca5192222c4664efec2d529bb8",
3428
strip_prefix = "bazel-toolchain-0.6.3",
3529
urls = ["https://github.com/grailbio/bazel-toolchain/archive/refs/tags/0.6.3.tar.gz"],

bazel/fuzz_target.bzl

+2-4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def java_fuzz_target_test(
2727
size = None,
2828
timeout = None,
2929
env = None,
30+
env_inherit = None,
3031
verify_crash_input = True,
3132
verify_crash_reproducer = True,
3233
# Superset of the findings the fuzzer is expected to find. Since fuzzing runs are not
@@ -61,10 +62,6 @@ def java_fuzz_target_test(
6162

6263
if launcher_variant == "native":
6364
driver = "//launcher:jazzer"
64-
elif launcher_variant == "address":
65-
driver = "//launcher:jazzer_asan"
66-
elif launcher_variant == "undefined":
67-
driver = "//launcher:jazzer_ubsan"
6865
elif launcher_variant == "java":
6966
driver = target_name
7067
else:
@@ -106,6 +103,7 @@ def java_fuzz_target_test(
106103
driver,
107104
] + data + ([hook_jar] if hook_jar else []),
108105
env = env,
106+
env_inherit = env_inherit,
109107
main_class = "com.code_intelligence.jazzer.tools.FuzzTargetTestWrapper",
110108
use_testrunner = False,
111109
tags = tags,

bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
import java.util.Collections;
3535
import java.util.Comparator;
3636
import java.util.List;
37-
import java.util.Map;
3837
import java.util.Set;
3938
import java.util.stream.Collectors;
4039
import java.util.stream.Stream;
@@ -91,9 +90,12 @@ public static void main(String[] args) {
9190
}
9291

9392
ProcessBuilder processBuilder = new ProcessBuilder();
94-
Map<String, String> environment = processBuilder.environment();
9593
// Ensure that Jazzer can find its runfiles.
96-
environment.putAll(runfiles.getEnvVars());
94+
processBuilder.environment().putAll(runfiles.getEnvVars());
95+
// Ensure that sanitizers behave consistently across OSes and use a dedicated exit code to make
96+
// them distinguishable from unexpected crashes.
97+
processBuilder.environment().put("ASAN_OPTIONS", "abort_on_error=0:exitcode=76");
98+
processBuilder.environment().put("UBSAN_OPTIONS", "abort_on_error=0:exitcode=76");
9799

98100
// Crashes will be available as test outputs. These are cleared on the next run,
99101
// so this is only useful for examples.

driver/src/main/java/com/code_intelligence/jazzer/BUILD.bazel

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
load("@bazel_skylib//rules:write_file.bzl", "write_file")
2+
load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library")
23
load("//:maven.bzl", "JAZZER_VERSION")
34

45
java_binary(
@@ -7,9 +8,13 @@ java_binary(
78
runtime_deps = [":jazzer_lib"],
89
)
910

10-
java_library(
11+
java_jni_library(
1112
name = "jazzer_lib",
1213
srcs = ["Jazzer.java"],
14+
native_libs = select({
15+
"@platforms//os:windows": [],
16+
"//conditions:default": ["//driver/src/main/native/com/code_intelligence/jazzer:jazzer_preload"],
17+
}),
1318
visibility = ["//visibility:public"],
1419
deps = ["//driver/src/main/java/com/code_intelligence/jazzer/driver"],
1520
)

0 commit comments

Comments
 (0)