diff options
-rw-r--r-- | .github/workflows/build-replayer.yml | 76 | ||||
-rw-r--r-- | agent/src/main/java/com/code_intelligence/jazzer/replay/BUILD.bazel | 18 | ||||
-rw-r--r-- | agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer.java | 159 | ||||
-rw-r--r-- | agent/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel | 25 | ||||
-rw-r--r-- | agent/src/main/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImpl.java | 4 | ||||
-rw-r--r-- | agent/src/main/native/com/code_intelligence/jazzer/replay/BUILD.bazel | 13 | ||||
-rw-r--r-- | agent/src/main/native/com/code_intelligence/jazzer/replay/com_code_intelligence_jazzer_replay_Replayer.cpp | 48 | ||||
-rw-r--r-- | driver/BUILD.bazel | 10 | ||||
-rw-r--r-- | examples/BUILD.bazel | 30 | ||||
-rwxr-xr-x | examples/check_for_finding.sh | 30 | ||||
-rw-r--r-- | examples/json_sanitizer_denylist_crash | bin | 0 -> 38 bytes |
11 files changed, 401 insertions, 12 deletions
diff --git a/.github/workflows/build-replayer.yml b/.github/workflows/build-replayer.yml new file mode 100644 index 00000000..aa617416 --- /dev/null +++ b/.github/workflows/build-replayer.yml @@ -0,0 +1,76 @@ +name: Release replayer + +on: + workflow_dispatch: + +jobs: + + build_replayer: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-10.15] + include: + - os: ubuntu-latest + arch: "linux" + cache: "/home/runner/.cache/bazel-disk" + - os: macos-10.15 + arch: "darwin" + cache: "/private/var/tmp/bazel-disk" + + steps: + - uses: actions/checkout@v2 + + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 8 + + - name: Mount Bazel disk cache + uses: actions/cache@v2 + with: + path: ${{ matrix.cache }} + key: bazel-disk-cache-${{ matrix.arch }}-8 + + - name: Build + run: | + bazelisk build --config=ci --remote_header=x-buildbuddy-api-key=${{ secrets.BUILDBUDDY_API_KEY }} --disk_cache=${{ matrix.cache }} --java_runtime_version=localjdk_${{ matrix.jdk }} //agent/src/main/java/com/code_intelligence/jazzer/replay:Replayer_deploy.jar + cp -L bazel-bin/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer_deploy.jar replayer.jar + + - name: Upload test logs + if: always() + uses: actions/upload-artifact@v2 + with: + name: replayer_${{ matrix.arch }}.jar + path: replayer.jar + + merge_jars: + runs-on: ubuntu-latest + needs: build_replayer + + steps: + - name: Download macOS jar + uses: actions/download-artifact@v2 + with: + name: replayer_darwin.jar + path: replayer_darwin + + - name: Download Linux jar + uses: actions/download-artifact@v2 + with: + name: replayer_linux.jar + path: replayer_linux + + - name: Merge jars + run: | + mkdir merged + unzip -o replayer_darwin/replayer.jar -d merged + unzip -o replayer_linux/replayer.jar -d merged + jar cvmf merged/META-INF/MANIFEST.MF replayer.jar -C merged . + + - name: Upload merged jar + uses: actions/upload-artifact@v2 + with: + name: replayer.jar + path: replayer.jar + diff --git a/agent/src/main/java/com/code_intelligence/jazzer/replay/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/replay/BUILD.bazel new file mode 100644 index 00000000..37365d8c --- /dev/null +++ b/agent/src/main/java/com/code_intelligence/jazzer/replay/BUILD.bazel @@ -0,0 +1,18 @@ +load("@fmeum_rules_jni//jni:defs.bzl", "java_library_with_native") + +java_library_with_native( + name = "replay", + srcs = ["Replayer.java"], + native_libs = ["//agent/src/main/native/com/code_intelligence/jazzer/replay"], + visibility = ["//agent/src/main/native/com/code_intelligence/jazzer/replay:__pkg__"], + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/api", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:fuzzed_data_provider", + ], +) + +java_binary( + name = "Replayer", + visibility = ["//visibility:public"], + runtime_deps = [":replay"], +) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer.java b/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer.java new file mode 100644 index 00000000..fc6bfc4f --- /dev/null +++ b/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer.java @@ -0,0 +1,159 @@ +// Copyright 2021 Code Intelligence GmbH +// +// 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.code_intelligence.jazzer.replay; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import com.code_intelligence.jazzer.runtime.FuzzedDataProviderImpl; +import com.github.fmeum.rules_jni.RulesJni; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; + +public class Replayer { + public static final int STATUS_FINDING = 77; + public static final int STATUS_OTHER_ERROR = 1; + + static { + try { + RulesJni.loadLibrary("replay", Replayer.class); + } catch (Throwable t) { + t.printStackTrace(); + System.exit(STATUS_OTHER_ERROR); + } + } + + public static void main(String[] args) { + if (args.length < 2) { + System.err.println("Usage: <fuzz target class> <input file path> <fuzzerInitialize args>..."); + System.exit(STATUS_OTHER_ERROR); + } + ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true); + + Class<?> fuzzTargetClass; + try { + fuzzTargetClass = Class.forName(args[0]); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + System.exit(STATUS_OTHER_ERROR); + // Without this return the compiler sees fuzzTargetClass as possibly uninitialized. + return; + } + + String inputFilePath = args[1]; + byte[] input = loadInput(inputFilePath); + + String[] fuzzTargetArgs = Arrays.copyOfRange(args, 2, args.length); + executeFuzzerInitialize(fuzzTargetClass, fuzzTargetArgs); + executeFuzzTarget(fuzzTargetClass, input); + } + + private static byte[] loadInput(String inputFilePath) { + try { + return Files.readAllBytes(Paths.get(inputFilePath)); + } catch (IOException e) { + e.printStackTrace(); + System.exit(STATUS_OTHER_ERROR); + // Without this return the compiler sees loadInput as possibly not returning a value. + return null; + } + } + + private static void executeFuzzerInitialize(Class<?> fuzzTarget, String[] args) { + // public static void fuzzerInitialize() + try { + Method fuzzerInitialize = fuzzTarget.getMethod("fuzzerInitialize"); + fuzzerInitialize.invoke(null); + return; + } catch (Exception e) { + handleInvokeException(e, fuzzTarget); + } + // public static void fuzzerInitialize(String[] args) + try { + Method fuzzerInitialize = fuzzTarget.getMethod("fuzzerInitialize", String[].class); + fuzzerInitialize.invoke(null, (Object) args); + } catch (Exception e) { + handleInvokeException(e, fuzzTarget); + } + } + + public static void executeFuzzTarget(Class<?> fuzzTarget, byte[] input) { + // public static void fuzzerTestOneInput(byte[] input) + try { + Method fuzzerTestOneInput = fuzzTarget.getMethod("fuzzerTestOneInput", byte[].class); + fuzzerTestOneInput.invoke(null, (Object) input); + return; + } catch (Exception e) { + handleInvokeException(e, fuzzTarget); + } + // public static void fuzzerTestOneInput(FuzzedDataProvider data) + try { + Method fuzzerTestOneInput = + fuzzTarget.getMethod("fuzzerTestOneInput", FuzzedDataProvider.class); + fuzzerTestOneInput.invoke(null, makeFuzzedDataProvider(input)); + return; + } catch (Exception e) { + handleInvokeException(e, fuzzTarget); + } + System.err.printf("%s must define exactly one of the following two functions:%n" + + " public static void fuzzerTestOneInput(byte[] ...)%n" + + " public static void fuzzerTestOneInput(FuzzedDataProvider ...)%n" + + "Note: Fuzz targets returning boolean are no longer supported; exceptions should%n" + + "be thrown instead of returning true.%n", + fuzzTarget.getName()); + System.exit(STATUS_OTHER_ERROR); + } + + private static void handleInvokeException(Exception e, Class<?> fuzzTarget) { + if (e instanceof NoSuchMethodException) + return; + if (e instanceof InvocationTargetException) { + filterOutOwnStackTraceElements(e.getCause(), fuzzTarget); + e.getCause().printStackTrace(); + System.exit(STATUS_FINDING); + } else { + e.printStackTrace(); + System.exit(STATUS_OTHER_ERROR); + } + } + + private static void filterOutOwnStackTraceElements(Throwable t, Class<?> fuzzTarget) { + if (t.getCause() != null) + filterOutOwnStackTraceElements(t.getCause(), fuzzTarget); + if (t.getStackTrace() == null || t.getStackTrace().length == 0) + return; + StackTraceElement lowestFrame = t.getStackTrace()[t.getStackTrace().length - 1]; + if (!lowestFrame.getClassName().equals(Replayer.class.getName()) + || !lowestFrame.getMethodName().equals("main")) + return; + for (int i = t.getStackTrace().length - 1; i >= 0; i--) { + StackTraceElement frame = t.getStackTrace()[i]; + if (frame.getClassName().equals(fuzzTarget.getName()) + && frame.getMethodName().equals("fuzzerTestOneInput")) { + t.setStackTrace(Arrays.copyOfRange(t.getStackTrace(), 0, i + 1)); + break; + } + } + } + + private static FuzzedDataProvider makeFuzzedDataProvider(byte[] input) { + feedFuzzedDataProvider(input); + return new FuzzedDataProviderImpl(); + } + + private static native void feedFuzzedDataProvider(byte[] input); +} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel index 764387f0..ff05e026 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel @@ -1,13 +1,30 @@ load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") +java_library( + name = "fuzzed_data_provider", + srcs = [ + "FuzzedDataProviderImpl.java", + ], + visibility = ["//agent/src/main/java/com/code_intelligence/jazzer/replay:__pkg__"], + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/api", + ], +) + kt_jvm_library( name = "runtime", - srcs = glob([ - "*.java", - "*.kt", - ]), + srcs = glob( + [ + "*.java", + "*.kt", + ], + exclude = [ + "FuzzedDataProviderImpl.java", + ], + ), visibility = ["//visibility:public"], deps = [ + ":fuzzed_data_provider", "//agent/src/main/java/com/code_intelligence/jazzer/api", "//agent/src/main/java/com/code_intelligence/jazzer/utils", ], diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImpl.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImpl.java index 0f4708e5..fe4d8ac7 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImpl.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImpl.java @@ -16,8 +16,8 @@ package com.code_intelligence.jazzer.runtime; import com.code_intelligence.jazzer.api.FuzzedDataProvider; -class FuzzedDataProviderImpl implements FuzzedDataProvider { - FuzzedDataProviderImpl() {} +public class FuzzedDataProviderImpl implements FuzzedDataProvider { + public FuzzedDataProviderImpl() {} @Override public native boolean consumeBoolean(); diff --git a/agent/src/main/native/com/code_intelligence/jazzer/replay/BUILD.bazel b/agent/src/main/native/com/code_intelligence/jazzer/replay/BUILD.bazel new file mode 100644 index 00000000..709156c3 --- /dev/null +++ b/agent/src/main/native/com/code_intelligence/jazzer/replay/BUILD.bazel @@ -0,0 +1,13 @@ +load("@fmeum_rules_jni//jni:defs.bzl", "java_native_library") + +java_native_library( + name = "replay", + srcs = [ + "com_code_intelligence_jazzer_replay_Replayer.cpp", + ], + java_lib = "//agent/src/main/java/com/code_intelligence/jazzer/replay", + visibility = ["//agent/src/main/java/com/code_intelligence/jazzer/replay:__pkg__"], + deps = [ + "//driver:fuzzed_data_provider", + ], +) diff --git a/agent/src/main/native/com/code_intelligence/jazzer/replay/com_code_intelligence_jazzer_replay_Replayer.cpp b/agent/src/main/native/com/code_intelligence/jazzer/replay/com_code_intelligence_jazzer_replay_Replayer.cpp new file mode 100644 index 00000000..c4bdfcfb --- /dev/null +++ b/agent/src/main/native/com/code_intelligence/jazzer/replay/com_code_intelligence_jazzer_replay_Replayer.cpp @@ -0,0 +1,48 @@ +// Copyright 2021 Code Intelligence GmbH +// +// 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. + +#include "com_code_intelligence_jazzer_replay_Replayer.h" + +#include <jni.h> + +#include "driver/fuzzed_data_provider.h" + +namespace { +uint8_t *data = nullptr; +} + +void Java_com_code_1intelligence_jazzer_replay_Replayer_feedFuzzedDataProvider( + JNIEnv *env, jclass, jbyteArray input) { + if (data == nullptr) { + jazzer::SetUpFuzzedDataProvider(*env); + } else { + delete[] data; + } + + std::size_t size = env->GetArrayLength(input); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->FatalError("Failed to get length of input"); + } + data = static_cast<uint8_t *>(operator new(size)); + if (data == nullptr) { + env->FatalError("Failed to allocate memory for a copy of the input"); + } + env->GetByteArrayRegion(input, 0, size, reinterpret_cast<jbyte *>(data)); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->FatalError("Failed to copy input"); + } + jazzer::FeedFuzzedDataProvider(data, size); +} diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index f15a2ddd..d0e0d531 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -1,10 +1,5 @@ load("//bazel:cc.bzl", "cc_17_library") -# None of the targets in this package should be built into shared objects. By -# disabling the supports_pic feature, we reduce build time otherwise spent on -# .pic.o files. -package(features = ["-supports_pic"]) - cc_library( name = "sanitizer_hooks_with_pc", srcs = ["sanitizer_hooks_with_pc.cpp"], @@ -31,9 +26,12 @@ cc_library( hdrs = [ "fuzzed_data_provider.h", ], + visibility = [ + "//agent/src/main/native/com/code_intelligence/jazzer/replay:__pkg__", + ], deps = [ - "@bazel_tools//tools/jdk:jni", "@com_google_absl//absl/strings:str_format", + "@fmeum_rules_jni//jni", ], ) diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index ca17ba3f..d721b81d 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -150,6 +150,36 @@ java_fuzz_target_test( ], ) +java_binary( + name = "JsonSanitizerDenylistCrash", + args = [ + "com.example.JsonSanitizerDenylistFuzzer", + "$(location :json_sanitizer_denylist_crash)", + ], + data = [ + ":json_sanitizer_denylist_crash", + ], + main_class = "com.code_intelligence.jazzer.replay.Replayer", + runtime_deps = [ + ":JsonSanitizerDenylistFuzzer_target_deploy.jar", + "//agent/src/main/java/com/code_intelligence/jazzer/replay:Replayer_deploy.jar", + ], +) + +sh_test( + name = "JsonSanitizerDenylistCrashTest", + srcs = ["check_for_finding.sh"], + args = [ + "$(location :JsonSanitizerDenylistCrash)", + "com.example.JsonSanitizerDenylistFuzzer", + "$(location :json_sanitizer_denylist_crash)", + ], + data = [ + ":JsonSanitizerDenylistCrash", + ":json_sanitizer_denylist_crash", + ], +) + java_fuzz_target_test( name = "JsonSanitizerIdempotenceFuzzer", srcs = [ diff --git a/examples/check_for_finding.sh b/examples/check_for_finding.sh new file mode 100755 index 00000000..f60c011c --- /dev/null +++ b/examples/check_for_finding.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Copyright 2021 Code Intelligence GmbH +# +# 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. + +eval "$@" +# Assert that we either found a crash in java (exit code 77) or an ASan crash +# (exit code 76). +declare -i exit_code=$? +if [ $exit_code -eq 77 ] || [ $exit_code -eq 76 ] +then + if [ "$(ls "$DEFAULT_CRASH_PREFIX/")" ]; then + exit 0 + else + exit 1 + fi +else + echo "Unexpected exit code: $exit_code" + exit 1 +fi diff --git a/examples/json_sanitizer_denylist_crash b/examples/json_sanitizer_denylist_crash Binary files differnew file mode 100644 index 00000000..7324203a --- /dev/null +++ b/examples/json_sanitizer_denylist_crash |