From d46133ac1baf7678c17008940af34e89161c47b7 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 8 Dec 2021 16:04:33 +0100 Subject: Port the hook trampoline to arm64 --- README.md | 5 ++++- driver/sanitizer_hooks_with_pc.cpp | 39 ++++++++++++++++++++++++++++++--- driver/sanitizer_hooks_with_pc_test.cpp | 35 +++++++++++++++-------------- 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 07bbdda1..ef2bfd93 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,10 @@ It is based on [libFuzzer](https://llvm.org/docs/LibFuzzer.html) and brings many The JVM bytecode is executed inside the fuzzer process, which ensures fast execution speeds and allows seamless fuzzing of native libraries. -Jazzer supports Linux and (experimentally) macOS 10.15 and 11 as well as Windows, all on the x64 architecture. +Jazzer currently supports the following platforms: +* Linux x86_64 +* macOS 10.15+ x86_64 (experimental support for arm64) +* Windows x86_64 ## News: Jazzer available in OSS-Fuzz diff --git a/driver/sanitizer_hooks_with_pc.cpp b/driver/sanitizer_hooks_with_pc.cpp index bb3ec5e1..08258d8f 100644 --- a/driver/sanitizer_hooks_with_pc.cpp +++ b/driver/sanitizer_hooks_with_pc.cpp @@ -41,13 +41,22 @@ // but take the return address as an argument and thus don't require the // indirection through a trampoline. -#define REPEAT_8(a) a a a a a a a a +#define REPEAT_2(a) a a + +#define REPEAT_8(a) REPEAT_2(REPEAT_2(REPEAT_2(a))) + +#define REPEAT_128(a) REPEAT_2(REPEAT_8(REPEAT_8(a))) #define REPEAT_512(a) REPEAT_8(REPEAT_8(REPEAT_8(a))) // The first four registers to pass arguments in according to the // platform-specific x64 calling convention. -#ifdef _WIN64 +#ifdef __aarch64__ +#define REG_1 "x0" +#define REG_2 "x1" +#define REG_3 "x2" +#define REG_4 "x3" +#elif _WIN64 #define REG_1 "rcx" #define REG_2 "rdx" #define REG_3 "r8" @@ -64,13 +73,36 @@ // offset. __attribute__((noinline)) void trampoline(uint64_t arg1, uint64_t arg2, void *func, uint16_t fake_pc) { - // arg1 and arg2 have to be forwarded according to the x64 calling convention. + // arg1 and arg2 have to be forwarded according to the calling convention. // We also fix func and fake_pc to their registers so that we can safely use // rax below. [[maybe_unused]] register uint64_t arg1_loc asm(REG_1) = arg1; [[maybe_unused]] register uint64_t arg2_loc asm(REG_2) = arg2; [[maybe_unused]] register void *func_loc asm(REG_3) = func; [[maybe_unused]] register uint64_t fake_pc_loc asm(REG_4) = fake_pc; +#ifdef __aarch64__ + asm volatile( + // Load address of the nop sled into the default register for the return + // address (offset of four instructions, which means 16 bytes). + "adr x30, 16 \n\t" + // Clear the lowest 2 bits of fake_pc. All arm64 instructions are four + // bytes long, so we can't get better return address granularity than + // multiples of 4. + "and %[fake_pc], %[fake_pc], #0xFFFFFFFFFFFFFFFC \n\t" + // Add the offset of the fake_pc-th ret (rounded to 0 mod 4 above). + "add x30, x30, %[fake_pc], lsl 0 \n\t" + // Call the function by jumping to it and reusing all registers except + // for the modified return address register r30. + "br %[func] \n\t" + // The nop sled for arm64 consists of 128 nop instructions, each of which + // is 4 bytes long. It thus has the same byte length of 4 * 128 = 512 as + // the x86_64 sled, but coarser granularity. + REPEAT_128("nop \n\t") + : + : "r"(arg1_loc), + "r"(arg2_loc), [func] "r"(func_loc), [fake_pc] "r"(fake_pc_loc) + : "memory", "x30"); +#else asm volatile goto( // Load RIP-relative address of the end of this function. "lea %l[end_of_function](%%rip), %%rax \n\t" @@ -96,6 +128,7 @@ __attribute__((noinline)) void trampoline(uint64_t arg1, uint64_t arg2, end_of_function: return; +#endif } namespace { diff --git a/driver/sanitizer_hooks_with_pc_test.cpp b/driver/sanitizer_hooks_with_pc_test.cpp index 71d1527b..f18ba147 100644 --- a/driver/sanitizer_hooks_with_pc_test.cpp +++ b/driver/sanitizer_hooks_with_pc_test.cpp @@ -54,13 +54,20 @@ void __sanitizer_cov_trace_pc_indir(uintptr_t callee) { RecordCoverage(); } void ClearCoverage() { std::fill(gCoverageMap.begin(), gCoverageMap.end(), 0); } -bool HasAllPcsCovered() { - return 0 == std::count(gCoverageMap.cbegin(), gCoverageMap.cend(), 0); +bool HasOptimalPcCoverage() { +#ifdef __aarch64__ + // All arm64 instructions are four bytes long and aligned to four bytes, so + // the lower two bits of each PC are fixed to 00. + return std::count(gCoverageMap.cbegin(), gCoverageMap.cend(), 0) <= + 3 * gCoverageMap.size() / 4; +#else + return std::count(gCoverageMap.cbegin(), gCoverageMap.cend(), 0) == 0; +#endif } bool HasSingleCoveredPc() { - return gCoverageMap.size() - 1 == - std::count(gCoverageMap.cbegin(), gCoverageMap.cend(), 0); + return std::count(gCoverageMap.cbegin(), gCoverageMap.cend(), 0) == + gCoverageMap.size() - 1; } std::string PrettyPrintCoverage() { @@ -134,55 +141,49 @@ TEST_F(TestFakePcTrampoline, TracePcIndirDirect) { TEST_F(TestFakePcTrampoline, TraceCmp4Trampoline) { for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { __sanitizer_cov_trace_cmp4_with_pc(reinterpret_cast(i), i, i); - EXPECT_EQ(1, gCoverageMap[i]); } - EXPECT_TRUE(HasAllPcsCovered()) << PrettyPrintCoverage(); + EXPECT_TRUE(HasOptimalPcCoverage()) << PrettyPrintCoverage(); } TEST_F(TestFakePcTrampoline, TraceCmp8Trampoline) { for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { __sanitizer_cov_trace_cmp8_with_pc(reinterpret_cast(i), i, i); - EXPECT_EQ(1, gCoverageMap[i]); } - EXPECT_TRUE(HasAllPcsCovered()) << PrettyPrintCoverage(); + EXPECT_TRUE(HasOptimalPcCoverage()) << PrettyPrintCoverage(); } TEST_F(TestFakePcTrampoline, TraceSwitchTrampoline) { for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { __sanitizer_cov_trace_switch_with_pc(reinterpret_cast(i), i, nullptr); - EXPECT_EQ(1, gCoverageMap[i]); } - EXPECT_TRUE(HasAllPcsCovered()) << PrettyPrintCoverage(); + EXPECT_TRUE(HasOptimalPcCoverage()) << PrettyPrintCoverage(); } TEST_F(TestFakePcTrampoline, TraceDiv4Trampoline) { for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { __sanitizer_cov_trace_div4_with_pc(reinterpret_cast(i), i); - EXPECT_EQ(1, gCoverageMap[i]); } - EXPECT_TRUE(HasAllPcsCovered()) << PrettyPrintCoverage(); + EXPECT_TRUE(HasOptimalPcCoverage()) << PrettyPrintCoverage(); } TEST_F(TestFakePcTrampoline, TraceDiv8Trampoline) { for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { __sanitizer_cov_trace_div8_with_pc(reinterpret_cast(i), i); - EXPECT_EQ(1, gCoverageMap[i]); } - EXPECT_TRUE(HasAllPcsCovered()) << PrettyPrintCoverage(); + EXPECT_TRUE(HasOptimalPcCoverage()) << PrettyPrintCoverage(); } TEST_F(TestFakePcTrampoline, TraceGepTrampoline) { for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { __sanitizer_cov_trace_gep_with_pc(reinterpret_cast(i), i); - EXPECT_EQ(1, gCoverageMap[i]); } - EXPECT_TRUE(HasAllPcsCovered()) << PrettyPrintCoverage(); + EXPECT_TRUE(HasOptimalPcCoverage()) << PrettyPrintCoverage(); } TEST_F(TestFakePcTrampoline, TracePcIndirTrampoline) { for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { __sanitizer_cov_trace_pc_indir_with_pc(reinterpret_cast(i), i); } - EXPECT_TRUE(HasAllPcsCovered()) << PrettyPrintCoverage(); + EXPECT_TRUE(HasOptimalPcCoverage()) << PrettyPrintCoverage(); } -- cgit v1.2.3 From 1d30969a5be8471e2275aad4fb74e870476e8ff4 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 31 Dec 2021 11:09:18 +0100 Subject: Fix default method return value recording Using a proxy to implement RecordingFuzzedDataProvider led to a subtle bug related to default methods: Since the invocation handler cannot pass the proxy instance itself to invoke (this would create a cycle), calls to non-default methods that were internal to default methods were not recorded as they occured on the original object. There does not seem to be a way to fix this with proxies without either making the FuzzedDataProvider interface aware of it being proxied (which likely incurs a performance penalty while fuzzing) or duplicating default method implementations. A simple and less laborious fix is to implement the proxy object by hand with every method delegating to the target and recording the return value. --- .../runtime/RecordingFuzzedDataProvider.java | 186 ++++++++++++++++++--- 1 file changed, 167 insertions(+), 19 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider.java index 976e024c..f17da356 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider.java @@ -26,41 +26,34 @@ import java.util.Base64; // Wraps the native FuzzedDataProviderImpl and serializes all its return values // into a Base64-encoded string. -final class RecordingFuzzedDataProvider implements InvocationHandler { - private final FuzzedDataProvider target = new FuzzedDataProviderImpl(); +final class RecordingFuzzedDataProvider implements FuzzedDataProvider { + private final FuzzedDataProvider target; private final ArrayList recordedReplies = new ArrayList<>(); - private RecordingFuzzedDataProvider() {} + private RecordingFuzzedDataProvider(FuzzedDataProvider target) { + this.target = target; + } // Called from native code. public static FuzzedDataProvider makeFuzzedDataProviderProxy() { - return (FuzzedDataProvider) Proxy.newProxyInstance( - RecordingFuzzedDataProvider.class.getClassLoader(), new Class[] {FuzzedDataProvider.class}, - new RecordingFuzzedDataProvider()); + return makeFuzzedDataProviderProxy(new FuzzedDataProviderImpl()); + } + + static FuzzedDataProvider makeFuzzedDataProviderProxy(FuzzedDataProvider target) { + return new RecordingFuzzedDataProvider(target); } // Called from native code. public static String serializeFuzzedDataProviderProxy(FuzzedDataProvider proxy) throws IOException { - return ((RecordingFuzzedDataProvider) Proxy.getInvocationHandler(proxy)).serialize(); + return ((RecordingFuzzedDataProvider) proxy).serialize(); } - private Object recordAndReturn(Object object) { + private T recordAndReturn(T object) { recordedReplies.add(object); return object; } - @Override - public Object invoke(Object object, Method method, Object[] args) throws Throwable { - if (method.isDefault()) { - // Default methods in FuzzedDataProvider are implemented in Java and - // don't need to be recorded. - return method.invoke(target, args); - } else { - return recordAndReturn(method.invoke(target, args)); - } - } - private String serialize() throws IOException { byte[] rawOut; try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream()) { @@ -71,4 +64,159 @@ final class RecordingFuzzedDataProvider implements InvocationHandler { } return Base64.getEncoder().encodeToString(rawOut); } + + @Override + public boolean consumeBoolean() { + return recordAndReturn(target.consumeBoolean()); + } + + @Override + public boolean[] consumeBooleans(int maxLength) { + return recordAndReturn(target.consumeBooleans(maxLength)); + } + + @Override + public byte consumeByte() { + return recordAndReturn(target.consumeByte()); + } + + @Override + public byte consumeByte(byte min, byte max) { + return recordAndReturn(target.consumeByte(min, max)); + } + + @Override + public byte[] consumeBytes(int maxLength) { + return recordAndReturn(target.consumeBytes(maxLength)); + } + + @Override + public byte[] consumeRemainingAsBytes() { + return recordAndReturn(target.consumeRemainingAsBytes()); + } + + @Override + public short consumeShort() { + return recordAndReturn(target.consumeShort()); + } + + @Override + public short consumeShort(short min, short max) { + return recordAndReturn(target.consumeShort(min, max)); + } + + @Override + public short[] consumeShorts(int maxLength) { + return recordAndReturn(target.consumeShorts(maxLength)); + } + + @Override + public int consumeInt() { + return recordAndReturn(target.consumeInt()); + } + + @Override + public int consumeInt(int min, int max) { + return recordAndReturn(target.consumeInt(min, max)); + } + + @Override + public int[] consumeInts(int maxLength) { + return recordAndReturn(target.consumeInts(maxLength)); + } + + @Override + public long consumeLong() { + return recordAndReturn(target.consumeLong()); + } + + @Override + public long consumeLong(long min, long max) { + return recordAndReturn(target.consumeLong(min, max)); + } + + @Override + public long[] consumeLongs(int maxLength) { + return recordAndReturn(target.consumeLongs(maxLength)); + } + + @Override + public float consumeFloat() { + return recordAndReturn(target.consumeFloat()); + } + + @Override + public float consumeRegularFloat() { + return recordAndReturn(target.consumeRegularFloat()); + } + + @Override + public float consumeRegularFloat(float min, float max) { + return recordAndReturn(target.consumeRegularFloat(min, max)); + } + + @Override + public float consumeProbabilityFloat() { + return recordAndReturn(target.consumeProbabilityFloat()); + } + + @Override + public double consumeDouble() { + return recordAndReturn(target.consumeDouble()); + } + + @Override + public double consumeRegularDouble() { + return recordAndReturn(target.consumeRegularDouble()); + } + + @Override + public double consumeRegularDouble(double min, double max) { + return recordAndReturn(target.consumeRegularDouble(min, max)); + } + + @Override + public double consumeProbabilityDouble() { + return recordAndReturn(target.consumeProbabilityDouble()); + } + + @Override + public char consumeChar() { + return recordAndReturn(target.consumeChar()); + } + + @Override + public char consumeChar(char min, char max) { + return recordAndReturn(target.consumeChar(min, max)); + } + + @Override + public char consumeCharNoSurrogates() { + return recordAndReturn(target.consumeCharNoSurrogates()); + } + + @Override + public String consumeString(int maxLength) { + return recordAndReturn(target.consumeString(maxLength)); + } + + @Override + public String consumeRemainingAsString() { + return recordAndReturn(target.consumeRemainingAsString()); + } + + @Override + public String consumeAsciiString(int maxLength) { + return recordAndReturn(target.consumeAsciiString(maxLength)); + } + + @Override + public String consumeRemainingAsAsciiString() { + return recordAndReturn(target.consumeRemainingAsAsciiString()); + } + + @Override + public int remainingBytes() { + return recordAndReturn(target.remainingBytes()); + } } -- cgit v1.2.3 From 54e8966eaece9bf74798cb62f303edead3484ad4 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 31 Dec 2021 11:09:18 +0100 Subject: Add a RecordingFuzzedDataProvider test --- .../code_intelligence/jazzer/runtime/BUILD.bazel | 5 +- .../code_intelligence/jazzer/runtime/BUILD.bazel | 12 ++ .../runtime/RecordingFuzzedDataProviderTest.java | 214 +++++++++++++++++++++ 3 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/runtime/BUILD.bazel create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProviderTest.java 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 095b0bf8..c7df3ccd 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 @@ -5,7 +5,10 @@ java_library( srcs = [ "FuzzedDataProviderImpl.java", ], - visibility = ["//agent/src/main/java/com/code_intelligence/jazzer/replay:__pkg__"], + visibility = [ + "//agent/src/main/java/com/code_intelligence/jazzer/replay:__pkg__", + "//agent/src/test/java/com/code_intelligence/jazzer/runtime:__pkg__", + ], deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/api", ], diff --git a/agent/src/test/java/com/code_intelligence/jazzer/runtime/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/runtime/BUILD.bazel new file mode 100644 index 00000000..360c4518 --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/runtime/BUILD.bazel @@ -0,0 +1,12 @@ +java_test( + name = "RecordingFuzzedDataProviderTest", + srcs = [ + "RecordingFuzzedDataProviderTest.java", + ], + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/api", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:fuzzed_data_provider", + "@maven//:junit_junit", + ], +) diff --git a/agent/src/test/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProviderTest.java b/agent/src/test/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProviderTest.java new file mode 100644 index 00000000..36b3ec61 --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProviderTest.java @@ -0,0 +1,214 @@ +// 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.runtime; + +import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import java.io.IOException; +import java.util.Arrays; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import org.junit.Assert; +import org.junit.Test; + +public class RecordingFuzzedDataProviderTest { + @Test + public void testRecordingFuzzedDataProvider() throws IOException { + FuzzedDataProvider mockData = new MockFuzzedDataProvider(); + String referenceResult = sampleFuzzTarget(mockData); + + FuzzedDataProvider recordingMockData = + RecordingFuzzedDataProvider.makeFuzzedDataProviderProxy(mockData); + Assert.assertEquals(referenceResult, sampleFuzzTarget(recordingMockData)); + + String cannedMockDataString = + RecordingFuzzedDataProvider.serializeFuzzedDataProviderProxy(recordingMockData); + FuzzedDataProvider cannedMockData = new CannedFuzzedDataProvider(cannedMockDataString); + Assert.assertEquals(referenceResult, sampleFuzzTarget(cannedMockData)); + } + + private String sampleFuzzTarget(FuzzedDataProvider data) { + StringBuilder result = new StringBuilder(); + result.append(data.consumeString(10)); + int[] ints = data.consumeInts(5); + result.append(Arrays.stream(ints).mapToObj(Integer::toString).collect(Collectors.joining(","))); + result.append(data.pickValue(ints)); + result.append(data.consumeString(20)); + result.append(data.pickValues(Arrays.stream(ints).boxed().collect(Collectors.toSet()), 5) + .stream() + .map(Integer::toHexString) + .collect(Collectors.joining(","))); + result.append(data.remainingBytes()); + return result.toString(); + } + + private static final class MockFuzzedDataProvider extends FuzzedDataProviderImpl { + @Override + public boolean consumeBoolean() { + return true; + } + + @Override + public boolean[] consumeBooleans(int maxLength) { + return new boolean[] {false, true}; + } + + @Override + public byte consumeByte() { + return 2; + } + + @Override + public byte consumeByte(byte min, byte max) { + return max; + } + + @Override + public short consumeShort() { + return 2; + } + + @Override + public short consumeShort(short min, short max) { + return min; + } + + @Override + public short[] consumeShorts(int maxLength) { + return new short[] {2, 4, 7}; + } + + @Override + public int consumeInt() { + return 5; + } + + @Override + public int consumeInt(int min, int max) { + return max; + } + + @Override + public int[] consumeInts(int maxLength) { + return IntStream.range(0, maxLength).toArray(); + } + + @Override + public long consumeLong() { + return 42; + } + + @Override + public long consumeLong(long min, long max) { + return min; + } + + @Override + public long[] consumeLongs(int maxLength) { + return LongStream.range(0, maxLength).toArray(); + } + + @Override + public float consumeFloat() { + return Float.NaN; + } + + @Override + public float consumeRegularFloat() { + return 0.3f; + } + + @Override + public float consumeRegularFloat(float min, float max) { + return min; + } + + @Override + public float consumeProbabilityFloat() { + return 0.2f; + } + + @Override + public double consumeDouble() { + return Double.NaN; + } + + @Override + public double consumeRegularDouble(double min, double max) { + return max; + } + + @Override + public double consumeRegularDouble() { + return Math.PI; + } + + @Override + public double consumeProbabilityDouble() { + return 0.5; + } + + @Override + public char consumeChar() { + return 'C'; + } + + @Override + public char consumeChar(char min, char max) { + return min; + } + + @Override + public char consumeCharNoSurrogates() { + return 'C'; + } + + @Override + public String consumeAsciiString(int maxLength) { + return "foobar"; + } + + @Override + public String consumeString(int maxLength) { + return "fooۊ"; + } + + @Override + public String consumeRemainingAsAsciiString() { + return "foobar"; + } + + @Override + public String consumeRemainingAsString() { + return "foobar"; + } + + @Override + public byte[] consumeBytes(int maxLength) { + return new byte[maxLength]; + } + + @Override + public byte[] consumeRemainingAsBytes() { + return new byte[] {1}; + } + + @Override + public int remainingBytes() { + return 1; + } + } +} -- cgit v1.2.3 From f6f10f7bbca9b23a2be4385fc7c8f9d6ed190ef8 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 3 Jan 2022 14:52:56 +0100 Subject: Implement traceGenericCmp for Number and CharSequence --- .../jazzer/runtime/TraceDataFlowNativeCallbacks.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java index 456d0cb9..5feca97b 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java @@ -75,13 +75,15 @@ final public class TraceDataFlowNativeCallbacks { // The caller has to ensure that arg1 and arg2 have the same class. public static void traceGenericCmp(Object arg1, Object arg2, int pc) { - if (arg1 instanceof String) { - traceStrcmp((String) arg1, (String) arg2, 1, pc); + if (arg1 instanceof CharSequence) { + traceStrcmp(arg1.toString(), arg2.toString(), 1, pc); } else if (arg1 instanceof Integer || arg1 instanceof Short || arg1 instanceof Byte || arg1 instanceof Character) { traceCmpInt((int) arg1, (int) arg2, pc); } else if (arg1 instanceof Long) { traceCmpLong((long) arg1, (long) arg2, pc); + } else if (arg1 instanceof Number) { + traceCmpLong(((Number) arg1).longValue(), ((Number) arg2).longValue(), pc); } else if (arg1 instanceof byte[]) { traceMemcmp((byte[]) arg1, (byte[]) arg2, 1, pc); } -- cgit v1.2.3 From 3c26eabd62b2e3a60ff68def44febcf9645f33d4 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 3 Jan 2022 14:53:11 +0100 Subject: Hook equals for generic and boxed types --- .../code_intelligence/jazzer/runtime/TraceCmpHooks.java | 16 ++++++++++++++++ .../main/java/com/example/ExampleValueProfileFuzzer.java | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java index 352da8ea..4f9924a7 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java @@ -80,6 +80,22 @@ final public class TraceCmpHooks { } } + @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.Object", targetMethod = "equals") + @MethodHook( + type = HookType.AFTER, targetClassName = "java.lang.CharSequence", targetMethod = "equals") + @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.Number", targetMethod = "equals") + @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.Byte", targetMethod = "equals") + @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.Integer", targetMethod = "equals") + @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.Short", targetMethod = "equals") + @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.Long", targetMethod = "equals") + public static void + genericEquals( + MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) { + if (!returnValue && thisObject.getClass() == arguments[0].getClass()) { + TraceDataFlowNativeCallbacks.traceGenericCmp(thisObject, arguments[0], hookId); + } + } + @MethodHook( type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "compareTo") @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", diff --git a/examples/src/main/java/com/example/ExampleValueProfileFuzzer.java b/examples/src/main/java/com/example/ExampleValueProfileFuzzer.java index acc023a2..1e45f428 100644 --- a/examples/src/main/java/com/example/ExampleValueProfileFuzzer.java +++ b/examples/src/main/java/com/example/ExampleValueProfileFuzzer.java @@ -32,7 +32,7 @@ public class ExampleValueProfileFuzzer { // Without -use_value_profile=1, the fuzzer gets stuck here as there is no direct correspondence // between the input bytes and the compared string. With value profile, the fuzzer can guess the // expected input byte by byte, which takes linear rather than exponential time. - if (base64(data.consumeBytes(6)).equals("SmF6emVy")) { + if (((Object) base64(data.consumeBytes(6))).equals("SmF6emVy")) { long[] plaintextBlocks = data.consumeLongs(2); if (plaintextBlocks.length != 2) return; -- cgit v1.2.3 From d927399a132dd91179d77dbf1424e89648df41c4 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 3 Jan 2022 15:27:11 +0100 Subject: Fix NullPointerException in generic equals hook --- .../main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java index 4f9924a7..8398e1d7 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java @@ -91,7 +91,7 @@ final public class TraceCmpHooks { public static void genericEquals( MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) { - if (!returnValue && thisObject.getClass() == arguments[0].getClass()) { + if (!returnValue && arguments[0] != null && thisObject.getClass() == arguments[0].getClass()) { TraceDataFlowNativeCallbacks.traceGenericCmp(thisObject, arguments[0], hookId); } } -- cgit v1.2.3 From 4434041f088365acf2a561e678bf9d61a7aa5dff Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 3 Jan 2022 16:59:35 +0100 Subject: Fix ClassCastException in traceGenericCmp Boxed primitives can't be directly cast to int even if their primitive equivalent could. --- .../jazzer/runtime/TraceDataFlowNativeCallbacks.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java index 5feca97b..ac10af94 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java @@ -77,11 +77,16 @@ final public class TraceDataFlowNativeCallbacks { public static void traceGenericCmp(Object arg1, Object arg2, int pc) { if (arg1 instanceof CharSequence) { traceStrcmp(arg1.toString(), arg2.toString(), 1, pc); - } else if (arg1 instanceof Integer || arg1 instanceof Short || arg1 instanceof Byte - || arg1 instanceof Character) { + } else if (arg1 instanceof Integer) { traceCmpInt((int) arg1, (int) arg2, pc); } else if (arg1 instanceof Long) { traceCmpLong((long) arg1, (long) arg2, pc); + } else if (arg1 instanceof Short) { + traceCmpInt((short) arg1, (short) arg2, pc); + } else if (arg1 instanceof Byte) { + traceCmpInt((byte) arg1, (byte) arg2, pc); + } else if (arg1 instanceof Character) { + traceCmpInt((char) arg1, (char) arg2, pc); } else if (arg1 instanceof Number) { traceCmpLong(((Number) arg1).longValue(), ((Number) arg2).longValue(), pc); } else if (arg1 instanceof byte[]) { -- cgit v1.2.3 From abca555bf5a06d566326f9befed348263d9041e9 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 4 Jan 2022 09:59:31 +0100 Subject: Mention that LLD is required --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ef2bfd93..ef887ff7 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ If Jazzer produces a finding, the input that triggered it will be available in t Jazzer has the following dependencies when being built from source: * JDK 8 or later (e.g. [OpenJDK](https://openjdk.java.net/)) -* [Clang](https://clang.llvm.org/) 9.0 or later (using a recent version is strongly recommended) +* [Clang](https://clang.llvm.org/) and [LLD](https://lld.llvm.org/) 9.0 or later (using a recent version is strongly recommended) #### Linux -- cgit v1.2.3 From 24b67a9db56417081a0dd0e979497100a96837bd Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 4 Jan 2022 17:28:38 +0100 Subject: Add an onFuzzTargetReady callback to the Java agent This can be used to integrate agent functionality that needs to know the fuzz target class. --- .../com/code_intelligence/jazzer/runtime/JazzerInternal.java | 3 +++ driver/fuzz_target_runner.cpp | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java index 8bc1b38c..d79bdf61 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java @@ -26,4 +26,7 @@ final public class JazzerInternal { // target returns even if this Error is swallowed. throw new HardToCatchError(); } + + // Accessed from native code. + public static void onFuzzTargetReady(String fuzzTargetClass) {} } diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index 934e27e1..4865cf64 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -183,6 +183,17 @@ FuzzTargetRunner::FuzzTargetRunner( exit(1); } + // Inform the agent about the fuzz target class. + auto on_fuzz_target_ready = jvm.GetStaticMethodID( + jazzer_, "onFuzzTargetReady", "(Ljava/lang/String;)V", true); + jstring fuzz_target_class = env.NewStringUTF(FLAGS_target_class.c_str()); + env.CallStaticObjectMethod(jazzer_, on_fuzz_target_ready, fuzz_target_class); + if (env.ExceptionCheck()) { + env.ExceptionDescribe(); + return; + } + env.DeleteLocalRef(fuzz_target_class); + // check existence of optional methods for initialization and destruction fuzzer_initialize_ = jvm.GetStaticMethodID(jclass_, "fuzzerInitialize", "()V", false); -- cgit v1.2.3 From 9df81b72836fa2d55d12d1383f6e61f52add3b4b Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 5 Jan 2022 13:24:34 +0100 Subject: Pin XCode config --- .github/BUILD.bazel | 26 ++++++++++++++++++++++++++ .github/workflows/release.yml | 2 +- .github/workflows/run-all-tests.yml | 2 +- 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 .github/BUILD.bazel diff --git a/.github/BUILD.bazel b/.github/BUILD.bazel new file mode 100644 index 00000000..ee17cccf --- /dev/null +++ b/.github/BUILD.bazel @@ -0,0 +1,26 @@ +# Extracted on 2022-01-05 as described in +# https://www.smileykeith.com/2021/03/08/locking-xcode-in-bazel/ + +package(default_visibility = ["//visibility:public"]) + +xcode_version( + name = "version13_1_0_13A1030d", + aliases = [ + "13.1.0", + "13.1", + "13.1.0.13A1030d", + ], + default_ios_sdk_version = "15.0", + default_macos_sdk_version = "12.0", + default_tvos_sdk_version = "15.0", + default_watchos_sdk_version = "8.0", + version = "13.1.0.13A1030d", +) + +xcode_config( + name = "host_xcodes", + default = ":version13_1_0_13A1030d", + versions = [ + ":version13_1_0_13A1030d", + ], +) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b2d5566d..d4f65438 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: bazel_args: "--config=toolchain --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-linux" - os: macos-10.15 arch: "macos-x86_64" - bazel_args: "--config=toolchain --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-darwin" + bazel_args: "--config=toolchain --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-darwin --xcode_version_config=//.github:host_xcodes" - os: windows-2016 arch: "windows" bazel_args: "" diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-all-tests.yml index 2af6f5ec..7992389c 100644 --- a/.github/workflows/run-all-tests.yml +++ b/.github/workflows/run-all-tests.yml @@ -29,7 +29,7 @@ jobs: - os: macos-11 arch: "macos-x86_64" # Always use the toolchain as UBSan produces linker errors with Apple LLVM 13. - bazel_args: "--config=toolchain --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-darwin" + bazel_args: "--config=toolchain --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-darwin --xcode_version_config=//.github:host_xcodes" cache: "/private/var/tmp/bazel-disk" - os: windows-latest arch: "windows" -- cgit v1.2.3 From e6ee7089bbef39489ad1ffc4580513f7b3cfa69e Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 10 Jan 2022 11:29:48 +0100 Subject: Reformat README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ef887ff7..56729d67 100644 --- a/README.md +++ b/README.md @@ -289,11 +289,11 @@ The format of the signature agrees with that obtained from the part after the `# Under the hood, jazzer tries various ways of creating objects from the fuzzer input. For example, if a parameter is an interface or an abstract class, it will look for all concrete implementing classes on the classpath. -Jazzer can also create objects from classes that follow the [builder design pattern](https://www.baeldung.com/creational-design-patterns#builder) +Jazzer can also create objects from classes that follow the [builder design pattern](https://www.baeldung.com/creational-design-patterns#builder) or have a default constructor and use setters to set the fields. -Creating objects from fuzzer input can lead to many reported exceptions. -Jazzer addresses this issue by ignoring exceptions that the target method declares to throw. +Creating objects from fuzzer input can lead to many reported exceptions. +Jazzer addresses this issue by ignoring exceptions that the target method declares to throw. In addition to that, you can provide a list of exceptions to be ignored during fuzzing via the `--autofuzz_ignore` flag in the form of a comma-separated list. You can specify concrete exceptions (e.g., `java.lang.NullPointerException`), in which case also subclasses of these exception classes will be ignored, or glob patterns to ignore all exceptions in a specific package (e.g. `java.lang.*` or `com.company.**`). @@ -317,7 +317,7 @@ docker run -it cifuzz/jazzer-autofuzz \ --keep_going=1 ``` -#### +#### ### Reproducing a bug @@ -395,7 +395,7 @@ to [its documentation](https://llvm.org/docs/LibFuzzer.html) for a detailed desc ### Passing JVM arguments -Arguments for the JVM started by Jazzer can be supplied via the `--jvm_args` argument. +Arguments for the JVM started by Jazzer can be supplied via the `--jvm_args` argument. Multiple arguments are delimited by the classpath separator, which is `;` on Windows and `:` else. For example, to enable preview features as well as set a maximum heap size, add the following to the Jazzer invocation: -- cgit v1.2.3 From 7b7ea35bba803c0c9f45d6f7147763581c5afa15 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 10 Jan 2022 11:29:58 +0100 Subject: Add CVE-2021-22569 to findings --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 56729d67..a9e52c16 100644 --- a/README.md +++ b/README.md @@ -357,6 +357,7 @@ Jazzer has so far uncovered the following vulnerabilities and bugs: | Project | Bug | Status | CVE | found by | | ------- | -------- | ------ | --- | -------- | +| [protocolbuffers/protobuf](https://github.com/protocolbuffers/protobuf) | Small protobuf messages can consume minutes of CPU time | [fixed](https://github.com/protocolbuffers/protobuf/security/advisories/GHSA-wrvw-hg22-4m67) | [CVE-2021-22569](https://nvd.nist.gov/vuln/detail/CVE-2021-22569) | [OSS-Fuzz](https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=39330) | | [jhy/jsoup](https://github.com/jhy/jsoup) | More than 19 Bugs found in HTML and XML parser | [fixed](https://github.com/jhy/jsoup/security/advisories/GHSA-m72m-mhq2-9p6c) | [CVE-2021-37714](https://nvd.nist.gov/vuln/detail/CVE-2021-37714) | [Code Intelligence](https://code-intelligence.com) | | [Apache/commons-compress](https://commons.apache.org/proper/commons-compress/) | Infinite loop when loading a crafted 7z | fixed | [CVE-2021-35515](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-35515) | [Code Intelligence](https://code-intelligence.com) | | [Apache/commons-compress](https://commons.apache.org/proper/commons-compress/) | `OutOfMemoryError` when loading a crafted 7z | fixed | [CVE-2021-35516](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-35516) | [Code Intelligence](https://code-intelligence.com) | -- cgit v1.2.3 From b94c374c304f24be5ccff68d6ba61bd920b63846 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 10 Jan 2022 07:20:46 +0100 Subject: Honor JAVA_OPTS The only mechanism to specify JVM arguments currently supported by Jazzer is the --jvm_args argument, which has a syntax that differs between OSes and may require shell quoting. This commit makes Jazzer honor the JAVA_OPTS environment variable, which is used by many popular Java tools to specify arguments to the JVMs they invoke. --- README.md | 7 ++++++- bazel/fuzz_target.bzl | 2 ++ driver/jvm_tooling.cpp | 14 ++++++++++++++ examples/BUILD.bazel | 3 +++ .../src/main/java/com/example/JpegImageParserFuzzer.java | 8 ++++++++ 5 files changed, 33 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a9e52c16..48b5d5b0 100644 --- a/README.md +++ b/README.md @@ -396,7 +396,10 @@ to [its documentation](https://llvm.org/docs/LibFuzzer.html) for a detailed desc ### Passing JVM arguments -Arguments for the JVM started by Jazzer can be supplied via the `--jvm_args` argument. +When Jazzer is launched, it starts a JVM in which it executes the fuzz target. +Arguments for this JVM can be provided via the `JAVA_OPTS` environment variable. + +Alternatively, arguments can also be supplied via the `--jvm_args` argument. Multiple arguments are delimited by the classpath separator, which is `;` on Windows and `:` else. For example, to enable preview features as well as set a maximum heap size, add the following to the Jazzer invocation: @@ -407,6 +410,8 @@ For example, to enable preview features as well as set a maximum heap size, add --jvm_args=--enable-preview:-Xmx1000m ``` +Arguments specified with `--jvm_args` take precendence over those in `JAVA_OPTS`. + ### Coverage Instrumentation The Jazzer agent inserts coverage markers into the JVM bytecode during class loading. libFuzzer uses this information diff --git a/bazel/fuzz_target.bzl b/bazel/fuzz_target.bzl index bd90e500..c3873b95 100644 --- a/bazel/fuzz_target.bzl +++ b/bazel/fuzz_target.bzl @@ -25,6 +25,7 @@ def java_fuzz_target_test( srcs = [], size = None, timeout = None, + env = None, **kwargs): target_name = name + "_target" deploy_manifest_lines = [] @@ -72,6 +73,7 @@ def java_fuzz_target_test( "//agent:jazzer_agent_deploy.jar", driver, ] + native_libs, + env = env, main_class = "FuzzTargetTestWrapper", use_testrunner = False, tags = tags, diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index 178eec02..d352f0b8 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -14,6 +14,7 @@ #include "jvm_tooling.h" +#include #include #include #include @@ -274,6 +275,19 @@ JVM::JVM(const std::string &executable_path) { // Optimize GC for high throughput rather than low latency. options.push_back(JavaVMOption{.optionString = (char *)"-XX:+UseParallelGC"}); + // Add additional JVM options set through JAVA_OPTS. + std::vector java_opts_args; + const char *java_opts = std::getenv("JAVA_OPTS"); + if (java_opts != nullptr) { + // Mimic the behavior of the JVM when it sees JAVA_TOOL_OPTIONS. + std::cerr << "Picked up JAVA_OPTS: " << java_opts << std::endl; + java_opts_args = absl::StrSplit(java_opts, ' '); + for (const std::string &java_opt : java_opts_args) { + options.push_back( + JavaVMOption{.optionString = const_cast(java_opt.c_str())}); + } + } + // add additional jvm options set through command line flags std::vector jvm_args; if (!FLAGS_jvm_args.empty()) { diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index dde8aaeb..749f53b9 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -129,6 +129,9 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/JpegImageParserFuzzer.java", ], + env = { + "JAVA_OPTS": "-Dfoo=not_foo -Djava_opts=1", + }, fuzzer_args = [ "-fork=5", "--additional_jvm_args=-Dbaz=baz", diff --git a/examples/src/main/java/com/example/JpegImageParserFuzzer.java b/examples/src/main/java/com/example/JpegImageParserFuzzer.java index a6898bf0..d6ef64f8 100644 --- a/examples/src/main/java/com/example/JpegImageParserFuzzer.java +++ b/examples/src/main/java/com/example/JpegImageParserFuzzer.java @@ -34,6 +34,14 @@ public class JpegImageParserFuzzer { System.err.printf("foo: %s%nbar: %s%nbaz: %s%n", foo, bar, baz); System.exit(3); } + // Only used to verify that Jazzer honors the JAVA_OPTS env var. + String javaOpts = System.getProperty("java_opts"); + if (javaOpts == null || !javaOpts.equals("1")) { + // Exit the process with an exit code different from that for a finding. + System.err.println("ERROR: Did not honor JAVA_OPTS."); + System.err.printf("java_opts: %s%n", javaOpts); + System.exit(4); + } } public static void fuzzerTestOneInput(byte[] input) { -- cgit v1.2.3 From 0a80fb8af1726d1e30b12cec7500af82941f10d9 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Wed, 12 Jan 2022 08:48:37 +0100 Subject: Split crash reproducer data If the recorded FuzzedDataProvider invocation data gets too long it can not be assigned to one String variable in the crash reproducer template anymore. More concretely one constant pool CONSTANT_Utf8_info entry can only hold data of a size up to uint16. This change splits up the recorded data into multiple chunks to always generate valid crash reproducers. Furthermore, a tests directory is introduced to hold an explicit Jazzer integration test to verify the issue. The integration test compiles and executes the generated crash reproducer to verify it's functionality. --- bazel/FuzzTargetTestWrapper.java | 98 ++++++++++++++++++++- bazel/fuzz_target.bzl | 18 +++- driver/fuzz_target_runner.cpp | 17 +++- driver/java_reproducer_templates.h | 2 +- examples/BUILD.bazel | 8 +- format.sh | 4 +- tests/BUILD.bazel | 15 ++++ .../test/java/com/example/LongStringFuzzer.java | 32 +++++++ .../test/java/com/example/LongStringFuzzerInput | Bin 0 -> 100000 bytes 9 files changed, 180 insertions(+), 14 deletions(-) create mode 100644 tests/BUILD.bazel create mode 100644 tests/src/test/java/com/example/LongStringFuzzer.java create mode 100644 tests/src/test/java/com/example/LongStringFuzzerInput diff --git a/bazel/FuzzTargetTestWrapper.java b/bazel/FuzzTargetTestWrapper.java index 59b15844..5b99c50a 100644 --- a/bazel/FuzzTargetTestWrapper.java +++ b/bazel/FuzzTargetTestWrapper.java @@ -15,21 +15,41 @@ import com.google.devtools.build.runfiles.Runfiles; import java.io.File; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.tools.JavaCompiler; +import javax.tools.JavaCompiler.CompilationTask; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; public class FuzzTargetTestWrapper { public static void main(String[] args) { + Runfiles runfiles; String driverActualPath; + String apiActualPath; String jarActualPath; - Runfiles runfiles; + boolean verifyCrashInput; + boolean verifyCrashReproducer; + boolean executeCrashReproducer; try { runfiles = Runfiles.create(); driverActualPath = runfiles.rlocation(rlocationPath(args[0])); - jarActualPath = runfiles.rlocation(rlocationPath(args[1])); + apiActualPath = runfiles.rlocation(rlocationPath(args[1])); + jarActualPath = runfiles.rlocation(rlocationPath(args[2])); + verifyCrashInput = Boolean.parseBoolean(args[3]); + verifyCrashReproducer = Boolean.parseBoolean(args[4]); + executeCrashReproducer = Boolean.parseBoolean(args[5]); } catch (IOException | ArrayIndexOutOfBoundsException e) { e.printStackTrace(); System.exit(1); @@ -44,12 +64,17 @@ public class FuzzTargetTestWrapper { // Crashes will be available as test outputs. These are cleared on the next run, // so this is only useful for examples. String outputDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR"); + + // Map all files/dirs to real location + Stream arguments = Arrays.stream(args).skip(6).map( + arg -> arg.startsWith("-") ? arg : runfiles.rlocation(rlocationPath(arg))); + List command = Stream .concat(Stream.of(driverActualPath, String.format("-artifact_prefix=%s/", outputDir), String.format("--reproducer_path=%s", outputDir), "-seed=2735196724", String.format("--cp=%s", jarActualPath)), - Arrays.stream(args).skip(2)) + arguments) .collect(Collectors.toList()); processBuilder.inheritIO(); processBuilder.command(command); @@ -66,13 +91,30 @@ public class FuzzTargetTestWrapper { System.exit(4); } // Verify that libFuzzer dumped a crashing input. - if (Arrays.stream(outputFiles).noneMatch(name -> name.startsWith("crash-"))) { + if (verifyCrashInput + && Arrays.stream(outputFiles).noneMatch(name -> name.startsWith("crash-"))) { + System.out.printf("No crashing input found in %s%n", outputDir); System.exit(5); } + // Verify that libFuzzer dumped a crash reproducer. + if (verifyCrashReproducer + && Arrays.stream(outputFiles).noneMatch(name -> name.startsWith("Crash_"))) { + System.out.printf("No crash reproducer found in %s%n", outputDir); + System.exit(6); + } } catch (IOException | InterruptedException e) { e.printStackTrace(); System.exit(2); } + + if (executeCrashReproducer) { + try { + verifyCrashReproducer(outputDir, driverActualPath, apiActualPath, jarActualPath); + } catch (Exception e) { + e.printStackTrace(); + System.exit(6); + } + } System.exit(0); } @@ -84,4 +126,52 @@ public class FuzzTargetTestWrapper { return "jazzer/" + rootpath; } } + + private static void verifyCrashReproducer(String outputDir, String driver, String api, String jar) + throws Exception { + File source = + Files.list(Paths.get(outputDir)) + .filter(f -> f.toFile().getName().endsWith(".java")) + .findFirst() + .map(Path::toFile) + .orElseThrow( + () -> new IllegalStateException("Could not find crash reproducer in " + outputDir)); + String crashReproducer = compile(source, driver, api, jar); + execute(crashReproducer, outputDir); + } + + private static String compile(File source, String driver, String api, String jar) + throws IOException { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)) { + Iterable compilationUnits = fileManager.getJavaFileObjects(source); + List options = + Arrays.asList("-classpath", String.join(File.pathSeparator, driver, api, jar)); + System.out.printf( + "Compile crash reproducer %s with options %s%n", source.getAbsolutePath(), options); + CompilationTask task = + compiler.getTask(null, fileManager, null, options, null, compilationUnits); + if (!task.call()) { + throw new IllegalStateException("Could not compile crash reproducer " + source); + } + return source.getName().substring(0, source.getName().indexOf(".")); + } + } + + private static void execute(String classFile, String outputDir) + throws IOException, ReflectiveOperationException { + try { + System.out.printf("Execute crash reproducer %s%n", classFile); + URLClassLoader classLoader = + new URLClassLoader(new URL[] {new URL("file://" + outputDir + "/")}); + Class crashReproducerClass = classLoader.loadClass(classFile); + Method main = crashReproducerClass.getMethod("main", String[].class); + main.invoke(null, new Object[] {new String[] {}}); + throw new IllegalStateException("Crash not reproduced by " + classFile); + } catch (InvocationTargetException e) { + // expect the invocation to fail with the crash + // other reflection exceptions indicate a real problem + System.out.printf("Reproduced exception \"%s\"%n", e.getCause().getMessage()); + } + } } diff --git a/bazel/fuzz_target.bzl b/bazel/fuzz_target.bzl index c3873b95..32de3035 100644 --- a/bazel/fuzz_target.bzl +++ b/bazel/fuzz_target.bzl @@ -17,7 +17,7 @@ def java_fuzz_target_test( target_class = None, deps = [], hook_classes = [], - native_libs = [], + data = [], sanitizer = None, visibility = None, tags = [], @@ -26,6 +26,9 @@ def java_fuzz_target_test( size = None, timeout = None, env = None, + verify_crash_input = True, + verify_crash_reproducer = True, + execute_crash_reproducer = False, **kwargs): target_name = name + "_target" deploy_manifest_lines = [] @@ -61,18 +64,27 @@ def java_fuzz_target_test( native.java_test( name = name, - runtime_deps = ["//bazel:fuzz_target_test_wrapper"], + runtime_deps = [ + "//bazel:fuzz_target_test_wrapper", + "//agent:jazzer_api_deploy.jar", + ":%s_deploy.jar" % target_name, + ], size = size or "enormous", timeout = timeout or "moderate", args = [ "$(rootpath %s)" % driver, + "$(rootpath //agent:jazzer_api_deploy.jar)", "$(rootpath :%s_deploy.jar)" % target_name, + str(verify_crash_input), + str(verify_crash_reproducer), + str(execute_crash_reproducer), ] + additional_args + fuzzer_args, data = [ ":%s_deploy.jar" % target_name, "//agent:jazzer_agent_deploy.jar", + "//agent:jazzer_api_deploy.jar", driver, - ] + native_libs, + ] + data, env = env, main_class = "FuzzTargetTestWrapper", use_testrunner = False, diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index 4865cf64..cf5e8e10 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -80,6 +81,11 @@ constexpr auto kJazzerClass = constexpr auto kAutofuzzFuzzTargetClass = "com/code_intelligence/jazzer/autofuzz/FuzzTarget"; +// A constant pool CONSTANT_Utf8_info entry should be able to hold data of size +// uint16, but somehow this does not seem to be the case and leads to invalid +// code crash reproducer code. Reducing the size by one resolves the problem. +constexpr auto dataChunkMaxLength = std::numeric_limits::max() - 1; + namespace jazzer { // split a string on unescaped spaces std::vector splitOnSpace(const std::string &s) { @@ -371,8 +377,17 @@ void FuzzTargetRunner::DumpReproducer(const uint8_t *data, std::size_t size) { ? kTestOneInputWithData : kTestOneInputWithBytes; std::string data_sha1 = jazzer::Sha1Hash(data, size); + + // The serialization of recorded FuzzedDataProvider invocations can get to + // long to be stored in one String variable in the template. This is + // mitigated by chunking the data and concatenating it again in the generated + // code. + absl::ByLength chunk_delimiter = absl::ByLength(dataChunkMaxLength); + std::string chunked_base64_data = + absl::StrJoin(absl::StrSplit(base64_data, chunk_delimiter), "\", \""); + std::string reproducer = - absl::Substitute(kBaseReproducer, data_sha1, base64_data, + absl::Substitute(kBaseReproducer, data_sha1, chunked_base64_data, FLAGS_target_class, fuzz_target_call); std::string reproducer_filename = absl::StrFormat("Crash_%s.java", data_sha1); std::string reproducer_full_path = absl::StrFormat( diff --git a/driver/java_reproducer_templates.h b/driver/java_reproducer_templates.h index 7d58e1fa..a6d8a4d7 100644 --- a/driver/java_reproducer_templates.h +++ b/driver/java_reproducer_templates.h @@ -23,7 +23,7 @@ constexpr const char *kBaseReproducer = import java.lang.reflect.Method; public class Crash_$0 { - static final String base64Bytes = "$1"; + static final String base64Bytes = String.join("", "$1"); public static void main(String[] args) { ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true); diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index 749f53b9..b6cea62e 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -46,6 +46,7 @@ java_fuzz_target_test( fuzzer_args = ["--jvm_args=-Djazzer.native_lib=native_asan"], sanitizer = "address", target_class = "com.example.ExampleFuzzerWithNative", + verify_crash_reproducer = False, runtime_deps = [ ":example_fuzzer_with_native_lib", ], @@ -58,6 +59,7 @@ java_fuzz_target_test( target_class = "com.example.ExampleFuzzerWithNative", # Crashes at runtime without an error message. target_compatible_with = SKIP_ON_WINDOWS, + verify_crash_reproducer = False, runtime_deps = [ ":example_fuzzer_with_native_lib", ], @@ -295,12 +297,12 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/TurboJpegFuzzer.java", ], + data = [ + "@libjpeg_turbo//:turbojpeg_native", + ], fuzzer_args = [ "-rss_limit_mb=8196", ], - native_libs = [ - "@libjpeg_turbo//:turbojpeg_native", - ], sanitizer = "address", tags = ["manual"], target_class = "com.example.TurboJpegFuzzer", diff --git a/format.sh b/format.sh index f783f6e9..da310c63 100755 --- a/format.sh +++ b/format.sh @@ -3,7 +3,7 @@ find -name '*.cpp' -o -name '*.h' -o -name '*.java' | xargs clang-format-13 -i # Kotlin # curl -sSLO https://github.com/pinterest/ktlint/releases/download/0.42.1/ktlint && chmod a+x ktlint -ktlint -F "agent/**/*.kt" "driver/**/*.kt" "examples/**/*.kt" "sanitizers/**/*.kt" +ktlint -F "agent/**/*.kt" "driver/**/*.kt" "examples/**/*.kt" "sanitizers/**/*.kt" "tests/**/*.kt" # BUILD files # go get github.com/bazelbuild/buildtools/buildifier @@ -11,4 +11,4 @@ buildifier -r . # Licence headers # go get -u github.com/google/addlicense -addlicense -c "Code Intelligence GmbH" agent/ bazel/ deploy/ docker/ driver/ examples/ sanitizers/ *.bzl +addlicense -c "Code Intelligence GmbH" agent/ bazel/ deploy/ docker/ driver/ examples/ sanitizers/ tests/ *.bzl diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel new file mode 100644 index 00000000..4be0c40f --- /dev/null +++ b/tests/BUILD.bazel @@ -0,0 +1,15 @@ +load("//bazel:fuzz_target.bzl", "java_fuzz_target_test") + +java_fuzz_target_test( + name = "LongStringFuzzer", + srcs = [ + "src/test/java/com/example/LongStringFuzzer.java", + ], + data = ["src/test/java/com/example/LongStringFuzzerInput"], + execute_crash_reproducer = True, + fuzzer_args = [ + "$(rootpath src/test/java/com/example/LongStringFuzzerInput)", + ], + target_class = "com.example.LongStringFuzzer", + verify_crash_input = False, +) diff --git a/tests/src/test/java/com/example/LongStringFuzzer.java b/tests/src/test/java/com/example/LongStringFuzzer.java new file mode 100644 index 00000000..5fecf91f --- /dev/null +++ b/tests/src/test/java/com/example/LongStringFuzzer.java @@ -0,0 +1,32 @@ +// 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.example; + +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow; + +/** + * Provoke a finding with huge captured data to verify that the generated + * crash reproducer is still compilable. This test uses a huge, predefined + * corpus to speed up finding the issue. + *

+ * Reproduces issue #269 (https://github.com/CodeIntelligenceTesting/jazzer/issues/269) + */ +public class LongStringFuzzer { + public static void fuzzerTestOneInput(byte[] data) { + if (data.length > 1024 * 64) { + throw new FuzzerSecurityIssueLow("String too long exception"); + } + } +} diff --git a/tests/src/test/java/com/example/LongStringFuzzerInput b/tests/src/test/java/com/example/LongStringFuzzerInput new file mode 100644 index 00000000..f18c9a67 Binary files /dev/null and b/tests/src/test/java/com/example/LongStringFuzzerInput differ -- cgit v1.2.3 From 7708c31728d78ad039ce90975f4b7c9f8b9fd803 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 12 Jan 2022 11:26:31 +0100 Subject: Update rules_jni to 0.4.0 The new version includes additional fallback logic to detect the default JVM on macOS. --- repositories.bzl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/repositories.bzl b/repositories.bzl index 36b34443..90ff51fc 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -91,8 +91,9 @@ def jazzer_dependencies(): maybe( http_archive, name = "fmeum_rules_jni", - sha256 = "8d685e381cb625e11fac330085de2ebc13ad497d30c4e9b09beb212f7c27e8e7", - url = "https://github.com/fmeum/rules_jni/releases/download/v0.3.0/rules_jni-v0.3.0.tar.gz", + sha256 = "9a387a066f683a8aac4d165917dc7fe15ec2a20931894a97e153a9caab6123ca", + strip_prefix = "rules_jni-0.4.0", + url = "https://github.com/fmeum/rules_jni/archive/refs/tags/v0.4.0.tar.gz", ) maybe( -- cgit v1.2.3 From e6a08f5626f52f55250ee9350410ece8b7c0a49b Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 5 Nov 2021 12:55:59 +0100 Subject: Make generic type available in consume This is needed to generate useful Map instances. --- .../code_intelligence/jazzer/autofuzz/Meta.java | 28 ++++++++++++++++++++-- .../jazzer/autofuzz/TestHelpers.java | 4 ++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java index 96980530..e6a62f72 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java @@ -36,9 +36,14 @@ import java.io.InputStream; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; +import java.lang.reflect.GenericArrayType; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -210,7 +215,8 @@ public class Meta { return consume(data, type, null); } - static Object consume(FuzzedDataProvider data, Class type, AutofuzzCodegenVisitor visitor) { + static Object consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor visitor) { + Class type = getRawType(genericType); if (type == byte.class || type == Byte.class) { byte result = data.consumeByte(); if (visitor != null) @@ -578,7 +584,7 @@ public class Meta { FuzzedDataProvider data, Executable executable, AutofuzzCodegenVisitor visitor) { Object[] result; try { - result = Arrays.stream(executable.getParameterTypes()) + result = Arrays.stream(executable.getGenericParameterTypes()) .map((type) -> consume(data, type, visitor)) .toArray(); return result; @@ -616,4 +622,22 @@ public class Meta { } return result; } + + private static Class getRawType(Type genericType) { + if (genericType instanceof Class) { + return (Class) genericType; + } else if (genericType instanceof ParameterizedType) { + return getRawType(((ParameterizedType) genericType).getRawType()); + } else if (genericType instanceof WildcardType) { + // TODO: Improve this. + return Object.class; + } else if (genericType instanceof TypeVariable) { + throw new AutofuzzError("Did not expect genericType to be a TypeVariable: " + genericType); + } else if (genericType instanceof GenericArrayType) { + // TODO: Improve this; + return Object[].class; + } else { + throw new AutofuzzError("Got unexpected class implementing Type: " + genericType); + } + } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java index 52f19a74..d556beb3 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java @@ -24,6 +24,7 @@ import java.io.ByteArrayInputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Method; +import java.lang.reflect.Type; import java.util.List; class TestHelpers { @@ -57,8 +58,7 @@ class TestHelpers { } static void consumeTestCase( - Class type, Object expectedResult, String expectedResultString, List cannedData) { - assertTrue(expectedResult == null || type.isAssignableFrom(expectedResult.getClass())); + Type type, Object expectedResult, String expectedResultString, List cannedData) { AutofuzzCodegenVisitor visitor = new AutofuzzCodegenVisitor(); FuzzedDataProvider data = CannedFuzzedDataProvider.create(cannedData); assertGeneralEquals(expectedResult, Meta.consume(data, type, visitor)); -- cgit v1.2.3 From 66de1754ad583594ae669714d77d78a54eee71c3 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 5 Nov 2021 14:32:45 +0100 Subject: Emit typed nulls in Autofuzz codegen --- .../java/com/code_intelligence/jazzer/autofuzz/Meta.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java index e6a62f72..5a8dda39 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java @@ -215,6 +215,11 @@ public class Meta { return consume(data, type, null); } + // Invariant: The Java source code representation of the returned object visited by visitor must + // represent an object of the same type as genericType. For example, a null value returned for + // the genericType Class should lead to the generated code + // "(java.lang.String) null", not just "null". This makes it possible to safely use consume in + // recursive argument constructions. static Object consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor visitor) { Class type = getRawType(genericType); if (type == byte.class || type == Byte.class) { @@ -263,8 +268,13 @@ public class Meta { // fact that TypeUtils can't distinguish between a primitive type and its wrapper and may // thus easily cause false-positive NullPointerExceptions. if (!type.isPrimitive() && data.consumeByte((byte) 0, (byte) 19) == 0) { - if (visitor != null) - visitor.pushElement("null"); + if (visitor != null) { + if (type == Object.class) { + visitor.pushElement("null"); + } else { + visitor.pushElement(String.format("(%s) null", type.getCanonicalName())); + } + } return null; } if (type == String.class || type == CharSequence.class) { -- cgit v1.2.3 From 0e341eb581fd66231a09a169962ba001e8000803 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 5 Nov 2021 14:36:07 +0100 Subject: Add support for Map generation to Autofuzz --- .../code_intelligence/jazzer/autofuzz/Meta.java | 49 ++++++++++++++++++ .../jazzer/autofuzz/MetaTest.java | 58 ++++++++++++++++++---- 2 files changed, 98 insertions(+), 9 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java index 5a8dda39..12669425 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java @@ -360,6 +360,55 @@ public class Meta { ", ", "new java.io.ByteArrayInputStream(new byte[]{", "})"))); } return new ByteArrayInputStream(array); + } else if (type == Map.class) { + if (visitor != null) { + // Do not use Collectors.toMap() since it cannot handle null values. + visitor.pushGroup("java.util.stream.Stream.of(", ", ", + ").collect(java.util.HashMap::new, (map, e) -> map.put(e.getKey(), e.getValue()), java.util.HashMap::putAll)"); + } + ParameterizedType mapType = (ParameterizedType) genericType; + if (mapType.getActualTypeArguments().length != 2) { + throw new AutofuzzError( + "Expected Map generic type to have two type parameters: " + mapType); + } + Type keyType = mapType.getActualTypeArguments()[0]; + Type valueType = mapType.getActualTypeArguments()[1]; + int remainingBytesBeforeFirstEntryCreation = data.remainingBytes(); + if (visitor != null) { + visitor.pushGroup("new java.util.AbstractMap.SimpleEntry<>(", ", ", ")"); + } + Object firstKey = consume(data, keyType, visitor); + Object firstValue = consume(data, valueType, visitor); + if (visitor != null) { + visitor.popGroup(); + } + int remainingBytesAfterFirstEntryCreation = data.remainingBytes(); + int sizeOfElementEstimate = + remainingBytesBeforeFirstEntryCreation - remainingBytesAfterFirstEntryCreation; + int mapSize = consumeArrayLength(data, sizeOfElementEstimate); + Map map = new HashMap<>(mapSize); + for (int i = 0; i < mapSize; i++) { + if (i == 0) { + map.put(firstKey, firstValue); + } else { + if (visitor != null) { + visitor.pushGroup("new java.util.AbstractMap.SimpleEntry<>(", ", ", ")"); + } + map.put(consume(data, keyType, visitor), consume(data, valueType, visitor)); + if (visitor != null) { + visitor.popGroup(); + } + } + } + if (visitor != null) { + if (mapSize == 0) { + // We implicitly pushed the first entry with the call to consume above, but it is not + // part of the array. + visitor.popElement(); + } + visitor.popGroup(); + } + return map; } else if (type.isEnum()) { Enum enumValue = (Enum) data.pickValue(type.getEnumConstants()); if (visitor != null) { diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java index 0615e9ae..55721c43 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java @@ -22,19 +22,13 @@ import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.google.json.JsonSanitizer; import java.io.ByteArrayInputStream; +import java.lang.reflect.Type; import java.util.Arrays; import java.util.Collections; +import java.util.Map; import org.junit.Test; public class MetaTest { - public static boolean isFive(int arg) { - return arg == 5; - } - - public static boolean intEquals(int arg1, int arg2) { - return arg1 == arg2; - } - public enum TestEnum { FOO, BAR, @@ -42,7 +36,7 @@ public class MetaTest { } @Test - public void testConsume() { + public void testConsume() throws NoSuchMethodException { consumeTestCase(5, "5", Collections.singletonList(5)); consumeTestCase((short) 5, "(short) 5", Collections.singletonList((short) 5)); consumeTestCase(5L, "5L", Collections.singletonList(5L)); @@ -121,6 +115,52 @@ public class MetaTest { consumeTestCase(YourAverageJavaClass.class, "com.code_intelligence.jazzer.autofuzz.YourAverageJavaClass.class", Collections.singletonList((byte) 1)); + + Type stringStringMapType = + MetaTest.class.getDeclaredMethod("returnsStringStringMap").getGenericReturnType(); + Map expectedMap = + java.util.stream.Stream + .of(new java.util.AbstractMap.SimpleEntry<>("key0", "value0"), + new java.util.AbstractMap.SimpleEntry<>("key1", "value1"), + new java.util.AbstractMap.SimpleEntry<>("key2", (java.lang.String) null)) + .collect(java.util.HashMap::new, + (map, e) -> map.put(e.getKey(), e.getValue()), java.util.HashMap::putAll); + consumeTestCase(stringStringMapType, expectedMap, + "java.util.stream.Stream.of(new java.util.AbstractMap.SimpleEntry<>(\"key0\", \"value0\"), new java.util.AbstractMap.SimpleEntry<>(\"key1\", \"value1\"), new java.util.AbstractMap.SimpleEntry<>(\"key2\", (java.lang.String) null)).collect(java.util.HashMap::new, (map, e) -> map.put(e.getKey(), e.getValue()), java.util.HashMap::putAll)", + Arrays.asList((byte) 1, // do not return null for the map + 32, // remaining bytes + (byte) 1, // do not return null for the string + 31, // remaining bytes + "key0", + (byte) 1, // do not return null for the string + 28, // remaining bytes + "value0", + 28, // remaining bytes + 28, // consumeArrayLength + (byte) 1, // do not return null for the string + 27, // remaining bytes + "key1", + (byte) 1, // do not return null for the string + 23, // remaining bytes + "value1", + (byte) 1, // do not return null for the string + 27, // remaining bytes + "key2", + (byte) 0 // *do* return null for the string + )); + } + + private Map returnsStringStringMap() { + throw new IllegalStateException( + "Should not be called, only exists to construct its generic return type"); + } + + public static boolean isFive(int arg) { + return arg == 5; + } + + public static boolean intEquals(int arg1, int arg2) { + return arg1 == arg2; } @Test -- cgit v1.2.3 From 60c695e49ac6e65a36b59a712947ebf018dd1d23 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 13 Jan 2022 15:46:22 +0100 Subject: Use non-internal class names for target_class in tests Some of our older tests currently set the --target_class to an internal class name, which we don't officially support. This is causing trouble for the Soot integration on the devassist branch, but is worth fixing on the main branch as well. --- driver/fuzzed_data_provider_test.cpp | 2 +- driver/jvm_tooling_test.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/driver/fuzzed_data_provider_test.cpp b/driver/fuzzed_data_provider_test.cpp index 210bf118..581dfa1d 100644 --- a/driver/fuzzed_data_provider_test.cpp +++ b/driver/fuzzed_data_provider_test.cpp @@ -242,7 +242,7 @@ const uint8_t kInput[] = { }; TEST_F(FuzzedDataProviderTest, FuzzTargetWithDataProvider) { - FLAGS_target_class = "test/FuzzTargetWithDataProvider"; + FLAGS_target_class = "test.FuzzTargetWithDataProvider"; FLAGS_target_args = ""; FuzzTargetRunner fuzz_target_runner(*jvm_); diff --git a/driver/jvm_tooling_test.cpp b/driver/jvm_tooling_test.cpp index f2e8c66a..f64d2f3a 100644 --- a/driver/jvm_tooling_test.cpp +++ b/driver/jvm_tooling_test.cpp @@ -106,7 +106,7 @@ TEST_F(JvmToolingTest, JniProperties) { TEST_F(JvmToolingTest, SimpleFuzzTarget) { // see testdata/test/SimpleFuzzTarget.java for the implementation of the fuzz // target - FLAGS_target_class = "test/SimpleFuzzTarget"; + FLAGS_target_class = "test.SimpleFuzzTarget"; FLAGS_target_args = ""; FuzzTargetRunner fuzz_target_runner(*jvm_); @@ -150,7 +150,7 @@ TEST_F(JvmToolingTest, FuzzTargetWithInit) { // see testdata/test/FuzzTargetWithInit.java for the implementation of the // fuzz target. All string arguments provided in fuzzerInitialize(String[]) // will cause a crash if input in fuzzerTestOneInput(byte[]). - FLAGS_target_class = "test/FuzzTargetWithInit"; + FLAGS_target_class = "test.FuzzTargetWithInit"; FLAGS_target_args = "crash_now crash_harder"; FuzzTargetRunner fuzz_target_runner(*jvm_); @@ -180,7 +180,7 @@ TEST_F(JvmToolingTest, TestCoverageMap) { auto coverage_counters_array = CoverageTracker::GetCoverageCounters(); ASSERT_EQ(0, coverage_counters_array[0]); - FLAGS_target_class = "test/FuzzTargetWithCoverage"; + FLAGS_target_class = "test.FuzzTargetWithCoverage"; FLAGS_target_args = ""; FuzzTargetRunner fuzz_target_runner(*jvm_); // run a fuzz target input which will cause the first coverage counter to -- cgit v1.2.3 From 997c203566350fa313dbd3d7119725387b190e3e Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 20 Jan 2022 15:54:34 +0100 Subject: Rely on JAVA_HOME to select the JDK Bazel defaults to using the local JDK to run tests and respects JAVA_HOME, which we already set in the CI. Also adds CI jobs that print the particular JDK used for verification. --- .github/workflows/release.yml | 6 +++++- .github/workflows/run-all-tests.yml | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d4f65438..e0d9b593 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,10 +31,14 @@ jobs: - name: Build run: | - bazelisk build --config=ci --remote_header=x-buildbuddy-api-key=${{ secrets.BUILDBUDDY_API_KEY }} --java_runtime_version=localjdk_${{ matrix.jdk }} ${{ matrix.bazel_args }} //agent/src/main/java/com/code_intelligence/jazzer/replay:Replayer_deploy.jar //:jazzer_release + bazelisk build --config=ci --remote_header=x-buildbuddy-api-key=${{ secrets.BUILDBUDDY_API_KEY }} ${{ matrix.bazel_args }} //agent/src/main/java/com/code_intelligence/jazzer/replay:Replayer_deploy.jar //:jazzer_release cp -L bazel-bin/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer_deploy.jar replayer.jar cp -L bazel-bin/jazzer_release.tar.gz release-${{ matrix.arch }}.tar.gz + - name: Print JDK used + if: matrix.os != 'windows-2016' + run: cat $(bazel info execution_root)/external/local_jdk/BUILD.bazel | grep java_home + - name: Upload replayer uses: actions/upload-artifact@v2 with: diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-all-tests.yml index 7992389c..15124ce2 100644 --- a/.github/workflows/run-all-tests.yml +++ b/.github/workflows/run-all-tests.yml @@ -50,10 +50,14 @@ jobs: key: bazel-disk-cache-${{ matrix.arch }}-${{ matrix.jdk }} - 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 }} ${{ matrix.bazel_args }} //... + run: bazelisk build --config=ci --remote_header=x-buildbuddy-api-key=${{ secrets.BUILDBUDDY_API_KEY }} --disk_cache=${{ matrix.cache }} ${{ matrix.bazel_args }} //... - name: Test - run: bazelisk test --config=ci --remote_header=x-buildbuddy-api-key=${{ secrets.BUILDBUDDY_API_KEY }} --disk_cache=${{ matrix.cache }} --java_runtime_version=localjdk_${{ matrix.jdk }} ${{ matrix.bazel_args }} //... + run: bazelisk test --config=ci --remote_header=x-buildbuddy-api-key=${{ secrets.BUILDBUDDY_API_KEY }} --disk_cache=${{ matrix.cache }} ${{ matrix.bazel_args }} //... + + - name: Print JDK used + if: matrix.os != 'windows-latest' + run: cat $(bazel info execution_root)/external/local_jdk/BUILD.bazel | grep java_home - name: Upload test logs if: always() -- cgit v1.2.3 From d6bc8f7de91b7e31748432a2ad7d5b0cfdf93053 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 19 Jan 2022 16:15:21 +0100 Subject: Update Bazel to 5.0.0 --- .bazelversion | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bazelversion b/.bazelversion index af8c8ec7..0062ac97 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -4.2.2 +5.0.0 -- cgit v1.2.3 From ffac0f2e3671c0358da9d45b001d0c68923393fe Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 21 Jan 2022 21:18:10 +0100 Subject: Remove duplicated shading Now that we shade the entire agent, we don't have to shade the instrumentor deps anymore. --- agent/agent_shade_rules | 1 + .../jazzer/instrumentor/BUILD.bazel | 14 --------- .../jazzer/instrumentor/CoverageRecorder.kt | 16 +++++----- .../instrumentor/EdgeCoverageInstrumentor.kt | 34 +++++++++++----------- .../jazzer/instrumentor/HookInstrumentor.kt | 8 ++--- .../jazzer/instrumentor/HookMethodVisitor.kt | 10 +++---- .../jazzer/instrumentor/Instrumentor.kt | 4 +-- .../instrumentor/TraceDataFlowInstrumentor.kt | 26 ++++++++--------- .../jazzer/instrumentor/shade_rules | 1 - 9 files changed, 50 insertions(+), 64 deletions(-) delete mode 100644 agent/src/main/java/com/code_intelligence/jazzer/instrumentor/shade_rules diff --git a/agent/agent_shade_rules b/agent/agent_shade_rules index b0e4d8c0..2897ec37 100644 --- a/agent/agent_shade_rules +++ b/agent/agent_shade_rules @@ -2,3 +2,4 @@ rule kotlin.** com.code_intelligence.jazzer.third_party.kotlin.@1 rule io.** com.code_intelligence.jazzer.third_party.io.@1 rule nonapi.** com.code_intelligence.jazzer.third_party.nonapi.@1 rule net.jodah.** com.code_intelligence.jazzer.third_party.net.jodah.@1 +rule org.** com.code_intelligence.jazzer.third_party.org.@1 diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel index 50d10705..c29f84e0 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -20,24 +20,10 @@ kt_jvm_library( "//agent/src/test/java/com/code_intelligence/jazzer/instrumentor:__pkg__", ], deps = [ - ":shaded_deps", "//agent/src/main/java/com/code_intelligence/jazzer/runtime", "//agent/src/main/java/com/code_intelligence/jazzer/utils", "@com_github_classgraph_classgraph//:classgraph", "@com_github_jetbrains_kotlin//:kotlin-reflect", - ], -) - -jar_jar( - name = "shaded_deps", - input_jar = "unshaded_deps_deploy.jar", - rules = "shade_rules", -) - -java_binary( - name = "unshaded_deps", - create_executable = False, - runtime_deps = [ "@jazzer_jacoco//:jacoco_internal", "@jazzer_ow2_asm//:asm", "@jazzer_ow2_asm//:asm_commons", diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt index 65956189..cd028c0f 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt @@ -15,16 +15,16 @@ package com.code_intelligence.jazzer.instrumentor import com.code_intelligence.jazzer.runtime.CoverageMap -import com.code_intelligence.jazzer.third_party.jacoco.core.analysis.CoverageBuilder -import com.code_intelligence.jazzer.third_party.jacoco.core.data.ExecutionData -import com.code_intelligence.jazzer.third_party.jacoco.core.data.ExecutionDataReader -import com.code_intelligence.jazzer.third_party.jacoco.core.data.ExecutionDataStore -import com.code_intelligence.jazzer.third_party.jacoco.core.data.ExecutionDataWriter -import com.code_intelligence.jazzer.third_party.jacoco.core.data.SessionInfo -import com.code_intelligence.jazzer.third_party.jacoco.core.data.SessionInfoStore -import com.code_intelligence.jazzer.third_party.jacoco.core.internal.data.CRC64 import com.code_intelligence.jazzer.utils.ClassNameGlobber import io.github.classgraph.ClassGraph +import org.jacoco.core.analysis.CoverageBuilder +import org.jacoco.core.data.ExecutionData +import org.jacoco.core.data.ExecutionDataReader +import org.jacoco.core.data.ExecutionDataStore +import org.jacoco.core.data.ExecutionDataWriter +import org.jacoco.core.data.SessionInfo +import org.jacoco.core.data.SessionInfoStore +import org.jacoco.core.internal.data.CRC64 import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.time.Instant diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt index ba5b7ee9..fab47c88 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt @@ -15,23 +15,23 @@ package com.code_intelligence.jazzer.instrumentor import com.code_intelligence.jazzer.runtime.CoverageMap -import com.code_intelligence.jazzer.third_party.jacoco.core.analysis.Analyzer -import com.code_intelligence.jazzer.third_party.jacoco.core.analysis.ICoverageVisitor -import com.code_intelligence.jazzer.third_party.jacoco.core.data.ExecutionDataStore -import com.code_intelligence.jazzer.third_party.jacoco.core.internal.flow.ClassProbesAdapter -import com.code_intelligence.jazzer.third_party.jacoco.core.internal.flow.ClassProbesVisitor -import com.code_intelligence.jazzer.third_party.jacoco.core.internal.flow.IClassProbesAdapterFactory -import com.code_intelligence.jazzer.third_party.jacoco.core.internal.flow.JavaNoThrowMethods -import com.code_intelligence.jazzer.third_party.jacoco.core.internal.instr.ClassInstrumenter -import com.code_intelligence.jazzer.third_party.jacoco.core.internal.instr.IProbeArrayStrategy -import com.code_intelligence.jazzer.third_party.jacoco.core.internal.instr.IProbeInserterFactory -import com.code_intelligence.jazzer.third_party.jacoco.core.internal.instr.InstrSupport -import com.code_intelligence.jazzer.third_party.jacoco.core.internal.instr.ProbeInserter -import com.code_intelligence.jazzer.third_party.objectweb.asm.ClassReader -import com.code_intelligence.jazzer.third_party.objectweb.asm.ClassVisitor -import com.code_intelligence.jazzer.third_party.objectweb.asm.ClassWriter -import com.code_intelligence.jazzer.third_party.objectweb.asm.MethodVisitor -import com.code_intelligence.jazzer.third_party.objectweb.asm.Opcodes +import org.jacoco.core.analysis.Analyzer +import org.jacoco.core.analysis.ICoverageVisitor +import org.jacoco.core.data.ExecutionDataStore +import org.jacoco.core.internal.flow.ClassProbesAdapter +import org.jacoco.core.internal.flow.ClassProbesVisitor +import org.jacoco.core.internal.flow.IClassProbesAdapterFactory +import org.jacoco.core.internal.flow.JavaNoThrowMethods +import org.jacoco.core.internal.instr.ClassInstrumenter +import org.jacoco.core.internal.instr.IProbeArrayStrategy +import org.jacoco.core.internal.instr.IProbeInserterFactory +import org.jacoco.core.internal.instr.InstrSupport +import org.jacoco.core.internal.instr.ProbeInserter +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes import kotlin.math.max class EdgeCoverageInstrumentor( diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookInstrumentor.kt index ac5f1780..6db76605 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookInstrumentor.kt @@ -14,10 +14,10 @@ package com.code_intelligence.jazzer.instrumentor -import com.code_intelligence.jazzer.third_party.objectweb.asm.ClassReader -import com.code_intelligence.jazzer.third_party.objectweb.asm.ClassVisitor -import com.code_intelligence.jazzer.third_party.objectweb.asm.ClassWriter -import com.code_intelligence.jazzer.third_party.objectweb.asm.MethodVisitor +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.MethodVisitor internal class HookInstrumentor(private val hooks: Iterable, private val java6Mode: Boolean) : Instrumentor { diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt index 7c23c703..0c0f0d01 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt @@ -15,11 +15,11 @@ package com.code_intelligence.jazzer.instrumentor import com.code_intelligence.jazzer.api.HookType -import com.code_intelligence.jazzer.third_party.objectweb.asm.Handle -import com.code_intelligence.jazzer.third_party.objectweb.asm.MethodVisitor -import com.code_intelligence.jazzer.third_party.objectweb.asm.Opcodes -import com.code_intelligence.jazzer.third_party.objectweb.asm.Type -import com.code_intelligence.jazzer.third_party.objectweb.asm.commons.LocalVariablesSorter +import org.objectweb.asm.Handle +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes +import org.objectweb.asm.Type +import org.objectweb.asm.commons.LocalVariablesSorter internal fun makeHookMethodVisitor( access: Int, diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt index 86ad45a3..78793842 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt @@ -14,8 +14,8 @@ package com.code_intelligence.jazzer.instrumentor -import com.code_intelligence.jazzer.third_party.objectweb.asm.Opcodes -import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.MethodNode +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.MethodNode enum class InstrumentationType { CMP, diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentor.kt index e6d3176e..7d4d06dc 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentor.kt @@ -15,19 +15,19 @@ package com.code_intelligence.jazzer.instrumentor import com.code_intelligence.jazzer.runtime.TraceDataFlowNativeCallbacks -import com.code_intelligence.jazzer.third_party.objectweb.asm.ClassReader -import com.code_intelligence.jazzer.third_party.objectweb.asm.ClassWriter -import com.code_intelligence.jazzer.third_party.objectweb.asm.Opcodes -import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.AbstractInsnNode -import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.ClassNode -import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.InsnList -import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.InsnNode -import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.IntInsnNode -import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.LdcInsnNode -import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.LookupSwitchInsnNode -import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.MethodInsnNode -import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.MethodNode -import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.TableSwitchInsnNode +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.AbstractInsnNode +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.InsnList +import org.objectweb.asm.tree.InsnNode +import org.objectweb.asm.tree.IntInsnNode +import org.objectweb.asm.tree.LdcInsnNode +import org.objectweb.asm.tree.LookupSwitchInsnNode +import org.objectweb.asm.tree.MethodInsnNode +import org.objectweb.asm.tree.MethodNode +import org.objectweb.asm.tree.TableSwitchInsnNode internal class TraceDataFlowInstrumentor(private val types: Set, callbackClass: Class<*> = TraceDataFlowNativeCallbacks::class.java) : Instrumentor { diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/shade_rules b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/shade_rules deleted file mode 100644 index c2092b3b..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/shade_rules +++ /dev/null @@ -1 +0,0 @@ -rule org.** com.code_intelligence.jazzer.third_party.@1 \ No newline at end of file -- cgit v1.2.3 From 44b7974026f0cf8fa5f1ef4aa744bd77398f0f80 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 24 Jan 2022 14:31:16 +0100 Subject: Add a test to ensure the agent jar is fully shaded --- agent/BUILD.bazel | 16 ++++++++++++++++ agent/verify_shading.sh | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100755 agent/verify_shading.sh diff --git a/agent/BUILD.bazel b/agent/BUILD.bazel index ddafc246..b227cf06 100644 --- a/agent/BUILD.bazel +++ b/agent/BUILD.bazel @@ -1,4 +1,5 @@ load("@com_github_johnynek_bazel_jar_jar//:jar_jar.bzl", "jar_jar") +load("//bazel:compat.bzl", "SKIP_ON_WINDOWS") load("//sanitizers:sanitizers.bzl", "SANITIZER_CLASSES") java_binary( @@ -21,6 +22,21 @@ jar_jar( visibility = ["//visibility:public"], ) +sh_test( + name = "jazzer_agent_shading_test", + srcs = ["verify_shading.sh"], + args = [ + "$(rootpath jazzer_agent_deploy.jar)", + ], + data = [ + # sh_test does not correctly forward runfiles associated to targets (such as + # ":jazzer_agent_deploy"), so depend on a generated file instead. + "jazzer_agent_deploy.jar", + "@local_jdk//:bin/jar", + ], + target_compatible_with = SKIP_ON_WINDOWS, +) + java_binary( name = "jazzer_api", create_executable = False, diff --git a/agent/verify_shading.sh b/agent/verify_shading.sh new file mode 100755 index 00000000..1f116b87 --- /dev/null +++ b/agent/verify_shading.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env sh +# Copyright 2022 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. + +# List all files in the jar and exclude an allowed list of files. +# Since grep fails if there is no match, ! ... | grep ... fails if there is a +# match. +! external/local_jdk/bin/jar tf "$1" | \ + grep -v \ + -e '^build-data.properties$' \ + -e '^com/$' \ + -e '^com/code_intelligence/$' \ + -e '^com/code_intelligence/jazzer/' \ + -e '^jaz/' \ + -e '^META-INF/' -- cgit v1.2.3 From f4a8267485a3624e8d446f3328265c9cec8dab04 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 24 Jan 2022 14:38:10 +0100 Subject: Do not compile SignalHandler twice This also gets rid of a compiler warning about SignalHandler being internal API even without the annotations. --- agent/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel | 4 ---- .../main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java | 1 - 2 files changed, 5 deletions(-) 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 c7df3ccd..c7a78071 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 @@ -17,9 +17,6 @@ java_library( java_library( name = "signal_handler", srcs = ["SignalHandler.java"], - javacopts = [ - "-XDenableSunApiLintControl", - ], ) kt_jvm_library( @@ -32,7 +29,6 @@ kt_jvm_library( "ManifestUtils.kt", "NativeLibHooks.java", "RecordingFuzzedDataProvider.java", - "SignalHandler.java", "TraceCmpHooks.java", "TraceDataFlowNativeCallbacks.java", "TraceDivHooks.java", diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java index 0a42aa94..06821d47 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java @@ -16,7 +16,6 @@ package com.code_intelligence.jazzer.runtime; import sun.misc.Signal; -@SuppressWarnings({"unused", "sunapi"}) final class SignalHandler { public static native void handleInterrupt(); -- cgit v1.2.3 From 9fb7880ddaba14890381061a5f8479f14d660134 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 24 Jan 2022 14:39:04 +0100 Subject: Silence a warning about unsafe casts in TraceCmpHooks That these casts are valid is ensure via runtime type checks, but can't be verified by the compiler. --- .../main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java | 1 + 1 file changed, 1 insertion(+) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java index 8398e1d7..1da9f426 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java @@ -276,6 +276,7 @@ final public class TraceCmpHooks { // key closest to the current lookup key in the mapGet hook. private static final int MAX_NUM_KEYS_TO_ENUMERATE = 100; + @SuppressWarnings({"rawtypes", "unchecked"}) @MethodHook(type = HookType.AFTER, targetClassName = "com.google.common.collect.ImmutableMap", targetMethod = "get") @MethodHook( -- cgit v1.2.3 From cb7cfc653565b02ab049085afc21935f558e2a4a Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 27 Jan 2022 09:50:55 +0100 Subject: Disable BuildBuddy integration GitHub Actions does not emit secrets into workflow started from forks, which results in the BuildBuddy authorization header being empty for PRs of external contributors. Rather than interpreting such requests as unauthenticated, BuildBuddy errors out: ERROR: The Build Event Protocol upload failed: Not retrying publishBuildEvents, no more attempts left: status='Status{code=UNAUTHENTICATED, description=failed to parse API key: missing API Key, cause=null}' UNAUTHENTICATED: UNAUTHENTICATED: failed to parse API key: missing API Key UNAUTHENTICATED: UNAUTHENTICATED: failed to parse API key: missing API Key At this point (as we are not using Remote Build Execution, just build result upload), I don't see a reason to further complicate our CI setup by setting the header conditionally. Rather, just disable the upload for now. --- .bazelrc | 6 ------ .github/workflows/release.yml | 2 +- .github/workflows/run-all-tests.yml | 4 ++-- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/.bazelrc b/.bazelrc index 6c60e6c2..fec99468 100644 --- a/.bazelrc +++ b/.bazelrc @@ -33,12 +33,6 @@ run:windows --noincompatible_strict_action_env # Since the toolchain is conditional on OS and architecture, set it on the particular GitHub Action. build:toolchain --//third_party:toolchain -# CI tests (not using the toolchain to test OSS-Fuzz & local compatibility) -build:ci --bes_results_url=https://app.buildbuddy.io/invocation/ -build:ci --bes_backend=grpcs://cloud.buildbuddy.io -build:ci --remote_cache=grpcs://cloud.buildbuddy.io -build:ci --remote_timeout=3600 - # Maven publishing (local only, requires GPG signature) build:maven --config=toolchain build:maven --stamp diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e0d9b593..10280370 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: - name: Build run: | - bazelisk build --config=ci --remote_header=x-buildbuddy-api-key=${{ secrets.BUILDBUDDY_API_KEY }} ${{ matrix.bazel_args }} //agent/src/main/java/com/code_intelligence/jazzer/replay:Replayer_deploy.jar //:jazzer_release + bazelisk build ${{ matrix.bazel_args }} //agent/src/main/java/com/code_intelligence/jazzer/replay:Replayer_deploy.jar //:jazzer_release cp -L bazel-bin/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer_deploy.jar replayer.jar cp -L bazel-bin/jazzer_release.tar.gz release-${{ matrix.arch }}.tar.gz diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-all-tests.yml index 15124ce2..969832e2 100644 --- a/.github/workflows/run-all-tests.yml +++ b/.github/workflows/run-all-tests.yml @@ -50,10 +50,10 @@ jobs: key: bazel-disk-cache-${{ matrix.arch }}-${{ matrix.jdk }} - name: Build - run: bazelisk build --config=ci --remote_header=x-buildbuddy-api-key=${{ secrets.BUILDBUDDY_API_KEY }} --disk_cache=${{ matrix.cache }} ${{ matrix.bazel_args }} //... + run: bazelisk build --disk_cache=${{ matrix.cache }} ${{ matrix.bazel_args }} //... - name: Test - run: bazelisk test --config=ci --remote_header=x-buildbuddy-api-key=${{ secrets.BUILDBUDDY_API_KEY }} --disk_cache=${{ matrix.cache }} ${{ matrix.bazel_args }} //... + run: bazelisk test --disk_cache=${{ matrix.cache }} ${{ matrix.bazel_args }} //... - name: Print JDK used if: matrix.os != 'windows-latest' -- cgit v1.2.3 From f920d39a7fa05f9a22f7379747ee631b22c14c94 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 27 Jan 2022 10:45:56 +0100 Subject: Silence a compiler warning on unused parameters (#291) These parameters are unused, but part of a contract. --- .../java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt index 2d4fb9cf..659a49f1 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt @@ -31,6 +31,7 @@ object NamingContextLookup { private const val LDAP_MARKER = "ldap://g.co/" private const val RMI_MARKER = "rmi://g.co/" + @Suppress("UNUSED_PARAMETER") @MethodHooks( MethodHook( type = HookType.REPLACE, -- cgit v1.2.3 From fbbdfd5361176bbe0a34d003367ea848e27e9d82 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 27 Jan 2022 08:40:58 +0100 Subject: Re-enable pinning local JDK versions in CI This ensures that Bazel indeed runs with the expected version of the JDK. This previously didn't work because I missed a typo in the migration notes (localjdk instead of local_jdk). --- .github/workflows/release.yml | 2 +- .github/workflows/run-all-tests.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 10280370..700f83b6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: - name: Build run: | - bazelisk build ${{ matrix.bazel_args }} //agent/src/main/java/com/code_intelligence/jazzer/replay:Replayer_deploy.jar //:jazzer_release + bazelisk build --java_runtime_version=local_jdk_8 ${{ matrix.bazel_args }} //agent/src/main/java/com/code_intelligence/jazzer/replay:Replayer_deploy.jar //:jazzer_release cp -L bazel-bin/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer_deploy.jar replayer.jar cp -L bazel-bin/jazzer_release.tar.gz release-${{ matrix.arch }}.tar.gz diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-all-tests.yml index 969832e2..be053d87 100644 --- a/.github/workflows/run-all-tests.yml +++ b/.github/workflows/run-all-tests.yml @@ -50,10 +50,10 @@ jobs: key: bazel-disk-cache-${{ matrix.arch }}-${{ matrix.jdk }} - name: Build - run: bazelisk build --disk_cache=${{ matrix.cache }} ${{ matrix.bazel_args }} //... + run: bazelisk build --java_runtime_version=local_jdk_${{ matrix.jdk }} --disk_cache=${{ matrix.cache }} ${{ matrix.bazel_args }} //... - name: Test - run: bazelisk test --disk_cache=${{ matrix.cache }} ${{ matrix.bazel_args }} //... + run: bazelisk test --java_runtime_version=local_jdk_${{ matrix.jdk }} --disk_cache=${{ matrix.cache }} ${{ matrix.bazel_args }} //... - name: Print JDK used if: matrix.os != 'windows-latest' -- cgit v1.2.3 From 5349b6c077d5f67569b780438e87e7edc956c035 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sun, 23 Jan 2022 22:43:28 +0100 Subject: Extract coverage counters descriptor out of patch --- .../instrumentor/EdgeCoverageInstrumentor.kt | 2 + .../jacoco-make-probe-inserter-subclassable.patch | 67 +++++++++++++++++----- 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt index fab47c88..4d69e981 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt @@ -170,6 +170,8 @@ class EdgeCoverageInstrumentor( val newMaxStack = max(maxStack + instrumentControlFlowEdgeStackSize, loadCoverageMapStackSize) mv.visitMaxs(newMaxStack, maxLocals + 1) } + + override fun getLocalType() = "java/nio/ByteBuffer" } private val edgeCoverageProbeInserterFactory = diff --git a/third_party/jacoco-make-probe-inserter-subclassable.patch b/third_party/jacoco-make-probe-inserter-subclassable.patch index 3885fa1f..04b5be0e 100644 --- a/third_party/jacoco-make-probe-inserter-subclassable.patch +++ b/third_party/jacoco-make-probe-inserter-subclassable.patch @@ -63,21 +63,8 @@ index 00000000..19c2a7e2 + ProbeInserter makeProbeInserter(int access, String name, String desc, + MethodVisitor mv, IProbeArrayStrategy arrayStrategy); +} -diff --git org.jacoco.core/src/org/jacoco/core/internal/instr/InstrSupport.java org.jacoco.core/src/org/jacoco/core/internal/instr/InstrSupport.java -index 71808ac8..3df93f63 100644 ---- org.jacoco.core/src/org/jacoco/core/internal/instr/InstrSupport.java -+++ org.jacoco.core/src/org/jacoco/core/internal/instr/InstrSupport.java -@@ -78,7 +78,7 @@ public final class InstrSupport { - * Data type of the field that stores coverage information for a class ( - * boolean[]). - */ -- public static final String DATAFIELD_DESC = "[Z"; -+ public static final String DATAFIELD_DESC = "java/nio/ByteBuffer"; - - // === Init Method === - diff --git org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeInserter.java org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeInserter.java -index 0f5b99ff..ba5daa6d 100644 +index 0f5b99ff..80965dfe 100644 --- org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeInserter.java +++ org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeInserter.java @@ -25,7 +25,7 @@ import org.objectweb.asm.TypePath; @@ -107,3 +94,55 @@ index 0f5b99ff..ba5daa6d 100644 final MethodVisitor mv, final IProbeArrayStrategy arrayStrategy) { super(InstrSupport.ASM_API_VERSION, mv); this.clinit = InstrSupport.CLINIT_NAME.equals(name); +@@ -91,6 +91,10 @@ class ProbeInserter extends MethodVisitor implements IProbeInserter { + mv.visitInsn(Opcodes.BASTORE); + } + ++ protected Object getLocalVariableType() { ++ return InstrSupport.DATAFIELD_DESC; ++ } ++ + @Override + public void visitCode() { + accessorStackSize = arrayStrategy.storeInstance(mv, clinit, variable); +@@ -118,6 +122,10 @@ class ProbeInserter extends MethodVisitor implements IProbeInserter { + public AnnotationVisitor visitLocalVariableAnnotation(final int typeRef, + final TypePath typePath, final Label[] start, final Label[] end, + final int[] index, final String descriptor, final boolean visible) { ++ if (getLocalVariableType() == null) { ++ return visitLocalVariableAnnotation(typeRef, typePath, start, end, index, descriptor, visible); ++ } ++ + final int[] newIndex = new int[index.length]; + for (int i = 0; i < newIndex.length; i++) { + newIndex[i] = map(index[i]); +@@ -137,6 +145,9 @@ class ProbeInserter extends MethodVisitor implements IProbeInserter { + } + + private int map(final int var) { ++ if (getLocalVariableType() == null) { ++ return var; ++ } + if (var < variable) { + return var; + } else { +@@ -153,13 +164,18 @@ class ProbeInserter extends MethodVisitor implements IProbeInserter { + "ClassReader.accept() should be called with EXPAND_FRAMES flag"); + } + ++ if (getLocalVariableType() == null) { ++ mv.visitFrame(type, nLocal, local, nStack, stack); ++ return; ++ } ++ + final Object[] newLocal = new Object[Math.max(nLocal, variable) + 1]; + int idx = 0; // Arrays index for existing locals + int newIdx = 0; // Array index for new locals + int pos = 0; // Current variable position + while (idx < nLocal || pos <= variable) { + if (pos == variable) { +- newLocal[newIdx++] = InstrSupport.DATAFIELD_DESC; ++ newLocal[newIdx++] = getLocalVariableType(); + pos++; + } else { + if (idx < nLocal) { -- cgit v1.2.3 From 6b2b43d6ba7d7feae20cfe275857609672aa5a6f Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 24 Jan 2022 09:08:05 +0100 Subject: Refactor EdgeCoverageInstrumentor The aim of this refactoring is to decouple the edge coverage instrumentation from the coverage map class as well as split its logic into three clearly defined parts: * An EdgeCoverageStrategy determines the actual type of bytecode instrumentation applied, which depends on the particular nature of the counters field of the coverage map class. * The EdgeCoverageInstrumentor class, which realizes the instrumentation strategy described by an EdgeCoverageStrategy using JaCoCo. This is the only part of the code that uses JaCoCo classes. * The makeTestable function in the test module, which can now mock the coverage map without requiring testing-related hooks in the other parts. This refactoring should make it easier to modify and replace parts of edge coverage instrumenation mechanism more easily, as well as test and benchmark them in isolation. --- .../jazzer/instrumentor/BUILD.bazel | 1 + .../jazzer/instrumentor/ClassInstrumentor.kt | 11 +- .../jazzer/instrumentor/CoverageRecorder.kt | 27 +-- .../instrumentor/DirectByteBufferStrategy.kt | 81 +++++++++ .../instrumentor/EdgeCoverageInstrumentor.kt | 188 ++++++++++----------- .../jazzer/runtime/CoverageMap.java | 12 +- .../instrumentor/CoverageInstrumentationTest.kt | 37 +++- .../jazzer/instrumentor/MockCoverageMap.java | 13 +- driver/coverage_tracker.cpp | 5 +- driver/testdata/test/FuzzTargetWithCoverage.java | 4 +- 10 files changed, 247 insertions(+), 132 deletions(-) create mode 100644 agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel index c29f84e0..f8ea25e9 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -8,6 +8,7 @@ kt_jvm_library( "CoverageRecorder.kt", "DescriptorUtils.kt", "DeterministicRandom.kt", + "DirectByteBufferStrategy.kt", "EdgeCoverageInstrumentor.kt", "Hook.kt", "HookInstrumentor.kt", diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt index f6728a1a..f1a0ab25 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt @@ -14,6 +14,8 @@ package com.code_intelligence.jazzer.instrumentor +import com.code_intelligence.jazzer.runtime.CoverageMap + fun extractClassFileMajorVersion(classfileBuffer: ByteArray): Int { return ((classfileBuffer[6].toInt() and 0xff) shl 8) or (classfileBuffer[7].toInt() and 0xff) } @@ -24,7 +26,11 @@ class ClassInstrumentor constructor(bytecode: ByteArray) { private set fun coverage(initialEdgeId: Int): Int { - val edgeCoverageInstrumentor = EdgeCoverageInstrumentor(initialEdgeId) + val edgeCoverageInstrumentor = EdgeCoverageInstrumentor( + defaultEdgeCoverageStrategy, + defaultCoverageMap, + initialEdgeId, + ) instrumentedBytecode = edgeCoverageInstrumentor.instrument(instrumentedBytecode) return edgeCoverageInstrumentor.numEdges } @@ -49,5 +55,8 @@ class ClassInstrumentor constructor(bytecode: ByteArray) { // Make it possible to use (parts of) the agent without the driver. } } + + val defaultEdgeCoverageStrategy = DirectByteBufferStrategy + val defaultCoverageMap = CoverageMap::class.java } } diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt index cd028c0f..a7d4dc10 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt @@ -52,21 +52,21 @@ object CoverageRecorder { } /** - * Manually records coverage IDs based on the current state of [CoverageMap.mem]. + * Manually records coverage IDs based on the current state of [CoverageMap.counters]. * Should be called after static initializers have run. */ @JvmStatic fun updateCoveredIdsWithCoverageMap() { - val mem = CoverageMap.mem - val size = mem.capacity() - additionalCoverage.addAll((0 until size).filter { mem[it] > 0 }) + val counters = CoverageMap.counters + val size = counters.capacity() + additionalCoverage.addAll((0 until size).filter { counters[it] > 0 }) } @JvmStatic fun replayCoveredIds() { - val mem = CoverageMap.mem + val counters = CoverageMap.counters for (coverageId in additionalCoverage) { - mem.put(coverageId, 1) + counters.put(coverageId, 1) } } @@ -173,12 +173,13 @@ object CoverageRecorder { } } for ((internalClassName, info) in instrumentedClassInfo) { - EdgeCoverageInstrumentor(0).analyze( - executionDataStore, - coverage, - info.bytecode, - internalClassName - ) + EdgeCoverageInstrumentor(ClassInstrumentor.defaultEdgeCoverageStrategy, ClassInstrumentor.defaultCoverageMap, 0) + .analyze( + executionDataStore, + coverage, + info.bytecode, + internalClassName + ) } coverage } catch (e: Exception) { @@ -215,7 +216,7 @@ object CoverageRecorder { .filterNot { classInfo -> classInfo.name in coveredClassNames } .forEach { classInfo -> classInfo.resource.use { resource -> - EdgeCoverageInstrumentor(0).analyze( + EdgeCoverageInstrumentor(ClassInstrumentor.defaultEdgeCoverageStrategy, ClassInstrumentor.defaultCoverageMap, 0).analyze( emptyExecutionDataStore, coverage, resource.load(), diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt new file mode 100644 index 00000000..49090184 --- /dev/null +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt @@ -0,0 +1,81 @@ +// Copyright 2022 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.instrumentor + +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes + +object DirectByteBufferStrategy : EdgeCoverageStrategy { + + override fun instrumentControlFlowEdge( + mv: MethodVisitor, + edgeId: Int, + variable: Int, + coverageMapInternalClassName: String + ) { + mv.apply { + visitVarInsn(Opcodes.ALOAD, variable) + // Stack: counters + push(edgeId) + // Stack: counters | edgeId + visitInsn(Opcodes.DUP2) + // Stack: counters | edgeId | counters | edgeId + visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/nio/ByteBuffer", "get", "(I)B", false) + // Increment the counter, but ensure that it never stays at 0 after an overflow by incrementing it again in + // that case. + // This approach performs better than saturating the counter at 255 (see Section 3.3 of + // https://www.usenix.org/system/files/woot20-paper-fioraldi.pdf) + // Stack: counters | edgeId | counter (sign-extended to int) + push(0xff) + // Stack: counters | edgeId | counter (sign-extended to int) | 0x000000ff + visitInsn(Opcodes.IAND) + // Stack: counters | edgeId | counter (zero-extended to int) + push(1) + // Stack: counters | edgeId | counter | 1 + visitInsn(Opcodes.IADD) + // Stack: counters | edgeId | counter + 1 + visitInsn(Opcodes.DUP) + // Stack: counters | edgeId | counter + 1 | counter + 1 + push(8) + // Stack: counters | edgeId | counter + 1 | counter + 1 | 8 (maxStack: +5) + visitInsn(Opcodes.ISHR) + // Stack: counters | edgeId | counter + 1 | 1 if the increment overflowed to 0, 0 otherwise + visitInsn(Opcodes.IADD) + // Stack: counters | edgeId | counter + 2 if the increment overflowed, counter + 1 otherwise + visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/nio/ByteBuffer", "put", "(IB)Ljava/nio/ByteBuffer;", false) + // Stack: counters + visitInsn(Opcodes.POP) + } + } + + override val instrumentControlFlowEdgeStackSize = 5 + + override val localVariableType get() = "java/nio/ByteBuffer" + + override fun loadLocalVariable(mv: MethodVisitor, variable: Int, coverageMapInternalClassName: String) { + mv.apply { + visitFieldInsn( + Opcodes.GETSTATIC, + coverageMapInternalClassName, + "counters", + "Ljava/nio/ByteBuffer;", + ) + // Stack: counters (maxStack: 1) + visitVarInsn(Opcodes.ASTORE, variable) + } + } + + override val loadLocalVariableStackSize = 1 +} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt index 4d69e981..397629c9 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt @@ -14,14 +14,12 @@ package com.code_intelligence.jazzer.instrumentor -import com.code_intelligence.jazzer.runtime.CoverageMap import org.jacoco.core.analysis.Analyzer import org.jacoco.core.analysis.ICoverageVisitor import org.jacoco.core.data.ExecutionDataStore import org.jacoco.core.internal.flow.ClassProbesAdapter import org.jacoco.core.internal.flow.ClassProbesVisitor import org.jacoco.core.internal.flow.IClassProbesAdapterFactory -import org.jacoco.core.internal.flow.JavaNoThrowMethods import org.jacoco.core.internal.instr.ClassInstrumenter import org.jacoco.core.internal.instr.IProbeArrayStrategy import org.jacoco.core.internal.instr.IProbeInserterFactory @@ -31,20 +29,77 @@ import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassVisitor import org.objectweb.asm.ClassWriter import org.objectweb.asm.MethodVisitor -import org.objectweb.asm.Opcodes +import java.lang.invoke.MethodHandle +import java.lang.invoke.MethodHandles.publicLookup +import java.lang.invoke.MethodType.methodType import kotlin.math.max +/** + * A particular way to instrument bytecode for edge coverage using a coverage map class available to + * hold the collected coverage data at runtime. + */ +interface EdgeCoverageStrategy { + + /** + * Inject bytecode instrumentation on a control flow edge with ID [edgeId], with access to the + * local variable [variable] that is populated at the beginning of each method by the + * instrumentation injected in [loadLocalVariable]. + */ + fun instrumentControlFlowEdge( + mv: MethodVisitor, + edgeId: Int, + variable: Int, + coverageMapInternalClassName: String + ) + + /** + * The maximal number of stack elements used by [instrumentControlFlowEdge]. + */ + val instrumentControlFlowEdgeStackSize: Int + + /** + * The type of the local variable used by the instrumentation in the format used by + * [MethodVisitor.visitFrame]'s `local` parameter, or `null` if the instrumentation does not use + * one. + * @see https://asm.ow2.io/javadoc/org/objectweb/asm/MethodVisitor.html#visitFrame(int,int,java.lang.Object%5B%5D,int,java.lang.Object%5B%5D) + */ + val localVariableType: Any? + + /** + * Inject bytecode that loads the coverage counters of the coverage map class described by + * [coverageMapInternalClassName] into the local variable [variable]. + */ + fun loadLocalVariable(mv: MethodVisitor, variable: Int, coverageMapInternalClassName: String) + + /** + * The maximal number of stack elements used by [loadLocalVariable]. + */ + val loadLocalVariableStackSize: Int +} + +// An instance of EdgeCoverageInstrumentor should only be used to instrument a single class as it +// internally tracks the edge IDs, which have to be globally unique. class EdgeCoverageInstrumentor( + private val strategy: EdgeCoverageStrategy, + /** + * The class must have the following public static member + * - method enlargeIfNeeded(int nextEdgeId): Called before a new edge ID is emitted. + */ + coverageMapClass: Class<*>, private val initialEdgeId: Int, - private val coverageMapClass: Class<*> = CoverageMap::class.java ) : Instrumentor { private var nextEdgeId = initialEdgeId + private val coverageMapInternalClassName = coverageMapClass.name.replace('.', '/') - init { - if (isTesting) { - JavaNoThrowMethods.isTesting = true - } - } + private val enlargeIfNeeded: MethodHandle = + publicLookup().findStatic( + coverageMapClass, + "enlargeIfNeeded", + methodType( + Void::class.javaPrimitiveType, + Int::class.javaPrimitiveType + ) + ) override fun instrument(bytecode: ByteArray): ByteArray { val reader = InstrSupport.classReaderFor(bytecode) @@ -67,93 +122,14 @@ class EdgeCoverageInstrumentor( val numEdges get() = nextEdgeId - initialEdgeId - private val isTesting - get() = coverageMapClass != CoverageMap::class.java - private fun nextEdgeId(): Int { - if (nextEdgeId >= CoverageMap.mem.capacity()) { - if (!isTesting) { - CoverageMap.enlargeCoverageMap() - } - } + enlargeIfNeeded.invokeExact(nextEdgeId) return nextEdgeId++ } /** - * The maximal number of stack elements used by [loadCoverageMap]. - */ - private val loadCoverageMapStackSize = 1 - - /** - * Inject bytecode that loads the coverage map into local variable [variable]. - */ - private fun loadCoverageMap(mv: MethodVisitor, variable: Int) { - mv.apply { - visitFieldInsn( - Opcodes.GETSTATIC, - coverageMapInternalClassName, - "mem", - "Ljava/nio/ByteBuffer;" - ) - // Stack: mem (maxStack: 1) - visitVarInsn(Opcodes.ASTORE, variable) - } - } - - /** - * The maximal number of stack elements used by [instrumentControlFlowEdge]. - */ - private val instrumentControlFlowEdgeStackSize = 5 - - /** - * Inject bytecode instrumentation on a control flow edge with ID [edgeId]. The coverage map can be loaded from - * local variable [variable]. - */ - private fun instrumentControlFlowEdge(mv: MethodVisitor, edgeId: Int, variable: Int) { - mv.apply { - visitVarInsn(Opcodes.ALOAD, variable) - // Stack: mem - push(edgeId) - // Stack: mem | edgeId - visitInsn(Opcodes.DUP2) - // Stack: mem | edgeId | mem | edgeId - visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/nio/ByteBuffer", "get", "(I)B", false) - // Increment the counter, but ensure that it never stays at 0 after an overflow by incrementing it again in - // that case. - // This approach performs better than saturating the counter at 255 (see Section 3.3 of - // https://www.usenix.org/system/files/woot20-paper-fioraldi.pdf) - // Stack: mem | edgeId | counter (sign-extended to int) - push(0xff) - // Stack: mem | edgeId | counter (sign-extended to int) | 0x000000ff - visitInsn(Opcodes.IAND) - // Stack: mem | edgeId | counter (zero-extended to int) - push(1) - // Stack: mem | edgeId | counter | 1 - visitInsn(Opcodes.IADD) - // Stack: mem | edgeId | counter + 1 - visitInsn(Opcodes.DUP) - // Stack: mem | edgeId | counter + 1 | counter + 1 - push(8) - // Stack: mem | edgeId | counter + 1 | counter + 1 | 8 (maxStack: +5) - visitInsn(Opcodes.ISHR) - // Stack: mem | edgeId | counter + 1 | 1 if the increment overflowed to 0, 0 otherwise - visitInsn(Opcodes.IADD) - // Stack: mem | edgeId | counter + 2 if the increment overflowed, counter + 1 otherwise - visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/nio/ByteBuffer", "put", "(IB)Ljava/nio/ByteBuffer;", false) - // Stack: mem - visitInsn(Opcodes.POP) - if (isTesting) { - visitMethodInsn(Opcodes.INVOKESTATIC, coverageMapInternalClassName, "updated", "()V", false) - } - } - } - -// The remainder of this file interfaces with classes in org.jacoco.core.internal. Changes to this part should not be -// necessary unless JaCoCo is updated or the way we instrument for coverage changes fundamentally. - - /** - * A [ProbeInserter] that injects the bytecode instrumentation returned by [instrumentControlFlowEdge] and modifies - * the stack size and number of local variables accordingly. + * A [ProbeInserter] that injects bytecode instrumentation at every control flow edge and + * modifies the stack size and number of local variables accordingly. */ private inner class EdgeCoverageProbeInserter( access: Int, @@ -163,15 +139,16 @@ class EdgeCoverageInstrumentor( arrayStrategy: IProbeArrayStrategy, ) : ProbeInserter(access, name, desc, mv, arrayStrategy) { override fun insertProbe(id: Int) { - instrumentControlFlowEdge(mv, id, variable) + strategy.instrumentControlFlowEdge(mv, id, variable, coverageMapInternalClassName) } override fun visitMaxs(maxStack: Int, maxLocals: Int) { - val newMaxStack = max(maxStack + instrumentControlFlowEdgeStackSize, loadCoverageMapStackSize) - mv.visitMaxs(newMaxStack, maxLocals + 1) + val newMaxStack = max(maxStack + strategy.instrumentControlFlowEdgeStackSize, strategy.loadLocalVariableStackSize) + val newMaxLocals = maxLocals + if (strategy.localVariableType != null) 1 else 0 + mv.visitMaxs(newMaxStack, newMaxLocals) } - override fun getLocalType() = "java/nio/ByteBuffer" + override fun getLocalVariableType() = strategy.localVariableType } private val edgeCoverageProbeInserterFactory = @@ -179,9 +156,16 @@ class EdgeCoverageInstrumentor( EdgeCoverageProbeInserter(access, name, desc, mv, arrayStrategy) } - private inner class EdgeCoverageClassProbesAdapter(cv: ClassProbesVisitor, trackFrames: Boolean) : - ClassProbesAdapter(cv, trackFrames) { + private inner class EdgeCoverageClassProbesAdapter(private val cpv: ClassProbesVisitor, trackFrames: Boolean) : + ClassProbesAdapter(cpv, trackFrames) { override fun nextId(): Int = nextEdgeId() + + override fun visitEnd() { + cpv.visitTotalProbeCount(numEdges) + // Avoid calling super.visitEnd() as that invokes cpv.visitTotalProbeCount with an + // incorrect value of `count`. + cv.visitEnd() + } } private val edgeCoverageClassProbesAdapterFactory = IClassProbesAdapterFactory { probesVisitor, trackFrames -> @@ -190,14 +174,14 @@ class EdgeCoverageInstrumentor( private val edgeCoverageProbeArrayStrategy = object : IProbeArrayStrategy { override fun storeInstance(mv: MethodVisitor, clinit: Boolean, variable: Int): Int { - loadCoverageMap(mv, variable) - return loadCoverageMapStackSize + strategy.loadLocalVariable(mv, variable, coverageMapInternalClassName) + return strategy.loadLocalVariableStackSize } override fun addMembers(cv: ClassVisitor, probeCount: Int) {} } +} - private fun MethodVisitor.push(value: Int) { - InstrSupport.push(this, value) - } +fun MethodVisitor.push(value: Int) { + InstrSupport.push(this, value) } diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java index af2424a2..959078bd 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java @@ -22,11 +22,15 @@ import java.nio.ByteBuffer; * native code. */ final public class CoverageMap { - public static ByteBuffer mem = ByteBuffer.allocateDirect(0); + public static ByteBuffer counters = ByteBuffer.allocateDirect(0); - public static void enlargeCoverageMap() { - registerNewCoverageCounters(); - System.out.println("INFO: New number of inline 8-bit counters: " + mem.capacity()); + // Called via reflection. + @SuppressWarnings("unused") + public static void enlargeIfNeeded(int nextId) { + if (nextId >= counters.capacity()) { + registerNewCoverageCounters(); + System.out.println("INFO: New number of inline 8-bit counters: " + counters.capacity()); + } } private static native void registerNewCoverageCounters(); diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt index 15c88f4c..ea9c8bb9 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt @@ -14,12 +14,43 @@ package com.code_intelligence.jazzer.instrumentor +import org.jacoco.core.internal.flow.JavaNoThrowMethods import org.junit.Test +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes import java.io.File import kotlin.test.assertEquals +/** + * Amends the instrumentation performed by [strategy] to call the map's public static void method + * updated() after every update to coverage counters. + * + * Note: Calling this method also enables the testing mode of [JavaNoThrowMethods] globally, which + * means that calls to methods in com.code_intelligence.jazzer.** will also be instrumented. + */ +private fun makeTestable(strategy: EdgeCoverageStrategy): EdgeCoverageStrategy = + object : EdgeCoverageStrategy by strategy { + init { + JavaNoThrowMethods.isTesting = true + } + + override fun instrumentControlFlowEdge( + mv: MethodVisitor, + edgeId: Int, + variable: Int, + coverageMapInternalClassName: String + ) { + strategy.instrumentControlFlowEdge(mv, edgeId, variable, coverageMapInternalClassName) + mv.visitMethodInsn(Opcodes.INVOKESTATIC, coverageMapInternalClassName, "updated", "()V", false) + } + } + private fun applyInstrumentation(bytecode: ByteArray): ByteArray { - return EdgeCoverageInstrumentor(0, MockCoverageMap::class.java).instrument(bytecode) + return EdgeCoverageInstrumentor( + makeTestable(ClassInstrumentor.defaultEdgeCoverageStrategy), + MockCoverageMap::class.java, + 0 + ).instrument(bytecode) } private fun getOriginalInstrumentationTargetInstance(): DynamicTestContract { @@ -114,12 +145,12 @@ class CoverageInstrumentationTest { var lastCounter = 0.toUByte() for (i in 1..600) { assertSelfCheck(target) - assertEquals(1, MockCoverageMap.mem[takenOnceEdge]) + assertEquals(1, MockCoverageMap.counters[takenOnceEdge]) // Verify that the counter increments, but is never zero. val expectedCounter = (lastCounter + 1U).toUByte().takeUnless { it == 0.toUByte() } ?: (lastCounter + 2U).toUByte() lastCounter = expectedCounter - val actualCounter = MockCoverageMap.mem[takenOnEveryRunEdge].toUByte() + val actualCounter = MockCoverageMap.counters[takenOnEveryRunEdge].toUByte() assertEquals(expectedCounter, actualCounter, "After $i runs:") } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java index 787ea493..e1d4d611 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java @@ -20,8 +20,7 @@ import java.util.Arrays; public class MockCoverageMap { public static final int SIZE = 65536; - public static final ByteBuffer mem = ByteBuffer.allocate(SIZE); - public static int prev_location = 0; // is used in byte code directly + public static final ByteBuffer counters = ByteBuffer.allocate(SIZE); private static final ByteBuffer previous_mem = ByteBuffer.allocate(SIZE); public static ArrayList locations = new ArrayList<>(); @@ -29,16 +28,20 @@ public class MockCoverageMap { public static void updated() { int updated_pos = -1; for (int i = 0; i < SIZE; i++) { - if (previous_mem.get(i) != mem.get(i)) { + if (previous_mem.get(i) != counters.get(i)) { updated_pos = i; } } locations.add(updated_pos); - System.arraycopy(mem.array(), 0, previous_mem.array(), 0, SIZE); + System.arraycopy(counters.array(), 0, previous_mem.array(), 0, SIZE); + } + + public static void enlargeIfNeeded(int nextId) { + // This mock coverage map is statically sized. } public static void clear() { - Arrays.fill(mem.array(), (byte) 0); + Arrays.fill(counters.array(), (byte) 0); Arrays.fill(previous_mem.array(), (byte) 0); locations.clear(); } diff --git a/driver/coverage_tracker.cpp b/driver/coverage_tracker.cpp index 0a576085..6066df6c 100644 --- a/driver/coverage_tracker.cpp +++ b/driver/coverage_tracker.cpp @@ -104,8 +104,9 @@ void JNICALL CoverageTracker::RegisterNewCoverageCounters(JNIEnv &env, jclass cls) { jclass coverage_map = env.FindClass(kCoverageMapClass); AssertNoException(env); - jfieldID counters_buffer_id = env.GetStaticFieldID( - coverage_map, "mem", absl::StrFormat("L%s;", kByteBufferClass).c_str()); + jfieldID counters_buffer_id = + env.GetStaticFieldID(coverage_map, "counters", + absl::StrFormat("L%s;", kByteBufferClass).c_str()); AssertNoException(env); jobject counters_buffer = env.GetStaticObjectField(coverage_map, counters_buffer_id); diff --git a/driver/testdata/test/FuzzTargetWithCoverage.java b/driver/testdata/test/FuzzTargetWithCoverage.java index 599b1fa8..776e9ce5 100644 --- a/driver/testdata/test/FuzzTargetWithCoverage.java +++ b/driver/testdata/test/FuzzTargetWithCoverage.java @@ -19,10 +19,10 @@ import com.code_intelligence.jazzer.runtime.CoverageMap; public class FuzzTargetWithCoverage { public static void fuzzerTestOneInput(byte[] input) { // manually increase the first coverage counter - byte counter = CoverageMap.mem.get(0); + byte counter = CoverageMap.counters.get(0); counter++; if (counter == 0) counter--; - CoverageMap.mem.put(0, counter); + CoverageMap.counters.put(0, counter); } } -- cgit v1.2.3 From c638e45d613b239cf87e194aa17d2e09eb0426db Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 1 Feb 2022 13:51:38 +0100 Subject: Switch to .tar.gz for all dependency archives Also update abseil-cpp to the latest release. --- repositories.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/repositories.bzl b/repositories.bzl index 90ff51fc..055f7c9b 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -57,9 +57,9 @@ def jazzer_dependencies(): maybe( http_archive, name = "com_google_absl", - sha256 = "5e1cbf25bf501f8e37866000a6052d02dbdd7b19a5b592251c59a4c9aa5c71ae", - strip_prefix = "abseil-cpp-f2dbd918d8d08529800eb72f23bd2829f92104a4", - url = "https://github.com/abseil/abseil-cpp/archive/f2dbd918d8d08529800eb72f23bd2829f92104a4.zip", + sha256 = "dcf71b9cba8dc0ca9940c4b316a0c796be8fab42b070bb6b7cab62b48f0e66c4", + strip_prefix = "abseil-cpp-20211102.0", + url = "https://github.com/abseil/abseil-cpp/archive/refs/tags/20211102.0.tar.gz", ) maybe( -- cgit v1.2.3 From 37e188eae83c5211712e4c9193adfd2b662af088 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 3 Feb 2022 10:27:54 +0100 Subject: Update rules_kotlin to 1.5.0 --- repositories.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repositories.bzl b/repositories.bzl index 055f7c9b..0971439f 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -41,8 +41,8 @@ def jazzer_dependencies(): maybe( http_archive, name = "io_bazel_rules_kotlin", - sha256 = "6cbd4e5768bdfae1598662e40272729ec9ece8b7bded8f0d2c81c8ff96dc139d", - url = "https://github.com/bazelbuild/rules_kotlin/releases/download/v1.5.0-beta-4/rules_kotlin_release.tgz", + sha256 = "12d22a3d9cbcf00f2e2d8f0683ba87d3823cb8c7f6837568dd7e48846e023307", + url = "https://github.com/bazelbuild/rules_kotlin/releases/download/v1.5.0/rules_kotlin_release.tgz", ) maybe( -- cgit v1.2.3 From d9d1da22eb80369b5da19d59dd9b1b407d74a465 Mon Sep 17 00:00:00 2001 From: Khaled Yakdan Date: Sun, 6 Feb 2022 22:04:30 +0100 Subject: Suppress warnings about unused sanitizer objects --- .../java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt | 2 +- .../code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt | 2 +- .../java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt | 1 + .../main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt index f6401dfd..55691c1a 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt @@ -29,7 +29,7 @@ import java.util.WeakHashMap /** * Detects unsafe deserialization that leads to attacker-controlled method calls, in particular to [Object.finalize]. */ -@Suppress("unused_parameter") +@Suppress("unused_parameter", "unused") object Deserialization { private val OBJECT_INPUT_STREAM_HEADER = diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt index 9b1e8ca6..0fc052f9 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt @@ -24,7 +24,7 @@ import java.lang.invoke.MethodHandle /** * Detects injectable inputs to an expression language interpreter which may lead to remote code execution. */ -@Suppress("unused_parameter") +@Suppress("unused_parameter", "unused") object ExpressionLanguageInjection { /** diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt index 659a49f1..1d2dd772 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt @@ -22,6 +22,7 @@ import com.code_intelligence.jazzer.api.MethodHooks import java.lang.invoke.MethodHandle import javax.naming.CommunicationException +@Suppress("unused") object NamingContextLookup { // The particular URL g.co is used here since it is: diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt index 7842d879..3dfd3ca1 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt @@ -22,7 +22,7 @@ import java.lang.invoke.MethodHandle /** * Detects unsafe reflective calls that lead to attacker-controlled method calls. */ -@Suppress("unused_parameter") +@Suppress("unused_parameter", "unused") object ReflectiveCall { @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Class", targetMethod = "forName") -- cgit v1.2.3 From 51086c74afde9f6baebd70648f5f3319b35f54d8 Mon Sep 17 00:00:00 2001 From: Khaled Yakdan Date: Sun, 6 Feb 2022 22:06:26 +0100 Subject: Simplify the hookElExpressionFactory hook --- .../jazzer/sanitizers/ExpressionLanguageInjection.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt index 0fc052f9..77675990 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt @@ -52,10 +52,8 @@ object ExpressionLanguageInjection { arguments: Array, hookId: Int ) { - if (arguments[1] is String) { - val expression = arguments[1] as String - Jazzer.guideTowardsContainment(expression, EXPRESSION_LANGUAGE_ATTACK, hookId) - } + val expression = arguments[1] as? String ?: return + Jazzer.guideTowardsContainment(expression, EXPRESSION_LANGUAGE_ATTACK, hookId) } // With default configurations the argument to -- cgit v1.2.3 From decc5a490a0470887f0d878bf94621c696e3bdbe Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sun, 6 Feb 2022 08:14:57 +0100 Subject: Remove Java dependencies on CoverageMap internals --- .../jazzer/instrumentor/CoverageRecorder.kt | 11 +++-------- .../code_intelligence/jazzer/runtime/CoverageMap.java | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt index a7d4dc10..b873d118 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt @@ -52,22 +52,17 @@ object CoverageRecorder { } /** - * Manually records coverage IDs based on the current state of [CoverageMap.counters]. + * Manually records coverage IDs based on the current state of [CoverageMap]. * Should be called after static initializers have run. */ @JvmStatic fun updateCoveredIdsWithCoverageMap() { - val counters = CoverageMap.counters - val size = counters.capacity() - additionalCoverage.addAll((0 until size).filter { counters[it] > 0 }) + additionalCoverage.addAll(CoverageMap.getCoveredIds()) } @JvmStatic fun replayCoveredIds() { - val counters = CoverageMap.counters - for (coverageId in additionalCoverage) { - counters.put(coverageId, 1) - } + CoverageMap.replayCoveredIds(additionalCoverage) } @JvmStatic diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java index 959078bd..71bccbd9 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java @@ -15,6 +15,9 @@ package com.code_intelligence.jazzer.runtime; import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; /** * Represents the Java view on a libFuzzer 8 bit counter coverage map. @@ -33,5 +36,21 @@ final public class CoverageMap { } } + public static Set getCoveredIds() { + Set coveredIds = new HashSet<>(); + for (int id = 0; id < counters.capacity(); id++) { + if (counters.get(id) > 0) { + coveredIds.add(id); + } + } + return Collections.unmodifiableSet(coveredIds); + } + + public static void replayCoveredIds(Set coveredIds) { + for (int id : coveredIds) { + counters.put(id, (byte) 1); + } + } + private static native void registerNewCoverageCounters(); } -- cgit v1.2.3 From a4fa864c13ea29d73e53c755a9b79e665a9a0b36 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sun, 6 Feb 2022 08:20:43 +0100 Subject: Remove C++ dependencies on CoverageMap internals This commit moves almost all coverage map logic into the Java class with the aim of making it easier to replace the particular implementation of the coverage map and reducing the amount of JNI code. This is almost a pure refactoring, but also makes the allocations of fake instructions and libFuzzer PC entries dynamic. This both slightly reduces the constant memory footprint of the fuzzer and further improves the encapsulation of the Java CoverageMap implementation as the native code no longer has to know the maximum size of the map. --- .../jazzer/runtime/CoverageMap.java | 60 ++++++++-- driver/coverage_tracker.cpp | 133 ++++++++------------- driver/coverage_tracker.h | 10 +- driver/fuzz_target_runner.cpp | 2 +- driver/fuzzed_data_provider_test.cpp | 4 +- driver/jvm_tooling.cpp | 1 - driver/jvm_tooling_test.cpp | 9 +- 7 files changed, 107 insertions(+), 112 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java index 71bccbd9..ad682ca0 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java @@ -20,19 +20,61 @@ import java.util.HashSet; import java.util.Set; /** - * Represents the Java view on a libFuzzer 8 bit counter coverage map. - * By using a direct ByteBuffer, the counter array is shared directly with - * native code. + * Represents the Java view on a libFuzzer 8 bit counter coverage map. By using a direct ByteBuffer, + * the counters are shared directly with native code. */ final public class CoverageMap { - public static ByteBuffer counters = ByteBuffer.allocateDirect(0); + private static final String ENV_MAX_NUM_COUNTERS = "JAZZER_MAX_NUM_COUNTERS"; + + private static final int MAX_NUM_COUNTERS = System.getenv(ENV_MAX_NUM_COUNTERS) != null + ? Integer.parseInt(System.getenv(ENV_MAX_NUM_COUNTERS)) + : 1 << 20; + + /** + * The collection of coverage counters directly interacted with by classes that are instrumented + * for coverage. The instrumentation assumes that this is always one contiguous block of memory, + * so it is allocated once at maximum size. Using a larger number here increases the memory usage + * of all fuzz targets, but has otherwise no impact on performance. + */ + public static final ByteBuffer counters = ByteBuffer.allocateDirect(MAX_NUM_COUNTERS); + + static { + initialize(counters); + } + + private static final int INITIAL_NUM_COUNTERS = 1 << 9; + + static { + registerNewCounters(0, INITIAL_NUM_COUNTERS); + } + + /** + * The number of coverage counters that are currently registered with libFuzzer. This number grows + * dynamically as classes are instrumented and should be kept as low as possible as libFuzzer has + * to iterate over the whole map for every execution. + */ + private static int currentNumCounters = INITIAL_NUM_COUNTERS; // Called via reflection. @SuppressWarnings("unused") public static void enlargeIfNeeded(int nextId) { - if (nextId >= counters.capacity()) { - registerNewCoverageCounters(); - System.out.println("INFO: New number of inline 8-bit counters: " + counters.capacity()); + int newNumCounters = currentNumCounters; + while (nextId >= newNumCounters) { + newNumCounters = 2 * currentNumCounters; + if (newNumCounters > MAX_NUM_COUNTERS) { + System.out.printf("ERROR: Maximum number (%s) of coverage counters exceeded. Try to%n" + + " limit the scope of a single fuzz target as much as possible to keep the%n" + + " fuzzer fast.%n" + + " If that is not possible, the maximum number of counters can be increased%n" + + " via the JAZZER_MAX_NUM_COUNTERS environment variable.", + MAX_NUM_COUNTERS); + System.exit(1); + } + } + if (newNumCounters > currentNumCounters) { + registerNewCounters(currentNumCounters, newNumCounters); + currentNumCounters = newNumCounters; + System.out.println("INFO: New number of coverage counters: " + currentNumCounters); } } @@ -52,5 +94,7 @@ final public class CoverageMap { } } - private static native void registerNewCoverageCounters(); + private static native void initialize(ByteBuffer counters); + + private static native void registerNewCounters(int oldNumCounters, int newNumCounters); } diff --git a/driver/coverage_tracker.cpp b/driver/coverage_tracker.cpp index 6066df6c..562761ce 100644 --- a/driver/coverage_tracker.cpp +++ b/driver/coverage_tracker.cpp @@ -19,8 +19,7 @@ #include #include #include - -#include "absl/strings/str_format.h" +#include extern "C" void __sanitizer_cov_8bit_counters_init(uint8_t *start, uint8_t *end); @@ -28,22 +27,9 @@ extern "C" void __sanitizer_cov_pcs_init(const uintptr_t *pcs_beg, const uintptr_t *pcs_end); extern "C" size_t __sanitizer_cov_get_observed_pcs(uintptr_t **pc_entries); -constexpr auto kCoverageMapClass = - "com/code_intelligence/jazzer/runtime/CoverageMap"; -constexpr auto kByteBufferClass = "java/nio/ByteBuffer"; constexpr auto kCoverageRecorderClass = "com/code_intelligence/jazzer/instrumentor/CoverageRecorder"; -// The initial size of the Java coverage map (512 counters). -constexpr std::size_t kInitialCoverageCountersBufferSize = 1u << 9u; -// The maximum size of the Java coverage map (1,048,576 counters). -// Since the memory for the coverage map needs to be allocated contiguously, -// increasing the maximum size incurs additional memory (but not runtime) -// overhead for all fuzz targets. -constexpr std::size_t kMaxCoverageCountersBufferSize = 1u << 20u; -static_assert(kMaxCoverageCountersBufferSize <= - std::numeric_limits::max()); - namespace { void AssertNoException(JNIEnv &env) { if (env.ExceptionCheck()) { @@ -60,18 +46,33 @@ uint8_t *CoverageTracker::counters_ = nullptr; uint32_t *CoverageTracker::fake_instructions_ = nullptr; PCTableEntry *CoverageTracker::pc_entries_ = nullptr; -void CoverageTracker::Setup(JNIEnv &env) { +void CoverageTracker::Initialize(JNIEnv &env, jobject buffer) { if (counters_ != nullptr) { throw std::runtime_error( - "CoverageTracker::Setup must not be called more than once"); + "CoverageTracker::Initialize must not be called more than once"); } - JNINativeMethod coverage_tracker_native_methods[]{ - {(char *)"registerNewCoverageCounters", (char *)"()V", - (void *)&RegisterNewCoverageCounters}, - }; - jclass coverage_map = env.FindClass(kCoverageMapClass); - env.RegisterNatives(coverage_map, coverage_tracker_native_methods, 1); + void *counters = env.GetDirectBufferAddress(buffer); + AssertNoException(env); + if (counters == nullptr) { + throw std::runtime_error("Failed to obtain address of counters buffer"); + } + counters_ = static_cast(counters); +} +void CoverageTracker::RegisterNewCounters(JNIEnv &env, jint old_num_counters, + jint new_num_counters) { + if (counters_ == nullptr) { + throw std::runtime_error( + "CoverageTracker::Initialize should have been called first"); + } + if (new_num_counters < old_num_counters) { + throw std::runtime_error( + "new_num_counters must not be smaller than old_num_counters"); + } + if (new_num_counters == old_num_counters) { + return; + } + std::size_t diff_num_counters = new_num_counters - old_num_counters; // libFuzzer requires an array containing the instruction addresses associated // with the coverage counters registered above. Given that we are // instrumenting Java code, we need to synthesize addresses that are known not @@ -80,75 +81,20 @@ void CoverageTracker::Setup(JNIEnv &env) { // allocated buffer. Note: We intentionally never deallocate the allocations // made here as they have static lifetime and we can't guarantee they wouldn't // be freed before libFuzzer stops using them. - constexpr std::size_t counters_size = kMaxCoverageCountersBufferSize; - counters_ = new uint8_t[counters_size]; - Clear(); - - // Never deallocated, see above. - fake_instructions_ = new uint32_t[counters_size]; - std::fill(fake_instructions_, fake_instructions_ + counters_size, 0); + fake_instructions_ = new uint32_t[diff_num_counters]; + std::fill(fake_instructions_, fake_instructions_ + diff_num_counters, 0); // Never deallocated, see above. - pc_entries_ = new PCTableEntry[counters_size]; - for (std::size_t i = 0; i < counters_size; ++i) { + pc_entries_ = new PCTableEntry[diff_num_counters]; + for (std::size_t i = 0; i < diff_num_counters; ++i) { pc_entries_[i].PC = reinterpret_cast(fake_instructions_ + i); // TODO: Label Java PCs corresponding to functions as such. pc_entries_[i].PCFlags = 0; } - - // Register the first batch of coverage counters. - RegisterNewCoverageCounters(env, nullptr); -} - -void JNICALL CoverageTracker::RegisterNewCoverageCounters(JNIEnv &env, - jclass cls) { - jclass coverage_map = env.FindClass(kCoverageMapClass); - AssertNoException(env); - jfieldID counters_buffer_id = - env.GetStaticFieldID(coverage_map, "counters", - absl::StrFormat("L%s;", kByteBufferClass).c_str()); - AssertNoException(env); - jobject counters_buffer = - env.GetStaticObjectField(coverage_map, counters_buffer_id); - AssertNoException(env); - - jclass byte_buffer = env.FindClass(kByteBufferClass); - AssertNoException(env); - jmethodID byte_buffer_capacity_id = - env.GetMethodID(byte_buffer, "capacity", "()I"); - AssertNoException(env); - jint old_counters_buffer_size = - env.CallIntMethod(counters_buffer, byte_buffer_capacity_id); - AssertNoException(env); - - jint new_counters_buffer_size; - if (old_counters_buffer_size == 0) { - new_counters_buffer_size = kInitialCoverageCountersBufferSize; - } else { - new_counters_buffer_size = 2 * old_counters_buffer_size; - if (new_counters_buffer_size > kMaxCoverageCountersBufferSize) { - throw std::runtime_error( - "Maximal size of the coverage counters buffer exceeded"); - } - } - - jobject new_counters_buffer = env.NewDirectByteBuffer( - static_cast(counters_), new_counters_buffer_size); - AssertNoException(env); - env.SetStaticObjectField(coverage_map, counters_buffer_id, - new_counters_buffer); - AssertNoException(env); - - // Register only the new second half of the counters buffer with libFuzzer. - __sanitizer_cov_8bit_counters_init(counters_ + old_counters_buffer_size, - counters_ + new_counters_buffer_size); - __sanitizer_cov_pcs_init( - (uintptr_t *)(pc_entries_ + old_counters_buffer_size), - (uintptr_t *)(pc_entries_ + new_counters_buffer_size)); -} - -void CoverageTracker::Clear() { - std::fill(counters_, counters_ + kMaxCoverageCountersBufferSize, 0); + __sanitizer_cov_8bit_counters_init(counters_ + old_num_counters, + counters_ + new_num_counters); + __sanitizer_cov_pcs_init((uintptr_t *)(pc_entries_), + (uintptr_t *)(pc_entries_ + diff_num_counters)); } uint8_t *CoverageTracker::GetCoverageCounters() { return counters_; } @@ -214,3 +160,18 @@ std::string CoverageTracker::ComputeCoverage(JNIEnv &env) { return file_coverage; } } // namespace jazzer + +extern "C" { +JNIEXPORT void JNICALL +Java_com_code_1intelligence_jazzer_runtime_CoverageMap_initialize( + JNIEnv &env, jclass cls, jobject buffer) { + ::jazzer::CoverageTracker::Initialize(env, buffer); +} + +JNIEXPORT void JNICALL +Java_com_code_1intelligence_jazzer_runtime_CoverageMap_registerNewCounters( + JNIEnv &env, jclass cls, jint old_num_counters, jint new_num_counters) { + ::jazzer::CoverageTracker::RegisterNewCounters(env, old_num_counters, + new_num_counters); +} +} diff --git a/driver/coverage_tracker.h b/driver/coverage_tracker.h index 5b237de3..cd39969e 100644 --- a/driver/coverage_tracker.h +++ b/driver/coverage_tracker.h @@ -39,14 +39,10 @@ class CoverageTracker : public ExceptionPrinter { static uint32_t *fake_instructions_; static PCTableEntry *pc_entries_; - static void JNICALL RegisterNewCoverageCounters(JNIEnv &env, jclass cls); - public: - static void Setup(JNIEnv &env); - // Clears the coverage counters array manually. It is cleared automatically - // by libFuzzer prior to running the fuzz target, so this function is only - // used in tests. - static void Clear(); + static void Initialize(JNIEnv &env, jobject buffer); + static void RegisterNewCounters(JNIEnv &env, jint old_num_counters, + jint new_num_counters); // Returns the address of the coverage counters array. static uint8_t *GetCoverageCounters(); diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index cf5e8e10..2758a158 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -279,7 +279,7 @@ FuzzTargetRunner::~FuzzTargetRunner() { RunResult FuzzTargetRunner::Run(const uint8_t *data, const std::size_t size) { auto &env = jvm_.GetEnv(); static std::size_t run_count = 0; - if (run_count < 2) { + if (run_count < 2 && FLAGS_hooks) { run_count++; // For the first two runs only, replay the coverage recorded from static // initializers. libFuzzer cleared the coverage map after they ran and could diff --git a/driver/fuzzed_data_provider_test.cpp b/driver/fuzzed_data_provider_test.cpp index 581dfa1d..33ef29ec 100644 --- a/driver/fuzzed_data_provider_test.cpp +++ b/driver/fuzzed_data_provider_test.cpp @@ -29,7 +29,7 @@ DECLARE_string(cp); DECLARE_string(jvm_args); -DECLARE_string(instrumentation_excludes); +DECLARE_bool(hooks); DECLARE_string(target_class); DECLARE_string(target_args); @@ -116,7 +116,7 @@ class FuzzedDataProviderTest : public ::testing::Test { // process, so we set up a single JVM instance for this test binary which gets // destroyed after all tests in this test suite have finished. static void SetUpTestCase() { - FLAGS_instrumentation_excludes = "**"; + FLAGS_hooks = false; using ::bazel::tools::cpp::runfiles::Runfiles; Runfiles* runfiles = Runfiles::CreateForTest(); FLAGS_cp = runfiles->Rlocation(FLAGS_cp); diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index d352f0b8..97f35555 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -111,7 +111,6 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad_jazzer_initialize(JavaVM *vm, exit(1); } jazzer::registerFuzzerCallbacks(*env); - jazzer::CoverageTracker::Setup(*env); jazzer::SignalHandler::Setup(*env); return JNI_VERSION_1_8; } diff --git a/driver/jvm_tooling_test.cpp b/driver/jvm_tooling_test.cpp index f64d2f3a..adef2cd1 100644 --- a/driver/jvm_tooling_test.cpp +++ b/driver/jvm_tooling_test.cpp @@ -175,8 +175,6 @@ TEST_F(JvmToolingTest, FuzzTargetWithInit) { } TEST_F(JvmToolingTest, TestCoverageMap) { - CoverageTracker::Clear(); - // check that after the initial clear the first coverage counter is 0 auto coverage_counters_array = CoverageTracker::GetCoverageCounters(); ASSERT_EQ(0, coverage_counters_array[0]); @@ -187,13 +185,10 @@ TEST_F(JvmToolingTest, TestCoverageMap) { // increase fuzz_target_runner.Run(nullptr, 0); ASSERT_EQ(1, coverage_counters_array[0]); - CoverageTracker::Clear(); - // back to initial state - ASSERT_EQ(0, coverage_counters_array[0]); - // calling the fuzz target twice + // calling the fuzz target twice more fuzz_target_runner.Run(nullptr, 0); fuzz_target_runner.Run(nullptr, 0); - ASSERT_EQ(2, coverage_counters_array[0]); + ASSERT_EQ(3, coverage_counters_array[0]); } } // namespace jazzer -- cgit v1.2.3 From 4e6a7ceae9f96665c85fb0eff926fbe49d4eb515 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sun, 6 Feb 2022 23:14:43 +0100 Subject: Remove checked-in bazelisk and update README Now that we support three different platforms and two architectures, maintaining an in-repo Bazelisk binary for just a single pair doesn't make much sense anymore. Instead, provide clear and up-to-date instructions on how to obtain and use Bazelisk to build Jazzer. Also includes a general overhaul of the "Getting Jazzer" section of the README and updates the Docker image to function without a checked-in Bazelisk. --- README.md | 75 ++++++++++++++++++----------------------------- bazelisk-linux-amd64 | Bin 5283840 -> 0 bytes docker/jazzer/Dockerfile | 8 +++-- 3 files changed, 34 insertions(+), 49 deletions(-) delete mode 100755 bazelisk-linux-amd64 diff --git a/README.md b/README.md index 48b5d5b0..9a91270e 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,7 @@ Jazzer currently supports the following platforms: If you want to learn more about Jazzer and OSS-Fuzz, [watch the FuzzCon 2020 talk](https://www.youtube.com/watch?v=SmH3Ys_k8vA&list=PLI0R_0_8-TV55gJU-UXrOzZoPbVOj1CW6&index=3) by [Abhishek Arya](https://twitter.com/infernosec) and [Fabian Meumertzheim](https://twitter.com/fhenneke). -## Installation - -The preferred way to install Jazzer is to compile it from source using [Bazel](https://bazel.build), but binary distributions for x64 Linux as well as a Docker image are also available. -Note that these binaries might be outdated as Jazzer follows the "Live at Head" philosophy - you should be able to just checkout the latest commit from `main` and build it. - -Support for Jazzer has recently been added to [rules_fuzzing](https://github.com/bazelbuild/rules_fuzzing), the official Bazel rules for fuzzing. See their README for instructions on how to use Jazzer in a Java Bazel project. +## Getting Jazzer ### Using Docker @@ -40,54 +35,35 @@ docker run -v path/containing/the/application:/fuzzing cifuzz/jazzer If Jazzer produces a finding, the input that triggered it will be available in the same directory. -### Using Bazel +### Compiling with Bazel + +#### Dependencies Jazzer has the following dependencies when being built from source: +* Bazel 4 or later * JDK 8 or later (e.g. [OpenJDK](https://openjdk.java.net/)) * [Clang](https://clang.llvm.org/) and [LLD](https://lld.llvm.org/) 9.0 or later (using a recent version is strongly recommended) -#### Linux - -Jazzer uses [Bazelisk](https://github.com/bazelbuild/bazelisk) to automatically download and install Bazel on Linux. -Building Jazzer from source and running it thus only requires the following assuming the dependencies are installed: - -```bash -git clone https://github.com/CodeIntelligenceTesting/jazzer -cd jazzer -# Note the double dash used to pass to Jazzer rather than Bazel. -./bazelisk-linux-amd64 run //:jazzer -- -``` - -If you prefer to build binaries that can be run without Bazel, use the following command to build your own archive with release binaries: - -```bash -$ ./bazelisk-linux-amd64 build //:jazzer_release -... -INFO: Found 1 target... -Target //:jazzer_release up-to-date: - bazel-bin/jazzer_release.tar.gz -... -``` - -This will print the path of a `jazzer_release.tar.gz` archive that contains the same binaries that would be part of a release. +It is recommended to use [Bazelisk](https://github.com/bazelbuild/bazelisk) to automatically download and install Bazel. +Simply download the release binary for your OS and architecture and ensure that it is available in the `PATH`. +The instructions below will assume that this binary is called `bazel` - Bazelisk is a thin wrapper around the actual Bazel binary and can be used interchangeably. -#### macOS +#### Compilation -Since Jazzer does not ship the macOS version of [Bazelisk](https://github.com/bazelbuild/bazelisk), a tool that automatically downloads and installs the correct version of Bazel, download [the most recent release](https://github.com/bazelbuild/bazelisk/releases) of `bazelisk-darwin`. -Afterwards, clone Jazzer and run it via: +Assuming the dependencies are installed, build Jazzer from source as follows: ```bash -git clone https://github.com/CodeIntelligenceTesting/jazzer -cd jazzer +$ git clone https://github.com/CodeIntelligenceTesting/jazzer +$ cd jazzer # Note the double dash used to pass to Jazzer rather than Bazel. -/path/to/bazelisk-darwin run //:jazzer -- +$ bazel run //:jazzer -- ``` If you prefer to build binaries that can be run without Bazel, use the following command to build your own archive with release binaries: ```bash -$ /path/to/bazelisk-darwin build //:jazzer_release +$ bazel build //:jazzer_release ... INFO: Found 1 target... Target //:jazzer_release up-to-date: @@ -97,20 +73,27 @@ Target //:jazzer_release up-to-date: This will print the path of a `jazzer_release.tar.gz` archive that contains the same binaries that would be part of a release. +##### macOS + The build may fail with the clang shipped with Xcode. If you encounter issues during the build, add `--config=toolchain` right after `run` or `build` in the `bazelisk` commands above to use a checked-in toolchain that is known to work. +Alternatively, manually install LLVM and set `CC` to the path of LLVM clang. + +#### rules_fuzzing + +Support for Jazzer has recently been added to [rules_fuzzing](https://github.com/bazelbuild/rules_fuzzing), the official Bazel rules for fuzzing. +See their README for instructions on how to use Jazzer in a Java Bazel project. ### Using the provided binaries -Binary releases are available under [Releases](https://github.com/CodeIntelligenceTesting/jazzer/releases) and are built -using an [LLVM 11 Bazel toolchain](https://github.com/CodeIntelligenceTesting/llvm-toolchain). +Binary releases are available under [Releases](https://github.com/CodeIntelligenceTesting/jazzer/releases), +but do not always include the latest changes. The binary distributions of Jazzer consist of the following components: -- `jazzer_driver` - native binary that interfaces between libFuzzer and the JVM fuzz target -- `jazzer_agent_deploy.jar` - Java agent that performs bytecode instrumentation and tracks coverage +- `jazzer` - main binary +- `jazzer_agent_deploy.jar` - Java agent that performs bytecode instrumentation and tracks coverage (automatically loaded by `jazzer`) - `jazzer_api_deploy.jar` - contains convenience methods for creating fuzz targets and defining custom hooks -- `jazzer` - convenience shell script that runs the Jazzer driver with the local JRE shared libraries added to `LD_LIBRARY_PATH` The additional release artifact `examples_deploy.jar` contains most of the examples and can be used to run them without having to build them (see Examples below). @@ -129,8 +112,8 @@ Multiple examples for instructive and real-world Jazzer fuzz targets can be foun A toy example can be run as follows: ```bash -# Using Bazelisk: -./bazelisk-linux-amd64 run //examples:ExampleFuzzer +# Using Bazel: +bazel run //examples:ExampleFuzzer # Using the binary release and examples_deploy.jar: ./jazzer --cp=examples_deploy.jar ``` @@ -514,7 +497,7 @@ pre-loading the mutator library: ```bash # Using Bazel: -LD_PRELOAD=libcustom_mutator.so ./bazelisk-linux-amd64 run //:jazzer -- +LD_PRELOAD=libcustom_mutator.so bazel run //:jazzer -- # Using the binary release: LD_PRELOAD=libcustom_mutator.so ./jazzer ``` diff --git a/bazelisk-linux-amd64 b/bazelisk-linux-amd64 deleted file mode 100755 index 22e49af1..00000000 Binary files a/bazelisk-linux-amd64 and /dev/null differ diff --git a/docker/jazzer/Dockerfile b/docker/jazzer/Dockerfile index 56787be7..7b788d8d 100644 --- a/docker/jazzer/Dockerfile +++ b/docker/jazzer/Dockerfile @@ -15,16 +15,18 @@ FROM ubuntu:20.04 AS builder ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install -y git python3 python-is-python3 openjdk-11-jdk-headless +RUN apt-get update && apt-get install -y curl git python3 python-is-python3 openjdk-11-jdk-headless WORKDIR /root -RUN git clone --depth=1 https://github.com/CodeIntelligenceTesting/jazzer.git && \ +RUN curl -L https://github.com/bazelbuild/bazelisk/releases/download/v1.11.0/bazelisk-linux-amd64 -o /usr/bin/bazelisk && \ + chmod +x /usr/bin/bazelisk && \ + git clone --depth=1 https://github.com/CodeIntelligenceTesting/jazzer.git && \ cd jazzer && \ # The LLVM toolchain requires ld and ld.gold to exist, but does not use them. touch /usr/bin/ld && \ touch /usr/bin/ld.gold && \ BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 \ - ./bazelisk-linux-amd64 build --config=toolchain --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-linux \ + bazelisk build --config=toolchain --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-linux \ //agent:jazzer_agent_deploy.jar //driver:jazzer_driver FROM gcr.io/distroless/java -- cgit v1.2.3 From cfdad993a9dbe01f1477dc8ef3b1121a2a764d80 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sun, 6 Feb 2022 23:04:05 +0100 Subject: Add an UnsafeProvider Will be used in an Unsafe-based CoverageMap implementation. --- .../code_intelligence/jazzer/runtime/BUILD.bazel | 5 +++ .../jazzer/runtime/UnsafeProvider.java | 50 ++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 agent/src/main/java/com/code_intelligence/jazzer/runtime/UnsafeProvider.java 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 c7a78071..1741ede1 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 @@ -19,6 +19,11 @@ java_library( srcs = ["SignalHandler.java"], ) +java_library( + name = "unsafe_provider", + srcs = ["UnsafeProvider.java"], +) + kt_jvm_library( name = "runtime", srcs = [ diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/UnsafeProvider.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/UnsafeProvider.java new file mode 100644 index 00000000..62be96e8 --- /dev/null +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/UnsafeProvider.java @@ -0,0 +1,50 @@ +// Copyright 2022 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.runtime; + +import java.lang.reflect.Field; +import sun.misc.Unsafe; + +public final class UnsafeProvider { + private static final Unsafe unsafe = getUnsafeInternal(); + + public static Unsafe getUnsafe() { + return unsafe; + } + + private static Unsafe getUnsafeInternal() { + try { + // The Java agent is loaded by the bootstrap class loader and should thus + // pass the security checks in getUnsafe. + return Unsafe.getUnsafe(); + } catch (Throwable unused) { + // If not running as an agent, use the classical reflection trick to get an Unsafe instance, + // taking into account that the private field may have a name other than "theUnsafe": + // https://android.googlesource.com/platform/libcore/+/gingerbread/luni/src/main/java/sun/misc/Unsafe.java#32 + try { + for (Field f : Unsafe.class.getDeclaredFields()) { + if (f.getType() == Unsafe.class) { + f.setAccessible(true); + return (Unsafe) f.get(null); + } + } + return null; + } catch (Throwable t) { + t.printStackTrace(); + return null; + } + } + } +} -- cgit v1.2.3 From d40b6e8a55d2823affcd6f8ef5bb950420c65c1c Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 28 Jan 2022 14:49:51 +0100 Subject: Benchmark EdgeCoverageStrategies in isolation --- .../jazzer/instrumentor/BUILD.bazel | 29 +++++ .../CoverageInstrumentationBenchmark.java | 130 +++++++++++++++++++++ .../instrumentor/DirectByteBufferCoverageMap.java | 27 +++++ .../jazzer/instrumentor/BUILD.bazel | 1 + maven.bzl | 2 + maven_install.json | 60 +++++++++- 6 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferCoverageMap.java diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel new file mode 100644 index 00000000..d2c9d965 --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -0,0 +1,29 @@ +java_binary( + name = "CoverageInstrumentationBenchmark", + srcs = [ + "CoverageInstrumentationBenchmark.java", + ], + main_class = "org.openjdk.jmh.Main", + plugins = [":JmhGeneratorAnnotationProcessor"], + runtime_deps = [ + "@maven//:com_mikesamuel_json_sanitizer", + ], + deps = [ + ":strategies", + "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor", + "@maven//:org_openjdk_jmh_jmh_core", + ], +) + +java_library( + name = "strategies", + srcs = [ + "DirectByteBufferCoverageMap.java", + ], +) + +java_plugin( + name = "JmhGeneratorAnnotationProcessor", + processor_class = "org.openjdk.jmh.generators.BenchmarkProcessor", + deps = ["@maven//:org_openjdk_jmh_jmh_generator_annprocess"], +) diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java new file mode 100644 index 00000000..f8609982 --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java @@ -0,0 +1,130 @@ +// Copyright 2022 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.instrumentor; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +/** + * This benchmark compares the throughput of a typical fuzz target when instrumented with different + * edge coverage instrumentation strategies and coverage map implementations. + * + * The benchmark currently uses the OWASP json-sanitizer as its target, which has the following + * desirable properties for a benchmark: + * - It is a reasonably sized project that does not consist of many different classes. + * - It is very heavy on computation with a high density of branching. + * - It is entirely CPU bound with no IO and does not call expensive methods from the standard + * library. + * With these properties, results obtained from this benchmark should provide reasonable lower + * bounds on the relative slowdown introduced by the various approaches to instrumentations. + */ +@State(Scope.Benchmark) +public class CoverageInstrumentationBenchmark { + private static final String TARGET_CLASSNAME = "com.google.json.JsonSanitizer"; + private static final String TARGET_PACKAGE = + TARGET_CLASSNAME.substring(0, TARGET_CLASSNAME.lastIndexOf('.')); + private static final String TARGET_METHOD = "sanitize"; + private static final MethodType TARGET_TYPE = MethodType.methodType(String.class, String.class); + + // This is part of the benchmark's state and not a constant to prevent constant folding. + String TARGET_ARG = + "{\"foo\":1123987,\"bar\":[true, false],\"baz\":{\"foo\":\"132ä3\",\"bar\":1.123e-005}}"; + + MethodHandle uninstrumented_sanitize; + MethodHandle local_DirectByteBuffer_NeverZero_sanitize; + + public static MethodHandle handleForTargetMethod(ClassLoader classLoader) + throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException { + Class targetClass = classLoader.loadClass(TARGET_CLASSNAME); + return MethodHandles.lookup().findStatic(targetClass, TARGET_METHOD, TARGET_TYPE); + } + + public static MethodHandle instrumentWithStrategy( + EdgeCoverageStrategy strategy, Class coverageMapClass) + throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException { + if (strategy == null) { + // Do not instrument the code by using the benchmark class' ClassLoader. + return handleForTargetMethod(CoverageInstrumentationBenchmark.class.getClassLoader()); + } + // It's fine to reuse a single instrumentor here as we don't want to know which class received + // how many counters. + Instrumentor instrumentor = new EdgeCoverageInstrumentor(strategy, coverageMapClass, 0); + return handleForTargetMethod(new InstrumentingClassLoader(instrumentor, TARGET_PACKAGE)); + } + + @Setup + public void instrumentWithStrategies() + throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException { + uninstrumented_sanitize = instrumentWithStrategy(null, null); + local_DirectByteBuffer_NeverZero_sanitize = instrumentWithStrategy( + DirectByteBufferStrategy.INSTANCE, DirectByteBufferCoverageMap.class); + } + + @Benchmark + public String uninstrumented() throws Throwable { + return (String) uninstrumented_sanitize.invokeExact(TARGET_ARG); + } + + @Benchmark + public String local_DirectByteBuffer_NeverZero() throws Throwable { + return (String) local_DirectByteBuffer_NeverZero_sanitize.invokeExact(TARGET_ARG); + } +} + +class InstrumentingClassLoader extends ClassLoader { + private final Instrumentor instrumentor; + private final String classNamePrefix; + + InstrumentingClassLoader(Instrumentor instrumentor, String packageToInstrument) { + super(InstrumentingClassLoader.class.getClassLoader()); + this.instrumentor = instrumentor; + this.classNamePrefix = packageToInstrument + "."; + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + if (!name.startsWith(classNamePrefix)) { + return super.loadClass(name); + } + try (InputStream stream = super.getResourceAsStream(name.replace('.', '/') + ".class")) { + if (stream == null) { + throw new ClassNotFoundException(String.format("Failed to find class file for %s", name)); + } + byte[] bytecode = readAllBytes(stream); + byte[] instrumentedBytecode = instrumentor.instrument(bytecode); + return defineClass(name, instrumentedBytecode, 0, instrumentedBytecode.length); + } catch (IOException e) { + throw new ClassNotFoundException(String.format("Failed to read class file for %s", name), e); + } + } + + private static byte[] readAllBytes(InputStream in) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[64 * 104 * 1024]; + int read; + while ((read = in.read(buffer)) != -1) { + out.write(buffer, 0, read); + } + return out.toByteArray(); + } +} diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferCoverageMap.java b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferCoverageMap.java new file mode 100644 index 00000000..6a143991 --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferCoverageMap.java @@ -0,0 +1,27 @@ +// Copyright 2022 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.instrumentor; + +import java.nio.ByteBuffer; + +public final class DirectByteBufferCoverageMap { + // The current target, JsonSanitizer, uses less than 2048 coverage counters. + private static final int NUM_COUNTERS = 4096; + public static final ByteBuffer counters = ByteBuffer.allocateDirect(NUM_COUNTERS); + + public static void enlargeIfNeeded(int nextId) { + // Statically sized counters buffer. + } +} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel index f8ea25e9..5494c1a0 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -17,6 +17,7 @@ kt_jvm_library( "TraceDataFlowInstrumentor.kt", ], visibility = [ + "//agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor:__pkg__", "//agent/src/main/java/com/code_intelligence/jazzer/agent:__pkg__", "//agent/src/test/java/com/code_intelligence/jazzer/instrumentor:__pkg__", ], diff --git a/maven.bzl b/maven.bzl index 29f38451..c7fa4c81 100644 --- a/maven.bzl +++ b/maven.bzl @@ -21,6 +21,8 @@ JAZZER_API_COORDINATES = "com.code-intelligence:jazzer-api:%s" % JAZZER_API_VERS # Jazzer finds these issues. DO NOT USE. MAVEN_ARTIFACTS = [ "junit:junit:4.12", + "org.openjdk.jmh:jmh-core:1.34", + "org.openjdk.jmh:jmh-generator-annprocess:1.34", "org.apache.commons:commons-imaging:1.0-alpha2", "com.mikesamuel:json-sanitizer:1.2.1", "com.google.code.gson:gson:2.8.6", diff --git a/maven_install.json b/maven_install.json index 16303c03..bb140da6 100644 --- a/maven_install.json +++ b/maven_install.json @@ -1,8 +1,8 @@ { "dependency_tree": { "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL", - "__INPUT_ARTIFACTS_HASH": 993215468, - "__RESOLVED_ARTIFACTS_HASH": 40706841, + "__INPUT_ARTIFACTS_HASH": -730060225, + "__RESOLVED_ARTIFACTS_HASH": 1247615742, "conflict_resolution": {}, "dependencies": [ { @@ -188,6 +188,17 @@ "sha256": "59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a", "url": "https://repo1.maven.org/maven2/junit/junit/4.12/junit-4.12.jar" }, + { + "coord": "net.sf.jopt-simple:jopt-simple:5.0.4", + "dependencies": [], + "directDependencies": [], + "file": "v1/https/repo1.maven.org/maven2/net/sf/jopt-simple/jopt-simple/5.0.4/jopt-simple-5.0.4.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/net/sf/jopt-simple/jopt-simple/5.0.4/jopt-simple-5.0.4.jar" + ], + "sha256": "df26cc58f235f477db07f753ba5a3ab243ebe5789d9f89ecf68dd62ea9a66c28", + "url": "https://repo1.maven.org/maven2/net/sf/jopt-simple/jopt-simple/5.0.4/jopt-simple-5.0.4.jar" + }, { "coord": "org.apache.commons:commons-imaging:1.0-alpha2", "dependencies": [], @@ -199,6 +210,17 @@ "sha256": "64d649007364d70dcab24a1f895646e6976f5e2b339ba73a4af20642d041666a", "url": "https://repo1.maven.org/maven2/org/apache/commons/commons-imaging/1.0-alpha2/commons-imaging-1.0-alpha2.jar" }, + { + "coord": "org.apache.commons:commons-math3:3.2", + "dependencies": [], + "directDependencies": [], + "file": "v1/https/repo1.maven.org/maven2/org/apache/commons/commons-math3/3.2/commons-math3-3.2.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/apache/commons/commons-math3/3.2/commons-math3-3.2.jar" + ], + "sha256": "6268a9a0ea3e769fc493a21446664c0ef668e48c93d126791f6f3f757978fee2", + "url": "https://repo1.maven.org/maven2/org/apache/commons/commons-math3/3.2/commons-math3-3.2.jar" + }, { "coord": "org.apache.logging.log4j:log4j-api:2.14.1", "dependencies": [], @@ -321,6 +343,40 @@ ], "sha256": "ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478", "url": "https://repo1.maven.org/maven2/org/jetbrains/annotations/13.0/annotations-13.0.jar" + }, + { + "coord": "org.openjdk.jmh:jmh-core:1.34", + "dependencies": [ + "net.sf.jopt-simple:jopt-simple:5.0.4", + "org.apache.commons:commons-math3:3.2" + ], + "directDependencies": [ + "net.sf.jopt-simple:jopt-simple:5.0.4", + "org.apache.commons:commons-math3:3.2" + ], + "file": "v1/https/repo1.maven.org/maven2/org/openjdk/jmh/jmh-core/1.34/jmh-core-1.34.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/openjdk/jmh/jmh-core/1.34/jmh-core-1.34.jar" + ], + "sha256": "904384762d2ffeca8005aa9b432a7891a0e60c888bfd36f61dfcfa97c3a1d1b3", + "url": "https://repo1.maven.org/maven2/org/openjdk/jmh/jmh-core/1.34/jmh-core-1.34.jar" + }, + { + "coord": "org.openjdk.jmh:jmh-generator-annprocess:1.34", + "dependencies": [ + "net.sf.jopt-simple:jopt-simple:5.0.4", + "org.openjdk.jmh:jmh-core:1.34", + "org.apache.commons:commons-math3:3.2" + ], + "directDependencies": [ + "org.openjdk.jmh:jmh-core:1.34" + ], + "file": "v1/https/repo1.maven.org/maven2/org/openjdk/jmh/jmh-generator-annprocess/1.34/jmh-generator-annprocess-1.34.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/openjdk/jmh/jmh-generator-annprocess/1.34/jmh-generator-annprocess-1.34.jar" + ], + "sha256": "aa0feeefc0da59427b14c50139cba6deba211750e0033fdc39a5b3b8008b2900", + "url": "https://repo1.maven.org/maven2/org/openjdk/jmh/jmh-generator-annprocess/1.34/jmh-generator-annprocess-1.34.jar" } ], "version": "0.1.0" -- cgit v1.2.3 From cd2b0e49d3497eef381c843e1a548fe75e049531 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 28 Jan 2022 15:06:13 +0100 Subject: Benchmark a DirectByteBuffer strategy using a static method This strategy uses a static method call instead of inlined coverage counter update logic, which greatly simplifies the complexity of the injected bytecode. --- .../jazzer/instrumentor/BUILD.bazel | 6 +++ .../CoverageInstrumentationBenchmark.java | 8 ++++ .../instrumentor/DirectByteBufferCoverageMap.java | 9 ++++ .../jazzer/instrumentor/StaticMethodStrategy.java | 48 ++++++++++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/StaticMethodStrategy.java diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel index d2c9d965..8c19c01e 100644 --- a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -19,6 +19,12 @@ java_library( name = "strategies", srcs = [ "DirectByteBufferCoverageMap.java", + "StaticMethodStrategy.java", + ], + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor", + "@jazzer_jacoco//:jacoco_internal", + "@jazzer_ow2_asm//:asm", ], ) diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java index f8609982..5b051b5c 100644 --- a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java @@ -52,6 +52,7 @@ public class CoverageInstrumentationBenchmark { MethodHandle uninstrumented_sanitize; MethodHandle local_DirectByteBuffer_NeverZero_sanitize; + MethodHandle staticMethod_DirectByteBuffer_NeverZero_sanitize; public static MethodHandle handleForTargetMethod(ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException { @@ -78,6 +79,8 @@ public class CoverageInstrumentationBenchmark { uninstrumented_sanitize = instrumentWithStrategy(null, null); local_DirectByteBuffer_NeverZero_sanitize = instrumentWithStrategy( DirectByteBufferStrategy.INSTANCE, DirectByteBufferCoverageMap.class); + staticMethod_DirectByteBuffer_NeverZero_sanitize = + instrumentWithStrategy(new StaticMethodStrategy(), DirectByteBufferCoverageMap.class); } @Benchmark @@ -89,6 +92,11 @@ public class CoverageInstrumentationBenchmark { public String local_DirectByteBuffer_NeverZero() throws Throwable { return (String) local_DirectByteBuffer_NeverZero_sanitize.invokeExact(TARGET_ARG); } + + @Benchmark + public String staticMethod_DirectByteBuffer_NeverZero() throws Throwable { + return (String) staticMethod_DirectByteBuffer_NeverZero_sanitize.invokeExact(TARGET_ARG); + } } class InstrumentingClassLoader extends ClassLoader { diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferCoverageMap.java b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferCoverageMap.java index 6a143991..e5e66abb 100644 --- a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferCoverageMap.java +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferCoverageMap.java @@ -24,4 +24,13 @@ public final class DirectByteBufferCoverageMap { public static void enlargeIfNeeded(int nextId) { // Statically sized counters buffer. } + + public static void recordCoverage(final int id) { + final byte counter = counters.get(id); + if (counter == -1) { + counters.put(id, (byte) 1); + } else { + counters.put(id, (byte) (counter + 1)); + } + } } diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/StaticMethodStrategy.java b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/StaticMethodStrategy.java new file mode 100644 index 00000000..ca0bd3d7 --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/StaticMethodStrategy.java @@ -0,0 +1,48 @@ +// Copyright 2022 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.instrumentor; + +import org.jacoco.core.internal.instr.InstrSupport; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class StaticMethodStrategy implements EdgeCoverageStrategy { + @Override + public void instrumentControlFlowEdge( + MethodVisitor mv, int edgeId, int variable, String coverageMapInternalClassName) { + InstrSupport.push(mv, edgeId); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, coverageMapInternalClassName, "recordCoverage", "(I)V", false); + } + + @Override + public int getInstrumentControlFlowEdgeStackSize() { + return 1; + } + + @Override + public Object getLocalVariableType() { + return null; + } + + @Override + public void loadLocalVariable( + MethodVisitor mv, int variable, String coverageMapInternalClassName) {} + + @Override + public int getLoadLocalVariableStackSize() { + return 0; + } +} -- cgit v1.2.3 From 66f355248d5be7875aa23d6d259b6689a5364a70 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 28 Jan 2022 15:16:28 +0100 Subject: Benchmark an Unsafe strategy using a static method This strategy uses a static method call instead of inlined coverage counter update logic, which greatly simplifies the complexity of the injected bytecode. Instead of a DirectByteBuffer, it uses Unsafe to circumvent bounds checks. --- .../jazzer/instrumentor/BUILD.bazel | 1 + .../CoverageInstrumentationBenchmark.java | 8 +++ .../jazzer/instrumentor/UnsafeCoverageMap.java | 59 ++++++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeCoverageMap.java diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel index 8c19c01e..bd9dfcc4 100644 --- a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -20,6 +20,7 @@ java_library( srcs = [ "DirectByteBufferCoverageMap.java", "StaticMethodStrategy.java", + "UnsafeCoverageMap.java", ], deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor", diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java index 5b051b5c..4424d303 100644 --- a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java @@ -53,6 +53,7 @@ public class CoverageInstrumentationBenchmark { MethodHandle uninstrumented_sanitize; MethodHandle local_DirectByteBuffer_NeverZero_sanitize; MethodHandle staticMethod_DirectByteBuffer_NeverZero_sanitize; + MethodHandle staticMethod_Unsafe_NeverZero_sanitize; public static MethodHandle handleForTargetMethod(ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException { @@ -81,6 +82,8 @@ public class CoverageInstrumentationBenchmark { DirectByteBufferStrategy.INSTANCE, DirectByteBufferCoverageMap.class); staticMethod_DirectByteBuffer_NeverZero_sanitize = instrumentWithStrategy(new StaticMethodStrategy(), DirectByteBufferCoverageMap.class); + staticMethod_Unsafe_NeverZero_sanitize = + instrumentWithStrategy(new StaticMethodStrategy(), UnsafeCoverageMap.class); } @Benchmark @@ -97,6 +100,11 @@ public class CoverageInstrumentationBenchmark { public String staticMethod_DirectByteBuffer_NeverZero() throws Throwable { return (String) staticMethod_DirectByteBuffer_NeverZero_sanitize.invokeExact(TARGET_ARG); } + + @Benchmark + public String staticMethod_Unsafe_NeverZero() throws Throwable { + return (String) staticMethod_Unsafe_NeverZero_sanitize.invokeExact(TARGET_ARG); + } } class InstrumentingClassLoader extends ClassLoader { diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeCoverageMap.java b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeCoverageMap.java new file mode 100644 index 00000000..cf73928d --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeCoverageMap.java @@ -0,0 +1,59 @@ +// Copyright 2022 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.instrumentor; + +import java.lang.reflect.Field; +import sun.misc.Unsafe; + +public final class UnsafeCoverageMap { + private static final Unsafe UNSAFE; + + static { + Unsafe unsafe; + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + unsafe = (Unsafe) f.get(null); + } catch (IllegalAccessException | NoSuchFieldException e) { + e.printStackTrace(); + System.exit(1); + // Not reached. + unsafe = null; + } + UNSAFE = unsafe; + } + + // The current target, JsonSanitizer, uses less than 2048 coverage counters. + private static final long NUM_COUNTERS = 4096; + private static final long countersAddress = UNSAFE.allocateMemory(NUM_COUNTERS); + + static { + UNSAFE.setMemory(countersAddress, NUM_COUNTERS, (byte) 0); + } + + public static void enlargeIfNeeded(int nextId) { + // Statically sized counters buffer. + } + + public static void recordCoverage(final int id) { + final long address = countersAddress + id; + final byte counter = UNSAFE.getByte(address); + if (counter == -1) { + UNSAFE.putByte(address, (byte) 1); + } else { + UNSAFE.putByte(address, (byte) (counter + 1)); + } + } +} -- cgit v1.2.3 From c5dce3937772c9d703db557a5164df7aa565f6a5 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sun, 30 Jan 2022 09:13:19 +0100 Subject: Run a light version of the benchmark as a test --- .../jazzer/instrumentor/BUILD.bazel | 32 ++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel index bd9dfcc4..4e417ef5 100644 --- a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -1,9 +1,37 @@ java_binary( name = "CoverageInstrumentationBenchmark", - srcs = [ - "CoverageInstrumentationBenchmark.java", + main_class = "org.openjdk.jmh.Main", + runtime_deps = [ + ":coverage_instrumentation_benchmark", + ], +) + +java_test( + name = "CoverageInstrumentationBenchmarkTest", + args = [ + # Fail fast on any exceptions produced by benchmarks. + "-foe true", + "-wf 1", + "-f 1", + "-wi 1", + "-i 1", + "-r 1s", + "-w 1s", + ], + jvm_flags = [ + "-XX:CompileCommand=print,*CoverageMap.recordCoverage", ], main_class = "org.openjdk.jmh.Main", + # Directly invoke JMH's main without using a testrunner. + use_testrunner = False, + runtime_deps = [ + ":coverage_instrumentation_benchmark", + ], +) + +java_library( + name = "coverage_instrumentation_benchmark", + srcs = ["CoverageInstrumentationBenchmark.java"], plugins = [":JmhGeneratorAnnotationProcessor"], runtime_deps = [ "@maven//:com_mikesamuel_json_sanitizer", -- cgit v1.2.3 From 2735c60232d1c536cfd72ffc0d89a38647a66ba4 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 31 Jan 2022 23:15:08 +0100 Subject: Add more coverage map versions to the benchmark --- .../jazzer/instrumentor/BUILD.bazel | 4 ++ .../CoverageInstrumentationBenchmark.java | 32 +++++++++++++ .../instrumentor/DirectByteBuffer2CoverageMap.java | 32 +++++++++++++ .../jazzer/instrumentor/Unsafe2CoverageMap.java | 55 ++++++++++++++++++++++ .../instrumentor/UnsafeBranchfreeCoverageMap.java | 55 ++++++++++++++++++++++ .../UnsafeSimpleIncrementCoverageMap.java | 54 +++++++++++++++++++++ 6 files changed, 232 insertions(+) create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBuffer2CoverageMap.java create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/Unsafe2CoverageMap.java create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeBranchfreeCoverageMap.java create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeSimpleIncrementCoverageMap.java diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel index 4e417ef5..e8858f0f 100644 --- a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -46,9 +46,13 @@ java_library( java_library( name = "strategies", srcs = [ + "DirectByteBuffer2CoverageMap.java", "DirectByteBufferCoverageMap.java", "StaticMethodStrategy.java", + "Unsafe2CoverageMap.java", + "UnsafeBranchfreeCoverageMap.java", "UnsafeCoverageMap.java", + "UnsafeSimpleIncrementCoverageMap.java", ], deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor", diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java index 4424d303..f388c4cc 100644 --- a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationBenchmark.java @@ -53,7 +53,11 @@ public class CoverageInstrumentationBenchmark { MethodHandle uninstrumented_sanitize; MethodHandle local_DirectByteBuffer_NeverZero_sanitize; MethodHandle staticMethod_DirectByteBuffer_NeverZero_sanitize; + MethodHandle staticMethod_DirectByteBuffer2_NeverZero_sanitize; MethodHandle staticMethod_Unsafe_NeverZero_sanitize; + MethodHandle staticMethod_Unsafe_NeverZero2_sanitize; + MethodHandle staticMethod_Unsafe_NeverZeroBranchfree_sanitize; + MethodHandle staticMethod_Unsafe_SimpleIncrement_sanitize; public static MethodHandle handleForTargetMethod(ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException { @@ -82,8 +86,16 @@ public class CoverageInstrumentationBenchmark { DirectByteBufferStrategy.INSTANCE, DirectByteBufferCoverageMap.class); staticMethod_DirectByteBuffer_NeverZero_sanitize = instrumentWithStrategy(new StaticMethodStrategy(), DirectByteBufferCoverageMap.class); + staticMethod_DirectByteBuffer2_NeverZero_sanitize = + instrumentWithStrategy(new StaticMethodStrategy(), DirectByteBuffer2CoverageMap.class); staticMethod_Unsafe_NeverZero_sanitize = instrumentWithStrategy(new StaticMethodStrategy(), UnsafeCoverageMap.class); + staticMethod_Unsafe_NeverZero2_sanitize = + instrumentWithStrategy(new StaticMethodStrategy(), Unsafe2CoverageMap.class); + staticMethod_Unsafe_SimpleIncrement_sanitize = + instrumentWithStrategy(new StaticMethodStrategy(), UnsafeSimpleIncrementCoverageMap.class); + staticMethod_Unsafe_NeverZeroBranchfree_sanitize = + instrumentWithStrategy(new StaticMethodStrategy(), UnsafeBranchfreeCoverageMap.class); } @Benchmark @@ -101,10 +113,30 @@ public class CoverageInstrumentationBenchmark { return (String) staticMethod_DirectByteBuffer_NeverZero_sanitize.invokeExact(TARGET_ARG); } + @Benchmark + public String staticMethod_DirectByteBuffer2_NeverZero() throws Throwable { + return (String) staticMethod_DirectByteBuffer2_NeverZero_sanitize.invokeExact(TARGET_ARG); + } + @Benchmark public String staticMethod_Unsafe_NeverZero() throws Throwable { return (String) staticMethod_Unsafe_NeverZero_sanitize.invokeExact(TARGET_ARG); } + + @Benchmark + public String staticMethod_Unsafe_NeverZero2() throws Throwable { + return (String) staticMethod_Unsafe_NeverZero2_sanitize.invokeExact(TARGET_ARG); + } + + @Benchmark + public String staticMethod_Unsafe_SimpleIncrement() throws Throwable { + return (String) staticMethod_Unsafe_SimpleIncrement_sanitize.invokeExact(TARGET_ARG); + } + + @Benchmark + public String staticMethod_Unsafe_NeverZeroBranchfree() throws Throwable { + return (String) staticMethod_Unsafe_NeverZeroBranchfree_sanitize.invokeExact(TARGET_ARG); + } } class InstrumentingClassLoader extends ClassLoader { diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBuffer2CoverageMap.java b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBuffer2CoverageMap.java new file mode 100644 index 00000000..c57babb5 --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBuffer2CoverageMap.java @@ -0,0 +1,32 @@ +// Copyright 2022 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.instrumentor; + +import java.nio.ByteBuffer; + +public final class DirectByteBuffer2CoverageMap { + // The current target, JsonSanitizer, uses less than 2048 coverage counters. + private static final int NUM_COUNTERS = 4096; + public static final ByteBuffer counters = ByteBuffer.allocateDirect(NUM_COUNTERS); + + public static void enlargeIfNeeded(int nextId) { + // Statically sized counters buffer. + } + + public static void recordCoverage(final int id) { + final byte counter = counters.get(id); + counters.put(id, (byte) (counter == -1 ? 1 : counter + 1)); + } +} diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/Unsafe2CoverageMap.java b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/Unsafe2CoverageMap.java new file mode 100644 index 00000000..030d9a95 --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/Unsafe2CoverageMap.java @@ -0,0 +1,55 @@ +// Copyright 2022 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.instrumentor; + +import java.lang.reflect.Field; +import sun.misc.Unsafe; + +public final class Unsafe2CoverageMap { + private static final Unsafe UNSAFE; + + static { + Unsafe unsafe; + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + unsafe = (Unsafe) f.get(null); + } catch (IllegalAccessException | NoSuchFieldException e) { + e.printStackTrace(); + System.exit(1); + // Not reached. + unsafe = null; + } + UNSAFE = unsafe; + } + + // The current target, JsonSanitizer, uses less than 2048 coverage counters. + private static final long NUM_COUNTERS = 4096; + private static final long countersAddress = UNSAFE.allocateMemory(NUM_COUNTERS); + + static { + UNSAFE.setMemory(countersAddress, NUM_COUNTERS, (byte) 0); + } + + public static void enlargeIfNeeded(int nextId) { + // Statically sized counters buffer. + } + + public static void recordCoverage(final int id) { + final long address = countersAddress + id; + final byte counter = UNSAFE.getByte(address); + UNSAFE.putByte(address, (byte) (counter == -1 ? 1 : counter + 1)); + } +} diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeBranchfreeCoverageMap.java b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeBranchfreeCoverageMap.java new file mode 100644 index 00000000..3694b95f --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeBranchfreeCoverageMap.java @@ -0,0 +1,55 @@ +// Copyright 2022 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.instrumentor; + +import java.lang.reflect.Field; +import sun.misc.Unsafe; + +public final class UnsafeBranchfreeCoverageMap { + private static final Unsafe UNSAFE; + + static { + Unsafe unsafe; + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + unsafe = (Unsafe) f.get(null); + } catch (IllegalAccessException | NoSuchFieldException e) { + e.printStackTrace(); + System.exit(1); + // Not reached. + unsafe = null; + } + UNSAFE = unsafe; + } + + // The current target, JsonSanitizer, uses less than 2048 coverage counters. + private static final long NUM_COUNTERS = 4096; + private static final long countersAddress = UNSAFE.allocateMemory(NUM_COUNTERS); + + static { + UNSAFE.setMemory(countersAddress, NUM_COUNTERS, (byte) 0); + } + + public static void enlargeIfNeeded(int nextId) { + // Statically sized counters buffer. + } + + public static void recordCoverage(final int id) { + final long address = countersAddress + id; + final int incrementedCounter = UNSAFE.getByte(address) + 1; + UNSAFE.putByte(address, (byte) (incrementedCounter ^ (incrementedCounter >>> 8))); + } +} diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeSimpleIncrementCoverageMap.java b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeSimpleIncrementCoverageMap.java new file mode 100644 index 00000000..60fb8c8d --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/UnsafeSimpleIncrementCoverageMap.java @@ -0,0 +1,54 @@ +// Copyright 2022 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.instrumentor; + +import java.lang.reflect.Field; +import sun.misc.Unsafe; + +public final class UnsafeSimpleIncrementCoverageMap { + private static final Unsafe UNSAFE; + + static { + Unsafe unsafe; + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + unsafe = (Unsafe) f.get(null); + } catch (IllegalAccessException | NoSuchFieldException e) { + e.printStackTrace(); + System.exit(1); + // Not reached. + unsafe = null; + } + UNSAFE = unsafe; + } + + // The current target, JsonSanitizer, uses less than 2048 coverage counters. + private static final long NUM_COUNTERS = 4096; + private static final long countersAddress = UNSAFE.allocateMemory(NUM_COUNTERS); + + static { + UNSAFE.setMemory(countersAddress, NUM_COUNTERS, (byte) 0); + } + + public static void enlargeIfNeeded(int nextId) { + // Statically sized counters buffer. + } + + public static void recordCoverage(final int id) { + final long address = countersAddress + id; + UNSAFE.putByte(address, (byte) (UNSAFE.getByte(address) + 1)); + } +} -- cgit v1.2.3 From 4f5d5b39787389c41284fab26443d37b9dbb57cb Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 8 Feb 2022 12:13:49 +0100 Subject: Do not hardcode ENV_MAX_NUM_COUNTERS --- .../main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java index ad682ca0..d3e6110f 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java @@ -66,8 +66,8 @@ final public class CoverageMap { + " limit the scope of a single fuzz target as much as possible to keep the%n" + " fuzzer fast.%n" + " If that is not possible, the maximum number of counters can be increased%n" - + " via the JAZZER_MAX_NUM_COUNTERS environment variable.", - MAX_NUM_COUNTERS); + + " via the %s environment variable.", + MAX_NUM_COUNTERS, ENV_MAX_NUM_COUNTERS); System.exit(1); } } -- cgit v1.2.3 From 70f4db399d9b5e7e9b1910bde403e67ee9857e91 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 8 Feb 2022 12:44:28 +0100 Subject: Switch to an Unsafe-backed CoverageMap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Benchmarks showed that this instrumentation, which has the name staticMethod_Unsafe_NeverZero2, is almost 2.5x faster than the inlined DirectByteBuffer strategy: CoverageInstrumentationBenchmark.local_DirectByteBuffer_NeverZero thrpt 25 606359.097 ± 51464.275 ops/s CoverageInstrumentationBenchmark.staticMethod_DirectByteBuffer2_NeverZero thrpt 25 848459.679 ± 127822.610 ops/s CoverageInstrumentationBenchmark.staticMethod_DirectByteBuffer_NeverZero thrpt 25 806509.876 ± 131400.478 ops/s CoverageInstrumentationBenchmark.staticMethod_Unsafe_AtomicSimpleIncrement thrpt 25 258652.693 ± 994.936 ops/s CoverageInstrumentationBenchmark.staticMethod_Unsafe_NeverZero thrpt 25 1393052.150 ± 11020.556 ops/s CoverageInstrumentationBenchmark.staticMethod_Unsafe_NeverZero2 thrpt 25 1477061.469 ± 7437.856 ops/s CoverageInstrumentationBenchmark.staticMethod_Unsafe_NeverZeroBranchfree thrpt 25 1269241.908 ± 11487.180 ops/s CoverageInstrumentationBenchmark.staticMethod_Unsafe_SimpleIncrement thrpt 25 1716329.628 ± 33942.194 ops/s CoverageInstrumentationBenchmark.uninstrumented thrpt 25 3194175.390 ± 65778.337 ops/s This commit preserves the original strategy as part of the benchmark. --- .../jazzer/instrumentor/BUILD.bazel | 14 +++- .../instrumentor/DirectByteBufferStrategy.kt | 81 ++++++++++++++++++++++ .../jazzer/instrumentor/StaticMethodStrategy.java | 48 ------------- .../jazzer/instrumentor/BUILD.bazel | 2 +- .../jazzer/instrumentor/ClassInstrumentor.kt | 2 +- .../instrumentor/DirectByteBufferStrategy.kt | 81 ---------------------- .../jazzer/instrumentor/StaticMethodStrategy.java | 48 +++++++++++++ .../code_intelligence/jazzer/runtime/BUILD.bazel | 13 +++- .../jazzer/runtime/CoverageMap.java | 33 +++++++-- .../jazzer/instrumentor/MockCoverageMap.java | 5 ++ driver/coverage_tracker.cpp | 13 ++-- driver/coverage_tracker.h | 2 +- driver/testdata/BUILD.bazel | 1 + driver/testdata/test/FuzzTargetWithCoverage.java | 6 +- 14 files changed, 195 insertions(+), 154 deletions(-) create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt delete mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/StaticMethodStrategy.java delete mode 100644 agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt create mode 100644 agent/src/main/java/com/code_intelligence/jazzer/instrumentor/StaticMethodStrategy.java diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel index e8858f0f..fd3a8ae1 100644 --- a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -1,3 +1,5 @@ +load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") + java_binary( name = "CoverageInstrumentationBenchmark", main_class = "org.openjdk.jmh.Main", @@ -37,6 +39,7 @@ java_library( "@maven//:com_mikesamuel_json_sanitizer", ], deps = [ + ":kotlin_strategies", ":strategies", "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor", "@maven//:org_openjdk_jmh_jmh_core", @@ -48,7 +51,6 @@ java_library( srcs = [ "DirectByteBuffer2CoverageMap.java", "DirectByteBufferCoverageMap.java", - "StaticMethodStrategy.java", "Unsafe2CoverageMap.java", "UnsafeBranchfreeCoverageMap.java", "UnsafeCoverageMap.java", @@ -61,6 +63,16 @@ java_library( ], ) +kt_jvm_library( + name = "kotlin_strategies", + srcs = ["DirectByteBufferStrategy.kt"], + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor", + "@jazzer_jacoco//:jacoco_internal", + "@jazzer_ow2_asm//:asm", + ], +) + java_plugin( name = "JmhGeneratorAnnotationProcessor", processor_class = "org.openjdk.jmh.generators.BenchmarkProcessor", diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt new file mode 100644 index 00000000..49090184 --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt @@ -0,0 +1,81 @@ +// Copyright 2022 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.instrumentor + +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes + +object DirectByteBufferStrategy : EdgeCoverageStrategy { + + override fun instrumentControlFlowEdge( + mv: MethodVisitor, + edgeId: Int, + variable: Int, + coverageMapInternalClassName: String + ) { + mv.apply { + visitVarInsn(Opcodes.ALOAD, variable) + // Stack: counters + push(edgeId) + // Stack: counters | edgeId + visitInsn(Opcodes.DUP2) + // Stack: counters | edgeId | counters | edgeId + visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/nio/ByteBuffer", "get", "(I)B", false) + // Increment the counter, but ensure that it never stays at 0 after an overflow by incrementing it again in + // that case. + // This approach performs better than saturating the counter at 255 (see Section 3.3 of + // https://www.usenix.org/system/files/woot20-paper-fioraldi.pdf) + // Stack: counters | edgeId | counter (sign-extended to int) + push(0xff) + // Stack: counters | edgeId | counter (sign-extended to int) | 0x000000ff + visitInsn(Opcodes.IAND) + // Stack: counters | edgeId | counter (zero-extended to int) + push(1) + // Stack: counters | edgeId | counter | 1 + visitInsn(Opcodes.IADD) + // Stack: counters | edgeId | counter + 1 + visitInsn(Opcodes.DUP) + // Stack: counters | edgeId | counter + 1 | counter + 1 + push(8) + // Stack: counters | edgeId | counter + 1 | counter + 1 | 8 (maxStack: +5) + visitInsn(Opcodes.ISHR) + // Stack: counters | edgeId | counter + 1 | 1 if the increment overflowed to 0, 0 otherwise + visitInsn(Opcodes.IADD) + // Stack: counters | edgeId | counter + 2 if the increment overflowed, counter + 1 otherwise + visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/nio/ByteBuffer", "put", "(IB)Ljava/nio/ByteBuffer;", false) + // Stack: counters + visitInsn(Opcodes.POP) + } + } + + override val instrumentControlFlowEdgeStackSize = 5 + + override val localVariableType get() = "java/nio/ByteBuffer" + + override fun loadLocalVariable(mv: MethodVisitor, variable: Int, coverageMapInternalClassName: String) { + mv.apply { + visitFieldInsn( + Opcodes.GETSTATIC, + coverageMapInternalClassName, + "counters", + "Ljava/nio/ByteBuffer;", + ) + // Stack: counters (maxStack: 1) + visitVarInsn(Opcodes.ASTORE, variable) + } + } + + override val loadLocalVariableStackSize = 1 +} diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/StaticMethodStrategy.java b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/StaticMethodStrategy.java deleted file mode 100644 index ca0bd3d7..00000000 --- a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/StaticMethodStrategy.java +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2022 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.instrumentor; - -import org.jacoco.core.internal.instr.InstrSupport; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; - -public class StaticMethodStrategy implements EdgeCoverageStrategy { - @Override - public void instrumentControlFlowEdge( - MethodVisitor mv, int edgeId, int variable, String coverageMapInternalClassName) { - InstrSupport.push(mv, edgeId); - mv.visitMethodInsn( - Opcodes.INVOKESTATIC, coverageMapInternalClassName, "recordCoverage", "(I)V", false); - } - - @Override - public int getInstrumentControlFlowEdgeStackSize() { - return 1; - } - - @Override - public Object getLocalVariableType() { - return null; - } - - @Override - public void loadLocalVariable( - MethodVisitor mv, int variable, String coverageMapInternalClassName) {} - - @Override - public int getLoadLocalVariableStackSize() { - return 0; - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel index 5494c1a0..8bf9d406 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -8,12 +8,12 @@ kt_jvm_library( "CoverageRecorder.kt", "DescriptorUtils.kt", "DeterministicRandom.kt", - "DirectByteBufferStrategy.kt", "EdgeCoverageInstrumentor.kt", "Hook.kt", "HookInstrumentor.kt", "HookMethodVisitor.kt", "Instrumentor.kt", + "StaticMethodStrategy.java", "TraceDataFlowInstrumentor.kt", ], visibility = [ diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt index f1a0ab25..e3fec450 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt @@ -56,7 +56,7 @@ class ClassInstrumentor constructor(bytecode: ByteArray) { } } - val defaultEdgeCoverageStrategy = DirectByteBufferStrategy + val defaultEdgeCoverageStrategy = StaticMethodStrategy() val defaultCoverageMap = CoverageMap::class.java } } diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt deleted file mode 100644 index 49090184..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DirectByteBufferStrategy.kt +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2022 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.instrumentor - -import org.objectweb.asm.MethodVisitor -import org.objectweb.asm.Opcodes - -object DirectByteBufferStrategy : EdgeCoverageStrategy { - - override fun instrumentControlFlowEdge( - mv: MethodVisitor, - edgeId: Int, - variable: Int, - coverageMapInternalClassName: String - ) { - mv.apply { - visitVarInsn(Opcodes.ALOAD, variable) - // Stack: counters - push(edgeId) - // Stack: counters | edgeId - visitInsn(Opcodes.DUP2) - // Stack: counters | edgeId | counters | edgeId - visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/nio/ByteBuffer", "get", "(I)B", false) - // Increment the counter, but ensure that it never stays at 0 after an overflow by incrementing it again in - // that case. - // This approach performs better than saturating the counter at 255 (see Section 3.3 of - // https://www.usenix.org/system/files/woot20-paper-fioraldi.pdf) - // Stack: counters | edgeId | counter (sign-extended to int) - push(0xff) - // Stack: counters | edgeId | counter (sign-extended to int) | 0x000000ff - visitInsn(Opcodes.IAND) - // Stack: counters | edgeId | counter (zero-extended to int) - push(1) - // Stack: counters | edgeId | counter | 1 - visitInsn(Opcodes.IADD) - // Stack: counters | edgeId | counter + 1 - visitInsn(Opcodes.DUP) - // Stack: counters | edgeId | counter + 1 | counter + 1 - push(8) - // Stack: counters | edgeId | counter + 1 | counter + 1 | 8 (maxStack: +5) - visitInsn(Opcodes.ISHR) - // Stack: counters | edgeId | counter + 1 | 1 if the increment overflowed to 0, 0 otherwise - visitInsn(Opcodes.IADD) - // Stack: counters | edgeId | counter + 2 if the increment overflowed, counter + 1 otherwise - visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/nio/ByteBuffer", "put", "(IB)Ljava/nio/ByteBuffer;", false) - // Stack: counters - visitInsn(Opcodes.POP) - } - } - - override val instrumentControlFlowEdgeStackSize = 5 - - override val localVariableType get() = "java/nio/ByteBuffer" - - override fun loadLocalVariable(mv: MethodVisitor, variable: Int, coverageMapInternalClassName: String) { - mv.apply { - visitFieldInsn( - Opcodes.GETSTATIC, - coverageMapInternalClassName, - "counters", - "Ljava/nio/ByteBuffer;", - ) - // Stack: counters (maxStack: 1) - visitVarInsn(Opcodes.ASTORE, variable) - } - } - - override val loadLocalVariableStackSize = 1 -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/StaticMethodStrategy.java b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/StaticMethodStrategy.java new file mode 100644 index 00000000..ca0bd3d7 --- /dev/null +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/StaticMethodStrategy.java @@ -0,0 +1,48 @@ +// Copyright 2022 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.instrumentor; + +import org.jacoco.core.internal.instr.InstrSupport; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class StaticMethodStrategy implements EdgeCoverageStrategy { + @Override + public void instrumentControlFlowEdge( + MethodVisitor mv, int edgeId, int variable, String coverageMapInternalClassName) { + InstrSupport.push(mv, edgeId); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, coverageMapInternalClassName, "recordCoverage", "(I)V", false); + } + + @Override + public int getInstrumentControlFlowEdgeStackSize() { + return 1; + } + + @Override + public Object getLocalVariableType() { + return null; + } + + @Override + public void loadLocalVariable( + MethodVisitor mv, int variable, String coverageMapInternalClassName) {} + + @Override + public int getLoadLocalVariableStackSize() { + return 0; + } +} 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 1741ede1..e2718c36 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 @@ -14,6 +14,17 @@ java_library( ], ) +java_library( + name = "coverage_map", + srcs = ["CoverageMap.java"], + visibility = [ + "//driver/testdata:__pkg__", + ], + deps = [ + ":unsafe_provider", + ], +) + java_library( name = "signal_handler", srcs = ["SignalHandler.java"], @@ -27,7 +38,6 @@ java_library( kt_jvm_library( name = "runtime", srcs = [ - "CoverageMap.java", "ExceptionUtils.kt", "HardToCatchError.java", "JazzerInternal.java", @@ -44,6 +54,7 @@ kt_jvm_library( "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz", ], deps = [ + ":coverage_map", ":fuzzed_data_provider", ":signal_handler", "//agent/src/main/java/com/code_intelligence/jazzer/api", diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java index d3e6110f..9bf0b395 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java @@ -18,6 +18,7 @@ import java.nio.ByteBuffer; import java.util.Collections; import java.util.HashSet; import java.util.Set; +import sun.misc.Unsafe; /** * Represents the Java view on a libFuzzer 8 bit counter coverage map. By using a direct ByteBuffer, @@ -30,16 +31,28 @@ final public class CoverageMap { ? Integer.parseInt(System.getenv(ENV_MAX_NUM_COUNTERS)) : 1 << 20; + private static final Unsafe UNSAFE = UnsafeProvider.getUnsafe(); + + static { + if (UNSAFE == null) { + System.out.println("ERROR: Failed to get Unsafe instance for CoverageMap.%n" + + " Please file a bug at:%n" + + " https://github.com/CodeIntelligenceTesting/jazzer/issues/new"); + System.exit(1); + } + } + /** * The collection of coverage counters directly interacted with by classes that are instrumented * for coverage. The instrumentation assumes that this is always one contiguous block of memory, * so it is allocated once at maximum size. Using a larger number here increases the memory usage * of all fuzz targets, but has otherwise no impact on performance. */ - public static final ByteBuffer counters = ByteBuffer.allocateDirect(MAX_NUM_COUNTERS); + public static final long countersAddress = UNSAFE.allocateMemory(MAX_NUM_COUNTERS); static { - initialize(counters); + UNSAFE.setMemory(countersAddress, MAX_NUM_COUNTERS, (byte) 0); + initialize(countersAddress); } private static final int INITIAL_NUM_COUNTERS = 1 << 9; @@ -78,10 +91,18 @@ final public class CoverageMap { } } + // Called by the coverage instrumentation. + @SuppressWarnings("unused") + public static void recordCoverage(final int id) { + final long address = countersAddress + id; + final byte counter = UNSAFE.getByte(address); + UNSAFE.putByte(address, (byte) (counter == -1 ? 1 : counter + 1)); + } + public static Set getCoveredIds() { Set coveredIds = new HashSet<>(); - for (int id = 0; id < counters.capacity(); id++) { - if (counters.get(id) > 0) { + for (int id = 0; id < currentNumCounters; id++) { + if (UNSAFE.getByte(countersAddress + id) > 0) { coveredIds.add(id); } } @@ -90,11 +111,11 @@ final public class CoverageMap { public static void replayCoveredIds(Set coveredIds) { for (int id : coveredIds) { - counters.put(id, (byte) 1); + UNSAFE.putByte(countersAddress + id, (byte) 1); } } - private static native void initialize(ByteBuffer counters); + private static native void initialize(long countersAddress); private static native void registerNewCounters(int oldNumCounters, int newNumCounters); } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java index e1d4d611..3ea33d19 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java @@ -40,6 +40,11 @@ public class MockCoverageMap { // This mock coverage map is statically sized. } + public static void recordCoverage(int id) { + byte counter = counters.get(id); + counters.put(id, (byte) (counter == -1 ? 1 : counter + 1)); + } + public static void clear() { Arrays.fill(counters.array(), (byte) 0); Arrays.fill(previous_mem.array(), (byte) 0); diff --git a/driver/coverage_tracker.cpp b/driver/coverage_tracker.cpp index 562761ce..1ab65ec2 100644 --- a/driver/coverage_tracker.cpp +++ b/driver/coverage_tracker.cpp @@ -46,17 +46,12 @@ uint8_t *CoverageTracker::counters_ = nullptr; uint32_t *CoverageTracker::fake_instructions_ = nullptr; PCTableEntry *CoverageTracker::pc_entries_ = nullptr; -void CoverageTracker::Initialize(JNIEnv &env, jobject buffer) { +void CoverageTracker::Initialize(JNIEnv &env, jlong counters) { if (counters_ != nullptr) { throw std::runtime_error( "CoverageTracker::Initialize must not be called more than once"); } - void *counters = env.GetDirectBufferAddress(buffer); - AssertNoException(env); - if (counters == nullptr) { - throw std::runtime_error("Failed to obtain address of counters buffer"); - } - counters_ = static_cast(counters); + counters_ = reinterpret_cast(static_cast(counters)); } void CoverageTracker::RegisterNewCounters(JNIEnv &env, jint old_num_counters, @@ -164,8 +159,8 @@ std::string CoverageTracker::ComputeCoverage(JNIEnv &env) { extern "C" { JNIEXPORT void JNICALL Java_com_code_1intelligence_jazzer_runtime_CoverageMap_initialize( - JNIEnv &env, jclass cls, jobject buffer) { - ::jazzer::CoverageTracker::Initialize(env, buffer); + JNIEnv &env, jclass cls, jlong counters) { + ::jazzer::CoverageTracker::Initialize(env, counters); } JNIEXPORT void JNICALL diff --git a/driver/coverage_tracker.h b/driver/coverage_tracker.h index cd39969e..e520aa12 100644 --- a/driver/coverage_tracker.h +++ b/driver/coverage_tracker.h @@ -40,7 +40,7 @@ class CoverageTracker : public ExceptionPrinter { static PCTableEntry *pc_entries_; public: - static void Initialize(JNIEnv &env, jobject buffer); + static void Initialize(JNIEnv &env, jlong counters); static void RegisterNewCounters(JNIEnv &env, jint old_num_counters, jint new_num_counters); diff --git a/driver/testdata/BUILD.bazel b/driver/testdata/BUILD.bazel index 8dd67e12..901ae89b 100644 --- a/driver/testdata/BUILD.bazel +++ b/driver/testdata/BUILD.bazel @@ -6,5 +6,6 @@ java_binary( deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/api", "//agent/src/main/java/com/code_intelligence/jazzer/runtime", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:coverage_map", ], ) diff --git a/driver/testdata/test/FuzzTargetWithCoverage.java b/driver/testdata/test/FuzzTargetWithCoverage.java index 776e9ce5..e85c674b 100644 --- a/driver/testdata/test/FuzzTargetWithCoverage.java +++ b/driver/testdata/test/FuzzTargetWithCoverage.java @@ -19,10 +19,6 @@ import com.code_intelligence.jazzer.runtime.CoverageMap; public class FuzzTargetWithCoverage { public static void fuzzerTestOneInput(byte[] input) { // manually increase the first coverage counter - byte counter = CoverageMap.counters.get(0); - counter++; - if (counter == 0) - counter--; - CoverageMap.counters.put(0, counter); + CoverageMap.recordCoverage(0); } } -- cgit v1.2.3 From 6c4d22766e3026e6f970a4c17835c529f7a6a1a4 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sun, 6 Feb 2022 18:10:56 +0100 Subject: Use LLVM's CMake C flags for libfuzzer We were using the flags from a standalone build script for libFuzzer, but should instead try to use the same set of flags as an LLVM release would use. --- third_party/libFuzzer.BUILD | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/third_party/libFuzzer.BUILD b/third_party/libFuzzer.BUILD index e8559936..bdb0ee56 100644 --- a/third_party/libFuzzer.BUILD +++ b/third_party/libFuzzer.BUILD @@ -1,4 +1,3 @@ -# Based on https://github.com/llvm/llvm-project/blob/llvmorg-11.1.0/compiler-rt/lib/fuzzer/build.sh cc_library( name = "libFuzzer", srcs = glob([ @@ -8,17 +7,29 @@ cc_library( "*.h", "*.def", ]), - copts = select({ + copts = [ + # https://github.com/llvm/llvm-project/blob/eab395fa4074a5a0cbfebe811937dbb1816df9ef/compiler-rt/CMakeLists.txt#L294-L309 + "-fno-builtin", + "-fno-exceptions", + "-funwind-tables", + "-fno-stack-protector", + "-fno-sanitize=safe-stack", + "-fvisibility=hidden", + "-fno-lto", + ] + select({ "@platforms//os:windows": [ - "/Ox", # Optimize for speed. - "/Oy-", # Do not omit frame pointer. + # https://github.com/llvm/llvm-project/blob/eab395fa4074a5a0cbfebe811937dbb1816df9ef/compiler-rt/CMakeLists.txt#L362-L363 + "/Oy-", + "/GS-", "/std:c++17", ], "//conditions:default": [ - "-g", - "-O2", + # https://github.com/llvm/llvm-project/commit/29d3ba7576b30a37bd19a5d40f304fc39c6ab13d "-fno-omit-frame-pointer", - "-std=c++11", + # https://github.com/llvm/llvm-project/blob/eab395fa4074a5a0cbfebe811937dbb1816df9ef/compiler-rt/CMakeLists.txt#L392 + "-O3", + # Use the same C++ standard as Jazzer itself. + "-std=c++17", ], }), alwayslink = True, -- cgit v1.2.3 From 8f37764b8d6948c5479496bfee403ab262714d9b Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 11 Feb 2022 12:35:31 +0100 Subject: Add a tool that hermetically strips paths from JARs Also reorganize our existing tool, the FuzzTargetTestWrapper, under a common tools directory with a Maven layout. --- bazel/BUILD.bazel | 6 - bazel/FuzzTargetTestWrapper.java | 177 -------------------- bazel/fuzz_target.bzl | 4 +- bazel/tools/java/BUILD.bazel | 13 ++ .../jazzer/tools/FuzzTargetTestWrapper.java | 178 +++++++++++++++++++++ .../jazzer/tools/JarStripper.java | 92 +++++++++++ 6 files changed, 285 insertions(+), 185 deletions(-) delete mode 100644 bazel/FuzzTargetTestWrapper.java create mode 100644 bazel/tools/java/BUILD.bazel create mode 100644 bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java create mode 100644 bazel/tools/java/com/code_intelligence/jazzer/tools/JarStripper.java diff --git a/bazel/BUILD.bazel b/bazel/BUILD.bazel index 1e2348c1..e69de29b 100644 --- a/bazel/BUILD.bazel +++ b/bazel/BUILD.bazel @@ -1,6 +0,0 @@ -java_library( - name = "fuzz_target_test_wrapper", - srcs = ["FuzzTargetTestWrapper.java"], - visibility = ["//:__subpackages__"], - deps = ["@bazel_tools//tools/java/runfiles"], -) diff --git a/bazel/FuzzTargetTestWrapper.java b/bazel/FuzzTargetTestWrapper.java deleted file mode 100644 index 5b99c50a..00000000 --- a/bazel/FuzzTargetTestWrapper.java +++ /dev/null @@ -1,177 +0,0 @@ -// 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. - -import com.google.devtools.build.runfiles.Runfiles; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import javax.tools.JavaCompiler; -import javax.tools.JavaCompiler.CompilationTask; -import javax.tools.JavaFileObject; -import javax.tools.StandardJavaFileManager; -import javax.tools.ToolProvider; - -public class FuzzTargetTestWrapper { - public static void main(String[] args) { - Runfiles runfiles; - String driverActualPath; - String apiActualPath; - String jarActualPath; - boolean verifyCrashInput; - boolean verifyCrashReproducer; - boolean executeCrashReproducer; - try { - runfiles = Runfiles.create(); - driverActualPath = runfiles.rlocation(rlocationPath(args[0])); - apiActualPath = runfiles.rlocation(rlocationPath(args[1])); - jarActualPath = runfiles.rlocation(rlocationPath(args[2])); - verifyCrashInput = Boolean.parseBoolean(args[3]); - verifyCrashReproducer = Boolean.parseBoolean(args[4]); - executeCrashReproducer = Boolean.parseBoolean(args[5]); - } catch (IOException | ArrayIndexOutOfBoundsException e) { - e.printStackTrace(); - System.exit(1); - return; - } - - ProcessBuilder processBuilder = new ProcessBuilder(); - Map environment = processBuilder.environment(); - // Ensure that Jazzer can find its runfiles. - environment.putAll(runfiles.getEnvVars()); - - // Crashes will be available as test outputs. These are cleared on the next run, - // so this is only useful for examples. - String outputDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR"); - - // Map all files/dirs to real location - Stream arguments = Arrays.stream(args).skip(6).map( - arg -> arg.startsWith("-") ? arg : runfiles.rlocation(rlocationPath(arg))); - - List command = - Stream - .concat(Stream.of(driverActualPath, String.format("-artifact_prefix=%s/", outputDir), - String.format("--reproducer_path=%s", outputDir), "-seed=2735196724", - String.format("--cp=%s", jarActualPath)), - arguments) - .collect(Collectors.toList()); - processBuilder.inheritIO(); - processBuilder.command(command); - - try { - int exitCode = processBuilder.start().waitFor(); - // Assert that we either found a crash in Java (exit code 77) or a sanitizer crash (exit code - // 76). - if (exitCode != 76 && exitCode != 77) { - System.exit(3); - } - String[] outputFiles = new File(outputDir).list(); - if (outputFiles == null) { - System.exit(4); - } - // Verify that libFuzzer dumped a crashing input. - if (verifyCrashInput - && Arrays.stream(outputFiles).noneMatch(name -> name.startsWith("crash-"))) { - System.out.printf("No crashing input found in %s%n", outputDir); - System.exit(5); - } - // Verify that libFuzzer dumped a crash reproducer. - if (verifyCrashReproducer - && Arrays.stream(outputFiles).noneMatch(name -> name.startsWith("Crash_"))) { - System.out.printf("No crash reproducer found in %s%n", outputDir); - System.exit(6); - } - } catch (IOException | InterruptedException e) { - e.printStackTrace(); - System.exit(2); - } - - if (executeCrashReproducer) { - try { - verifyCrashReproducer(outputDir, driverActualPath, apiActualPath, jarActualPath); - } catch (Exception e) { - e.printStackTrace(); - System.exit(6); - } - } - System.exit(0); - } - - // Turns the result of Bazel's `$(rootpath ...)` into the correct format for rlocation. - private static String rlocationPath(String rootpath) { - if (rootpath.startsWith("external/")) { - return rootpath.substring("external/".length()); - } else { - return "jazzer/" + rootpath; - } - } - - private static void verifyCrashReproducer(String outputDir, String driver, String api, String jar) - throws Exception { - File source = - Files.list(Paths.get(outputDir)) - .filter(f -> f.toFile().getName().endsWith(".java")) - .findFirst() - .map(Path::toFile) - .orElseThrow( - () -> new IllegalStateException("Could not find crash reproducer in " + outputDir)); - String crashReproducer = compile(source, driver, api, jar); - execute(crashReproducer, outputDir); - } - - private static String compile(File source, String driver, String api, String jar) - throws IOException { - JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)) { - Iterable compilationUnits = fileManager.getJavaFileObjects(source); - List options = - Arrays.asList("-classpath", String.join(File.pathSeparator, driver, api, jar)); - System.out.printf( - "Compile crash reproducer %s with options %s%n", source.getAbsolutePath(), options); - CompilationTask task = - compiler.getTask(null, fileManager, null, options, null, compilationUnits); - if (!task.call()) { - throw new IllegalStateException("Could not compile crash reproducer " + source); - } - return source.getName().substring(0, source.getName().indexOf(".")); - } - } - - private static void execute(String classFile, String outputDir) - throws IOException, ReflectiveOperationException { - try { - System.out.printf("Execute crash reproducer %s%n", classFile); - URLClassLoader classLoader = - new URLClassLoader(new URL[] {new URL("file://" + outputDir + "/")}); - Class crashReproducerClass = classLoader.loadClass(classFile); - Method main = crashReproducerClass.getMethod("main", String[].class); - main.invoke(null, new Object[] {new String[] {}}); - throw new IllegalStateException("Crash not reproduced by " + classFile); - } catch (InvocationTargetException e) { - // expect the invocation to fail with the crash - // other reflection exceptions indicate a real problem - System.out.printf("Reproduced exception \"%s\"%n", e.getCause().getMessage()); - } - } -} diff --git a/bazel/fuzz_target.bzl b/bazel/fuzz_target.bzl index 32de3035..f9ebb7ae 100644 --- a/bazel/fuzz_target.bzl +++ b/bazel/fuzz_target.bzl @@ -65,7 +65,7 @@ def java_fuzz_target_test( native.java_test( name = name, runtime_deps = [ - "//bazel:fuzz_target_test_wrapper", + "//bazel/tools/java:fuzz_target_test_wrapper", "//agent:jazzer_api_deploy.jar", ":%s_deploy.jar" % target_name, ], @@ -86,7 +86,7 @@ def java_fuzz_target_test( driver, ] + data, env = env, - main_class = "FuzzTargetTestWrapper", + main_class = "com.code_intelligence.jazzer.tools.FuzzTargetTestWrapper", use_testrunner = False, tags = tags, visibility = visibility, diff --git a/bazel/tools/java/BUILD.bazel b/bazel/tools/java/BUILD.bazel new file mode 100644 index 00000000..becfe759 --- /dev/null +++ b/bazel/tools/java/BUILD.bazel @@ -0,0 +1,13 @@ +java_library( + name = "fuzz_target_test_wrapper", + srcs = ["com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java"], + visibility = ["//:__subpackages__"], + deps = ["@bazel_tools//tools/java/runfiles"], +) + +java_binary( + name = "JarStripper", + srcs = ["com/code_intelligence/jazzer/tools/JarStripper.java"], + main_class = "com.code_intelligence.jazzer.tools.JarStripper", + visibility = ["//visibility:public"], +) diff --git a/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java b/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java new file mode 100644 index 00000000..14eaf0ec --- /dev/null +++ b/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java @@ -0,0 +1,178 @@ +// 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.tools; + +import com.google.devtools.build.runfiles.Runfiles; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.tools.JavaCompiler; +import javax.tools.JavaCompiler.CompilationTask; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; + +public class FuzzTargetTestWrapper { + public static void main(String[] args) { + Runfiles runfiles; + String driverActualPath; + String apiActualPath; + String jarActualPath; + boolean verifyCrashInput; + boolean verifyCrashReproducer; + boolean executeCrashReproducer; + try { + runfiles = Runfiles.create(); + driverActualPath = runfiles.rlocation(rlocationPath(args[0])); + apiActualPath = runfiles.rlocation(rlocationPath(args[1])); + jarActualPath = runfiles.rlocation(rlocationPath(args[2])); + verifyCrashInput = Boolean.parseBoolean(args[3]); + verifyCrashReproducer = Boolean.parseBoolean(args[4]); + executeCrashReproducer = Boolean.parseBoolean(args[5]); + } catch (IOException | ArrayIndexOutOfBoundsException e) { + e.printStackTrace(); + System.exit(1); + return; + } + + ProcessBuilder processBuilder = new ProcessBuilder(); + Map environment = processBuilder.environment(); + // Ensure that Jazzer can find its runfiles. + environment.putAll(runfiles.getEnvVars()); + + // Crashes will be available as test outputs. These are cleared on the next run, + // so this is only useful for examples. + String outputDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR"); + + // Map all files/dirs to real location + Stream arguments = Arrays.stream(args).skip(6).map( + arg -> arg.startsWith("-") ? arg : runfiles.rlocation(rlocationPath(arg))); + + List command = + Stream + .concat(Stream.of(driverActualPath, String.format("-artifact_prefix=%s/", outputDir), + String.format("--reproducer_path=%s", outputDir), "-seed=2735196724", + String.format("--cp=%s", jarActualPath)), + arguments) + .collect(Collectors.toList()); + processBuilder.inheritIO(); + processBuilder.command(command); + + try { + int exitCode = processBuilder.start().waitFor(); + // Assert that we either found a crash in Java (exit code 77) or a sanitizer crash (exit code + // 76). + if (exitCode != 76 && exitCode != 77) { + System.exit(3); + } + String[] outputFiles = new File(outputDir).list(); + if (outputFiles == null) { + System.exit(4); + } + // Verify that libFuzzer dumped a crashing input. + if (verifyCrashInput + && Arrays.stream(outputFiles).noneMatch(name -> name.startsWith("crash-"))) { + System.out.printf("No crashing input found in %s%n", outputDir); + System.exit(5); + } + // Verify that libFuzzer dumped a crash reproducer. + if (verifyCrashReproducer + && Arrays.stream(outputFiles).noneMatch(name -> name.startsWith("Crash_"))) { + System.out.printf("No crash reproducer found in %s%n", outputDir); + System.exit(6); + } + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + System.exit(2); + } + + if (executeCrashReproducer) { + try { + verifyCrashReproducer(outputDir, driverActualPath, apiActualPath, jarActualPath); + } catch (Exception e) { + e.printStackTrace(); + System.exit(6); + } + } + System.exit(0); + } + + // Turns the result of Bazel's `$(rootpath ...)` into the correct format for rlocation. + private static String rlocationPath(String rootpath) { + if (rootpath.startsWith("external/")) { + return rootpath.substring("external/".length()); + } else { + return "jazzer/" + rootpath; + } + } + + private static void verifyCrashReproducer(String outputDir, String driver, String api, String jar) + throws Exception { + File source = + Files.list(Paths.get(outputDir)) + .filter(f -> f.toFile().getName().endsWith(".java")) + .findFirst() + .map(Path::toFile) + .orElseThrow( + () -> new IllegalStateException("Could not find crash reproducer in " + outputDir)); + String crashReproducer = compile(source, driver, api, jar); + execute(crashReproducer, outputDir); + } + + private static String compile(File source, String driver, String api, String jar) + throws IOException { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)) { + Iterable compilationUnits = fileManager.getJavaFileObjects(source); + List options = + Arrays.asList("-classpath", String.join(File.pathSeparator, driver, api, jar)); + System.out.printf( + "Compile crash reproducer %s with options %s%n", source.getAbsolutePath(), options); + CompilationTask task = + compiler.getTask(null, fileManager, null, options, null, compilationUnits); + if (!task.call()) { + throw new IllegalStateException("Could not compile crash reproducer " + source); + } + return source.getName().substring(0, source.getName().indexOf(".")); + } + } + + private static void execute(String classFile, String outputDir) + throws IOException, ReflectiveOperationException { + try { + System.out.printf("Execute crash reproducer %s%n", classFile); + URLClassLoader classLoader = + new URLClassLoader(new URL[] {new URL("file://" + outputDir + "/")}); + Class crashReproducerClass = classLoader.loadClass(classFile); + Method main = crashReproducerClass.getMethod("main", String[].class); + main.invoke(null, new Object[] {new String[] {}}); + throw new IllegalStateException("Crash not reproduced by " + classFile); + } catch (InvocationTargetException e) { + // expect the invocation to fail with the crash + // other reflection exceptions indicate a real problem + System.out.printf("Reproduced exception \"%s\"%n", e.getCause().getMessage()); + } + } +} diff --git a/bazel/tools/java/com/code_intelligence/jazzer/tools/JarStripper.java b/bazel/tools/java/com/code_intelligence/jazzer/tools/JarStripper.java new file mode 100644 index 00000000..0bbb1b09 --- /dev/null +++ b/bazel/tools/java/com/code_intelligence/jazzer/tools/JarStripper.java @@ -0,0 +1,92 @@ +// Copyright 2022 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.tools; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; +import java.util.stream.Collectors; + +public class JarStripper { + private static final Map ZIP_FS_PROPERTIES = new HashMap<>(); + static { + // We copy the input to the output path before modifying, so don't try to create a new file at + // that path if something went wrong. + ZIP_FS_PROPERTIES.put("create", "false"); + } + + public static void main(String[] args) { + if (args.length < 2) { + System.err.println( + "Hermetically removes files and directories from .jar files by relative paths."); + System.err.println("Usage: in.jar out.jar [relative path]..."); + System.exit(1); + } + + Path inFile = Paths.get(args[0]); + Path outFile = Paths.get(args[1]); + Iterable pathsToDelete = + Collections.unmodifiableList(Arrays.stream(args).skip(2).collect(Collectors.toList())); + + try { + Files.copy(inFile, outFile); + if (!outFile.toFile().setWritable(true)) { + System.err.printf("Failed to make %s writable", outFile); + System.exit(1); + } + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + } + + URI outUri = null; + try { + outUri = new URI("jar", outFile.toUri().toString(), null); + } catch (URISyntaxException e) { + e.printStackTrace(); + System.exit(1); + } + + // Ensure that the ZipFileSystem uses a system-independent time zone for mtimes. + // https://github.com/openjdk/jdk/blob/4d64076058a4ec5df101b06572195ed5fdee6f64/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipUtils.java#L241 + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + + try (FileSystem zipFs = FileSystems.newFileSystem(outUri, ZIP_FS_PROPERTIES)) { + for (String pathToDelete : pathsToDelete) { + // Visit files before the directory they are contained in by sorting in reverse order. + Iterable subpaths = Files.walk(zipFs.getPath(pathToDelete)) + .sorted(Comparator.reverseOrder()) + .collect(Collectors.toList()); + for (Path subpath : subpaths) { + Files.delete(subpath); + } + } + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + } + } +} -- cgit v1.2.3 From dfa07a8faf76e6d7d4e5f2c9616665234e58f683 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 7 Feb 2022 12:50:40 +0100 Subject: Get ASM from Maven rather than gitlab.ow2.org The OW2 GitLab hasn't been very reliable in the past and just encountered another outage. Getting the ASM jars from Maven should be more reliable. --- agent/BUILD.bazel | 15 ++++++++-- .../jazzer/instrumentor/BUILD.bazel | 4 +-- .../jazzer/instrumentor/BUILD.bazel | 4 +-- repositories.bzl | 26 +++++++++++++----- third_party/asm.BUILD | 32 ---------------------- third_party/jacoco_internal.BUILD | 6 ++-- 6 files changed, 39 insertions(+), 48 deletions(-) delete mode 100644 third_party/asm.BUILD diff --git a/agent/BUILD.bazel b/agent/BUILD.bazel index b227cf06..9f157cd2 100644 --- a/agent/BUILD.bazel +++ b/agent/BUILD.bazel @@ -15,11 +15,22 @@ java_binary( ], ) -jar_jar( +genrule( name = "jazzer_agent_deploy", + srcs = [":jazzer_agent_shaded_deploy"], + outs = ["jazzer_agent_deploy.jar"], + cmd = """ +$(execpath //bazel/tools/java:JarStripper) $(execpath :jazzer_agent_shaded_deploy) $@ \ +module-info.class +""", + exec_tools = ["//bazel/tools/java:JarStripper"], + visibility = ["//visibility:public"], +) + +jar_jar( + name = "jazzer_agent_shaded_deploy", input_jar = "jazzer_agent_unshaded_deploy.jar", rules = "agent_shade_rules", - visibility = ["//visibility:public"], ) sh_test( diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel index fd3a8ae1..8862c435 100644 --- a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -59,7 +59,7 @@ java_library( deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor", "@jazzer_jacoco//:jacoco_internal", - "@jazzer_ow2_asm//:asm", + "@org_ow2_asm_asm//jar", ], ) @@ -69,7 +69,7 @@ kt_jvm_library( deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor", "@jazzer_jacoco//:jacoco_internal", - "@jazzer_ow2_asm//:asm", + "@org_ow2_asm_asm//jar", ], ) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel index 8bf9d406..f57cc758 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -27,7 +27,7 @@ kt_jvm_library( "@com_github_classgraph_classgraph//:classgraph", "@com_github_jetbrains_kotlin//:kotlin-reflect", "@jazzer_jacoco//:jacoco_internal", - "@jazzer_ow2_asm//:asm", - "@jazzer_ow2_asm//:asm_commons", + "@org_ow2_asm_asm//jar", + "@org_ow2_asm_asm_commons//jar", ], ) diff --git a/repositories.bzl b/repositories.bzl index 0971439f..48391c0d 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -14,7 +14,7 @@ """Contains the external dependencies required to build Jazzer (but not the examples).""" -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_jar") load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") def jazzer_dependencies(): @@ -97,12 +97,24 @@ def jazzer_dependencies(): ) maybe( - http_archive, - build_file = Label("//third_party:asm.BUILD"), - name = "jazzer_ow2_asm", - sha256 = "7b596cc584b241619911e99c5c96366fccd533b1a50b8720c151c2f74b5915e3", - strip_prefix = "asm-ASM_9_2", - url = "https://gitlab.ow2.org/asm/asm/-/archive/ASM_9_2/asm-ASM_9_2.tar.gz", + http_jar, + name = "org_ow2_asm_asm", + sha256 = "b9d4fe4d71938df38839f0eca42aaaa64cf8b313d678da036f0cb3ca199b47f5", + url = "https://repo1.maven.org/maven2/org/ow2/asm/asm/9.2/asm-9.2.jar", + ) + + maybe( + http_jar, + name = "org_ow2_asm_asm_commons", + sha256 = "be4ce53138a238bb522cd781cf91f3ba5ce2f6ca93ec62d46a162a127225e0a6", + url = "https://repo1.maven.org/maven2/org/ow2/asm/asm-commons/9.2/asm-commons-9.2.jar", + ) + + maybe( + http_jar, + name = "org_ow2_asm_asm_tree", + sha256 = "aabf9bd23091a4ebfc109c1f3ee7cf3e4b89f6ba2d3f51c5243f16b3cffae011", + url = "https://repo1.maven.org/maven2/org/ow2/asm/asm-tree/9.2/asm-tree-9.2.jar", ) maybe( diff --git a/third_party/asm.BUILD b/third_party/asm.BUILD deleted file mode 100644 index 2f659fc6..00000000 --- a/third_party/asm.BUILD +++ /dev/null @@ -1,32 +0,0 @@ -java_library( - name = "asm", - srcs = glob(["asm/src/main/**/*.java"]), - visibility = ["//visibility:public"], -) - -java_library( - name = "asm_commons", - srcs = glob(["asm-commons/src/main/**/*.java"]), - deps = [ - ":asm", - ":asm_analysis", - ":asm_tree", - ], - visibility = ["//visibility:public"], -) - -java_library( - name = "asm_tree", - srcs = glob(["asm-tree/src/main/**/*.java"]), - deps = [":asm"], - visibility = ["//visibility:public"], -) - -java_library( - name = "asm_analysis", - srcs = glob(["asm-analysis/src/main/**/*.java"]), - deps = [ - ":asm", - ":asm_tree", - ], -) diff --git a/third_party/jacoco_internal.BUILD b/third_party/jacoco_internal.BUILD index 9e6140a7..4731525f 100644 --- a/third_party/jacoco_internal.BUILD +++ b/third_party/jacoco_internal.BUILD @@ -10,9 +10,9 @@ java_library( "-Xep:EqualsHashCode:OFF", ], deps = [ - "@jazzer_ow2_asm//:asm", - "@jazzer_ow2_asm//:asm_commons", - "@jazzer_ow2_asm//:asm_tree", + "@org_ow2_asm_asm//jar", + "@org_ow2_asm_asm_commons//jar", + "@org_ow2_asm_asm_tree//jar", ], visibility = ["//visibility:public"], ) -- cgit v1.2.3 From 93fcdf3111210bd5ce0533de0bf247d8605e3a85 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 11 Feb 2022 13:34:55 +0100 Subject: Use tags for all dependencies GitHub support has confirmed that only the /archive/refs/tags endpoint can be assumed to return stable results over time. For dependencies that do not offer tags we can use, I created a fork with a "commit-" tag for the commit we were previously depending on. Along the way, update some dependencies to their latest tag. See this comment for more context regarding the GitHub guarantees for archive stability: https://github.com/bazel-contrib/SIG-rules-authors/issues/11#issuecomment-1029861300 --- WORKSPACE.bazel | 14 +++++++------- repositories.bzl | 32 ++++++++++++++++---------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index c07582b7..7a7d4b5a 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -32,28 +32,28 @@ http_archive( ], sha256 = "da607faed78c4cb5a5637ef74a36fdd2286f85ca5192222c4664efec2d529bb8", strip_prefix = "bazel-toolchain-0.6.3", - urls = ["https://github.com/grailbio/bazel-toolchain/archive/0.6.3.tar.gz"], + urls = ["https://github.com/grailbio/bazel-toolchain/archive/refs/tags/0.6.3.tar.gz"], ) http_archive( name = "googletest", sha256 = "9dc9157a9a1551ec7a7e43daea9a694a0bb5fb8bec81235d8a1e6ef64c716dcb", strip_prefix = "googletest-release-1.10.0", - url = "https://github.com/google/googletest/archive/release-1.10.0.tar.gz", + url = "https://github.com/google/googletest/archive/refs/tags/release-1.10.0.tar.gz", ) http_archive( name = "rules_foreign_cc", - sha256 = "8ab257584256e2c7eefa0c4e0794ae3be3e8f634f9ec0356da0a653dfed5da9a", - strip_prefix = "rules_foreign_cc-76198edc790de8e8514bddaa3895d1145fccd6aa", - url = "https://github.com/bazelbuild/rules_foreign_cc/archive/76198edc790de8e8514bddaa3895d1145fccd6aa.tar.gz", + sha256 = "bcd0c5f46a49b85b384906daae41d277b3dc0ff27c7c752cc51e43048a58ec83", + strip_prefix = "rules_foreign_cc-0.7.1", + url = "https://github.com/bazelbuild/rules_foreign_cc/archive/refs/tags/0.7.1.tar.gz", ) http_archive( name = "rules_jvm_external", sha256 = "f36441aa876c4f6427bfb2d1f2d723b48e9d930b62662bf723ddfb8fc80f0140", strip_prefix = "rules_jvm_external-4.1", - url = "https://github.com/bazelbuild/rules_jvm_external/archive/4.1.zip", + url = "https://github.com/bazelbuild/rules_jvm_external/archive/refs/tags/4.1.zip", ) http_archive( @@ -61,7 +61,7 @@ http_archive( build_file = "//third_party:libjpeg_turbo.BUILD", sha256 = "6a965adb02ad898b2ae48214244618fe342baea79db97157fdc70d8844ac6f09", strip_prefix = "libjpeg-turbo-2.0.90", - url = "https://github.com/libjpeg-turbo/libjpeg-turbo/archive/2.0.90.tar.gz", + url = "https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/2.0.90.tar.gz", ) load("@com_grail_bazel_toolchain//toolchain:deps.bzl", "bazel_toolchain_dependencies") diff --git a/repositories.bzl b/repositories.bzl index 48391c0d..755e5d0e 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -21,10 +21,10 @@ def jazzer_dependencies(): maybe( http_archive, name = "platforms", - sha256 = "079945598e4b6cc075846f7fd6a9d0857c33a7afc0de868c2ccb96405225135d", + sha256 = "379113459b0feaf6bfbb584a91874c065078aa673222846ac765f86661c27407", urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.4/platforms-0.0.4.tar.gz", - "https://github.com/bazelbuild/platforms/releases/download/0.0.4/platforms-0.0.4.tar.gz", + "https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.5/platforms-0.0.5.tar.gz", + "https://github.com/bazelbuild/platforms/releases/download/0.0.5/platforms-0.0.5.tar.gz", ], ) @@ -49,9 +49,9 @@ def jazzer_dependencies(): http_archive, name = "com_google_glog", repo_mapping = {"@com_github_gflags_gflags": "@jazzer_com_github_gflags_gflags"}, - sha256 = "5a39d51a6058348e6b683f5343a24d94e01c518c7a045101045e301a27efab13", - strip_prefix = "glog-a4a725d547a6c1329607db50af044c4fa329e07a", - url = "https://github.com/google/glog/archive/a4a725d547a6c1329607db50af044c4fa329e07a.tar.gz", + sha256 = "eede71f28371bf39aa69b45de23b329d37214016e2055269b3b5e7cfd40b59f5", + strip_prefix = "glog-0.5.0", + url = "https://github.com/google/glog/archive/refs/tags/v0.5.0.tar.gz", ) maybe( @@ -65,18 +65,18 @@ def jazzer_dependencies(): maybe( http_archive, name = "com_github_johnynek_bazel_jar_jar", - sha256 = "97c5f862482a05f385bd8f9d28a9bbf684b0cf3fae93112ee96f3fb04d34b193", - strip_prefix = "bazel_jar_jar-171f268569384c57c19474b04aebe574d85fde0d", - url = "https://github.com/johnynek/bazel_jar_jar/archive/171f268569384c57c19474b04aebe574d85fde0d.tar.gz", + sha256 = "44dbf93907c361594057bcf1205dab91e1169f3b7a89db8c778161459588b5d6", + strip_prefix = "bazel_jar_jar-commit-171f268569384c57c19474b04aebe574d85fde0d", + url = "https://github.com/CodeIntelligenceTesting/bazel_jar_jar/archive/refs/tags/commit-171f268569384c57c19474b04aebe574d85fde0d.tar.gz", ) maybe( http_archive, name = "com_github_jhalterman_typetools", build_file = Label("//third_party:typetools.BUILD"), - sha256 = "754f46de7d4c278cee2d4dba3c09ebe08fde03d0e67fc85d700611d9cdfb7868", - strip_prefix = "typetools-887153d2a9adf032fac9f145594d0a0248618d48", - url = "https://github.com/jhalterman/typetools/archive/887153d2a9adf032fac9f145594d0a0248618d48.tar.gz", + sha256 = "4e11a613aebb3c35deef58d5d942e44802da1a6c6ef7f127419261f00a0a082c", + strip_prefix = "typetools-commit-887153d2a9adf032fac9f145594d0a0248618d48", + url = "https://github.com/CodeIntelligenceTesting/typetools/archive/refs/tags/commit-887153d2a9adf032fac9f145594d0a0248618d48.tar.gz", ) maybe( @@ -123,9 +123,9 @@ def jazzer_dependencies(): patches = [ Label("//third_party:gflags-use-double-dash-args.patch"), ], - sha256 = "ce2931dd537eaab7dab78b25bec6136a0756ca0b2acbdab9aec0266998c0d9a7", - strip_prefix = "gflags-827c769e5fc98e0f2a34c47cef953cc6328abced", - url = "https://github.com/gflags/gflags/archive/827c769e5fc98e0f2a34c47cef953cc6328abced.tar.gz", + sha256 = "34af2f15cf7367513b352bdcd2493ab14ce43692d2dcd9dfc499492966c64dcf", + strip_prefix = "gflags-2.2.2", + url = "https://github.com/gflags/gflags/archive/refs/tags/v2.2.2.tar.gz", ) maybe( @@ -138,7 +138,7 @@ def jazzer_dependencies(): ], sha256 = "4a3c65b8a8ca58ffcec77288820f557ed93125e8a0b43dd7460b776c58bb8ed9", strip_prefix = "jacoco-0.8.7-jazzer", - url = "https://github.com/CodeIntelligenceTesting/jacoco/archive/v0.8.7-jazzer.tar.gz", + url = "https://github.com/CodeIntelligenceTesting/jacoco/archive/refs/tags/v0.8.7-jazzer.tar.gz", ) maybe( -- cgit v1.2.3 From e44dc83b7f0974b4a1d00f7621dd9c439ba7a40a Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Mon, 14 Feb 2022 11:36:51 +0100 Subject: Synchronize coverage ID generation Even though concurrent fuzzing is not fully supported situations arise in which classes are loaded concurrently (e.g. by used frameworks). To guarantee consistent coverage IDs the provided strategies are now synchronized. --- .../jazzer/agent/CoverageIdStrategy.kt | 103 ++++++++++----------- .../jazzer/agent/RuntimeInstrumentor.kt | 20 ++-- .../com/code_intelligence/jazzer/utils/Utils.kt | 25 +++++ 3 files changed, 87 insertions(+), 61 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/CoverageIdStrategy.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/CoverageIdStrategy.kt index fd2a1e7c..6a6661f5 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/CoverageIdStrategy.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/CoverageIdStrategy.kt @@ -14,7 +14,8 @@ package com.code_intelligence.jazzer.agent -import java.nio.ByteBuffer +import com.code_intelligence.jazzer.utils.append +import com.code_intelligence.jazzer.utils.readFully import java.nio.channels.FileChannel import java.nio.channels.FileLock import java.nio.file.Path @@ -27,56 +28,39 @@ import java.util.UUID internal class CoverageIdException(cause: Throwable? = null) : RuntimeException("Failed to synchronize coverage IDs", cause) +/** + * [CoverageIdStrategy] provides an abstraction to switch between context specific coverage ID generation. + * + * Coverage (i.e., edge) IDs differ from other kinds of IDs, such as those generated for call sites or cmp + * instructions, in that they should be consecutive, collision-free, and lie in a known, small range. + * This precludes us from generating them simply as hashes of class names. + */ interface CoverageIdStrategy { - /** - * Obtain the first coverage ID to be used for the class [className]. - * The caller *must* also call [commitIdCount] once it has instrumented that class, even if instrumentation fails. - */ - @Throws(CoverageIdException::class) - fun obtainFirstId(className: String): Int /** - * Records the number of coverage IDs used to instrument the class specified in a previous call to [obtainFirstId]. - * If instrumenting the class should fail, this function must still be called. In this case, [idCount] is set to 0. + * [withIdForClass] provides the initial coverage ID of the given [className] as parameter to the + * [block] to execute. [block] has to return the number of additionally used IDs. */ @Throws(CoverageIdException::class) - fun commitIdCount(idCount: Int) + fun withIdForClass(className: String, block: (Int) -> Int) } /** - * An unsynchronized strategy for coverage ID generation that simply increments a global counter. + * A memory synced strategy for coverage ID generation. + * + * This strategy uses a synchronized block to guard access to a global edge ID counter. + * Even though concurrent fuzzing is not fully supported this strategy enables consistent coverage + * IDs in case of concurrent class loading. + * + * It only prevents races within one VM instance. */ -internal class TrivialCoverageIdStrategy : CoverageIdStrategy { +internal class MemSyncCoverageIdStrategy : CoverageIdStrategy { private var nextEdgeId = 0 - override fun obtainFirstId(className: String) = nextEdgeId - - override fun commitIdCount(idCount: Int) { - nextEdgeId += idCount - } -} - -/** - * Reads the [FileChannel] to the end as a UTF-8 string. - */ -private fun FileChannel.readFully(): String { - check(size() <= Int.MAX_VALUE) - val buffer = ByteBuffer.allocate(size().toInt()) - while (buffer.hasRemaining()) { - when (read(buffer)) { - 0 -> throw IllegalStateException("No bytes read") - -1 -> break - } + @Synchronized + override fun withIdForClass(className: String, block: (Int) -> Int) { + nextEdgeId += block(nextEdgeId) } - return String(buffer.array()) -} - -/** - * Appends [string] to the end of the [FileChannel]. - */ -private fun FileChannel.append(string: String) { - position(size()) - write(ByteBuffer.wrap(string.toByteArray())) } /** @@ -84,19 +68,30 @@ private fun FileChannel.append(string: String) { * specified [idSyncFile]. * This class takes care of synchronizing the access to the file between multiple processes as long as the general * contract of [CoverageIdStrategy] is followed. - * - * Rationale: Coverage (i.e., edge) IDs differ from other kinds of IDs, such as those generated for call sites or cmp - * instructions, in that they should be consecutive, collision-free, and lie in a known, small range. This precludes us - * from generating them simply as hashes of class names and explains why go through the arduous process of synchronizing - * them across multiple agents. */ -internal class SynchronizedCoverageIdStrategy(private val idSyncFile: Path) : CoverageIdStrategy { - val uuid: UUID = UUID.randomUUID() - var idFileLock: FileLock? = null +internal class FileSyncCoverageIdStrategy(private val idSyncFile: Path) : CoverageIdStrategy { + private val uuid: UUID = UUID.randomUUID() + private var idFileLock: FileLock? = null + + private var cachedFirstId: Int? = null + private var cachedClassName: String? = null + private var cachedIdCount: Int? = null - var cachedFirstId: Int? = null - var cachedClassName: String? = null - var cachedIdCount: Int? = null + /** + * This method is synchronized to prevent concurrent access to the internal file lock which would result in + * [java.nio.channels.OverlappingFileLockException]. Furthermore, every coverage ID obtained by [obtainFirstId] + * is always committed back again to the sync file by [commitIdCount]. + */ + @Synchronized + override fun withIdForClass(className: String, block: (Int) -> Int) { + var actualNumEdgeIds = 0 + try { + val firstId = obtainFirstId(className) + actualNumEdgeIds = block(firstId) + } finally { + commitIdCount(actualNumEdgeIds) + } + } /** * Obtains a coverage ID for [className] such that all cooperating agent processes will obtain the same ID. @@ -108,7 +103,7 @@ internal class SynchronizedCoverageIdStrategy(private val idSyncFile: Path) : Co * In this case, the lock on the file is returned immediately and the extracted first coverage ID is returned to * the caller. The caller is still expected to call [commitIdCount] so that desynchronization can be detected. */ - override fun obtainFirstId(className: String): Int { + private fun obtainFirstId(className: String): Int { try { check(idFileLock == null) { "Already holding a lock on the ID file" } val localIdFile = FileChannel.open( @@ -170,7 +165,11 @@ internal class SynchronizedCoverageIdStrategy(private val idSyncFile: Path) : Co } } - override fun commitIdCount(idCount: Int) { + /** + * Records the number of coverage IDs used to instrument the class specified in a previous call to [obtainFirstId]. + * If instrumenting the class should fail, this function must still be called. In this case, [idCount] is set to 0. + */ + private fun commitIdCount(idCount: Int) { val localIdFileLock = idFileLock try { check(cachedClassName != null) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt index e2283aa2..b4afdcf5 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt @@ -42,9 +42,9 @@ internal class RuntimeInstrumentor( ) : ClassFileTransformer { private val coverageIdSynchronizer = if (idSyncFile != null) - SynchronizedCoverageIdStrategy(idSyncFile) + FileSyncCoverageIdStrategy(idSyncFile) else - TrivialCoverageIdStrategy() + MemSyncCoverageIdStrategy() private val includedHooks = instrumentationTypes .mapNotNull { type -> @@ -165,14 +165,16 @@ internal class RuntimeInstrumentor( // trigger the GEP callbacks for ByteBuffer. traceDataFlow(instrumentationTypes) hooks(includedHooks + customHooks) - val firstId = coverageIdSynchronizer.obtainFirstId(internalClassName) - var actualNumEdgeIds = 0 - try { - actualNumEdgeIds = coverage(firstId) - } finally { - coverageIdSynchronizer.commitIdCount(actualNumEdgeIds) + coverageIdSynchronizer.withIdForClass(internalClassName) { firstId -> + coverage(firstId).also { actualNumEdgeIds -> + CoverageRecorder.recordInstrumentedClass( + internalClassName, + bytecode, + firstId, + firstId + actualNumEdgeIds + ) + } } - CoverageRecorder.recordInstrumentedClass(internalClassName, bytecode, firstId, firstId + actualNumEdgeIds) } else { hooks(customHooks) } diff --git a/agent/src/main/java/com/code_intelligence/jazzer/utils/Utils.kt b/agent/src/main/java/com/code_intelligence/jazzer/utils/Utils.kt index af8cce9b..1b399baf 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/utils/Utils.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/utils/Utils.kt @@ -17,6 +17,8 @@ package com.code_intelligence.jazzer.utils import java.lang.reflect.Executable import java.lang.reflect.Method +import java.nio.ByteBuffer +import java.nio.channels.FileChannel val Class<*>.descriptor: String get() = when { @@ -80,3 +82,26 @@ fun simpleFastHash(vararg strings: String): Int { } return hash } + +/** + * Reads the [FileChannel] to the end as a UTF-8 string. + */ +fun FileChannel.readFully(): String { + check(size() <= Int.MAX_VALUE) + val buffer = ByteBuffer.allocate(size().toInt()) + while (buffer.hasRemaining()) { + when (read(buffer)) { + 0 -> throw IllegalStateException("No bytes read") + -1 -> break + } + } + return String(buffer.array()) +} + +/** + * Appends [string] to the end of the [FileChannel]. + */ +fun FileChannel.append(string: String) { + position(size()) + write(ByteBuffer.wrap(string.toByteArray())) +} -- cgit v1.2.3 From c34c78ec2c1bade15beb453bc792a5a87439daba Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 11 Feb 2022 17:50:26 +0100 Subject: Disallow REPLACE hooks on REPLACE hooks on can't work as only can mark the stack variable as initialized. --- .../main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt | 4 ++++ .../code_intelligence/jazzer/instrumentor/InvalidHookMocks.java | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt index 92106e14..a1709495 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt @@ -55,6 +55,10 @@ class Hook private constructor(hookMethod: Method, annotation: MethodHook) { require(Modifier.isPublic(hookMethod.modifiers)) { "$potentialHook: hook method must be public" } require(Modifier.isStatic(hookMethod.modifiers)) { "$potentialHook: hook method must be static" } + require(hookData.targetMethod != "" || hookData.type != HookType.REPLACE) { + "$potentialHook: REPLACE hooks can't be applied to " + } + // Verify the hook method's parameter count. val numParameters = hookMethod.parameters.size when (hookData.type) { diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java index 2723ad6e..524c1eb0 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java @@ -45,7 +45,7 @@ class InvalidHookMocks { return true; } - @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.StringBuilder", + @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.StringBuilder", targetMethod = "", targetMethodDescriptor = "(Ljava/lang/String;)V") public static Object invalidReturnType(MethodHandle method, Object thisObject, Object[] arguments, int hookId) @@ -58,4 +58,10 @@ class InvalidHookMocks { public static void primitiveReturnValueMustBeWrapped(MethodHandle method, String thisObject, Object[] arguments, int hookId, boolean returnValue) {} + + @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.StringBuilder", + targetMethod = "", targetMethodDescriptor = "(Ljava/lang/String;)V") + public static void + replaceOnInit(MethodHandle method, Object thisObject, Object[] arguments, int hookId) + throws Throwable {} } -- cgit v1.2.3 From 25aad86719d40f16cedf54488c75010200c8d84f Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 15 Feb 2022 12:16:42 +0100 Subject: Update JaCoCo fork to v0.8.7-jazzer+1 This includes fixes required to instrument java.util.regex.**. --- repositories.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/repositories.bzl b/repositories.bzl index 755e5d0e..6ad8f9f5 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -136,9 +136,9 @@ def jazzer_dependencies(): Label("//third_party:jacoco-make-probe-adapter-subclassable.patch"), Label("//third_party:jacoco-make-probe-inserter-subclassable.patch"), ], - sha256 = "4a3c65b8a8ca58ffcec77288820f557ed93125e8a0b43dd7460b776c58bb8ed9", - strip_prefix = "jacoco-0.8.7-jazzer", - url = "https://github.com/CodeIntelligenceTesting/jacoco/archive/refs/tags/v0.8.7-jazzer.tar.gz", + sha256 = "65bfdf38047a3bbefc5f68b180ec4a933068a547d4f569578f3ce3f7355168ba", + strip_prefix = "jacoco-0.8.7-jazzer-1", + url = "https://github.com/CodeIntelligenceTesting/jacoco/archive/refs/tags/v0.8.7-jazzer+1.tar.gz", ) maybe( -- cgit v1.2.3 From 2632f0c4d0dbcacd8989e5b89ab038fcf9ef76b3 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 14 Feb 2022 17:56:13 +0100 Subject: autofuzz: End autofuzz codegen group even after an exception Otherwise, codegen would fail for inputs that produce a finding. --- .../com/code_intelligence/jazzer/autofuzz/Meta.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java index 12669425..66cc59f1 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java @@ -69,9 +69,12 @@ public class Meta { visitor.pushGroup( String.format("%s.", method.getDeclaringClass().getCanonicalName()), "", ""); } - result = autofuzz(data, method, null, visitor); - if (visitor != null) { - visitor.popGroup(); + try { + result = autofuzz(data, method, null, visitor); + } finally { + if (visitor != null) { + visitor.popGroup(); + } } } else { if (visitor != null) { @@ -82,9 +85,12 @@ public class Meta { if (thisObject == null) { throw new AutofuzzConstructionException(); } - result = autofuzz(data, method, thisObject, visitor); - if (visitor != null) { - visitor.popGroup(); + try { + result = autofuzz(data, method, thisObject, visitor); + } finally { + if (visitor != null) { + visitor.popGroup(); + } } } return result; -- cgit v1.2.3 From 95c64f8410885fac693470e44780b113ab1391a8 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 14 Feb 2022 18:17:33 +0100 Subject: autofuzz: Wrap this object in paranthesis --- agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java | 3 ++- .../src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java index 66cc59f1..efa3abc6 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java @@ -79,7 +79,8 @@ public class Meta { } else { if (visitor != null) { // This group will always have two elements: The thisObject and the method call. - visitor.pushGroup("", ".", ""); + // Since the this object can be a complex expression, wrap it in paranthesis. + visitor.pushGroup("(", ").", ""); } Object thisObject = consume(data, method.getDeclaringClass(), visitor); if (thisObject == null) { diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java index 55721c43..40a25d34 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java @@ -169,7 +169,7 @@ public class MetaTest { MetaTest.class.getMethod("isFive", int.class), Collections.singletonList(5)); autofuzzTestCase(false, "com.code_intelligence.jazzer.autofuzz.MetaTest.intEquals(5, 4)", MetaTest.class.getMethod("intEquals", int.class, int.class), Arrays.asList(5, 4)); - autofuzzTestCase("foobar", "\"foo\".concat(\"bar\")", + autofuzzTestCase("foobar", "(\"foo\").concat(\"bar\")", String.class.getMethod("concat", String.class), Arrays.asList((byte) 1, 6, "foo", (byte) 1, 6, "bar")); autofuzzTestCase("jazzer", "new java.lang.String(\"jazzer\")", -- cgit v1.2.3 From 2a75cf76822e793f8b14c09ea1d5f42cb0b3ca34 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 14 Feb 2022 17:56:13 +0100 Subject: autofuzz: Specify type of map entry stream --- .../java/com/code_intelligence/jazzer/autofuzz/Meta.java | 15 ++++++++++----- .../com/code_intelligence/jazzer/autofuzz/MetaTest.java | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java index efa3abc6..33e30839 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java @@ -368,11 +368,6 @@ public class Meta { } return new ByteArrayInputStream(array); } else if (type == Map.class) { - if (visitor != null) { - // Do not use Collectors.toMap() since it cannot handle null values. - visitor.pushGroup("java.util.stream.Stream.of(", ", ", - ").collect(java.util.HashMap::new, (map, e) -> map.put(e.getKey(), e.getValue()), java.util.HashMap::putAll)"); - } ParameterizedType mapType = (ParameterizedType) genericType; if (mapType.getActualTypeArguments().length != 2) { throw new AutofuzzError( @@ -380,6 +375,16 @@ public class Meta { } Type keyType = mapType.getActualTypeArguments()[0]; Type valueType = mapType.getActualTypeArguments()[1]; + if (visitor != null) { + // Do not use Collectors.toMap() since it cannot handle null values. + // Also annotate the type of the entry stream since it might be empty, in which case type + // inference on the accumulator could fail. + visitor.pushGroup( + String.format("java.util.stream.Stream.>of(", + keyType.getTypeName(), valueType.getTypeName()), + ", ", + ").collect(java.util.HashMap::new, (map, e) -> map.put(e.getKey(), e.getValue()), java.util.HashMap::putAll)"); + } int remainingBytesBeforeFirstEntryCreation = data.remainingBytes(); if (visitor != null) { visitor.pushGroup("new java.util.AbstractMap.SimpleEntry<>(", ", ", ")"); diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java index 40a25d34..0906d1d5 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java @@ -126,7 +126,7 @@ public class MetaTest { .collect(java.util.HashMap::new, (map, e) -> map.put(e.getKey(), e.getValue()), java.util.HashMap::putAll); consumeTestCase(stringStringMapType, expectedMap, - "java.util.stream.Stream.of(new java.util.AbstractMap.SimpleEntry<>(\"key0\", \"value0\"), new java.util.AbstractMap.SimpleEntry<>(\"key1\", \"value1\"), new java.util.AbstractMap.SimpleEntry<>(\"key2\", (java.lang.String) null)).collect(java.util.HashMap::new, (map, e) -> map.put(e.getKey(), e.getValue()), java.util.HashMap::putAll)", + "java.util.stream.Stream.>of(new java.util.AbstractMap.SimpleEntry<>(\"key0\", \"value0\"), new java.util.AbstractMap.SimpleEntry<>(\"key1\", \"value1\"), new java.util.AbstractMap.SimpleEntry<>(\"key2\", (java.lang.String) null)).collect(java.util.HashMap::new, (map, e) -> map.put(e.getKey(), e.getValue()), java.util.HashMap::putAll)", Arrays.asList((byte) 1, // do not return null for the map 32, // remaining bytes (byte) 1, // do not return null for the string -- cgit v1.2.3 From 545d45a37b762ad63aa950f46198a3329051123a Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 14 Feb 2022 19:36:57 +0100 Subject: autofuzz: Only fuzz public methods declared by the specified class --- .../java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java index 8c344621..c6ae9523 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java @@ -24,6 +24,7 @@ import java.io.UnsupportedEncodingException; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.net.URLDecoder; import java.util.Arrays; import java.util.List; @@ -96,8 +97,13 @@ public class FuzzTarget { || Utils.getReadableDescriptor(constructor).equals(descriptor)) .toArray(Executable[] ::new); } else { + // We use getDeclaredMethods and filter for the public access modifier instead of using + // getMethods as we want to exclude methods inherited from superclasses or interfaces, which + // can lead to unexpected results when autofuzzing. If desired, these can be autofuzzed + // explicitly instead. targetExecutables = - Arrays.stream(targetClass.getMethods()) + Arrays.stream(targetClass.getDeclaredMethods()) + .filter(method -> Modifier.isPublic(method.getModifiers())) .filter(method -> method.getName().equals(methodName) && (descriptor == null -- cgit v1.2.3 From 481babee61e7aee7fcc55df0b6bfd7b7b7b062a5 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 14 Feb 2022 17:56:13 +0100 Subject: autofuzz: Generate standalone Java reproducers The Autofuzz Java reproducers are based on the existing codegen functionality and have the following advantages compared to the usual Java reproducers generated for Autofuzz findings: * They are completely independent of Jazzer and can thus be debugged easily, even by developers not familiar with Jazzer. * They are stable with respect to the classpath as they encode all choices made during Autofuzz in literal references to classes. * They are human-readable, especially after a straightforward pass through IntelliJ's formatting and code cleanups. --- .../jazzer/autofuzz/FuzzTarget.java | 44 +++++++++++++++++++--- driver/fuzz_target_runner.cpp | 31 ++++++++++++++- examples/BUILD.bazel | 1 + tests/BUILD.bazel | 14 +++++++ 4 files changed, 83 insertions(+), 7 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java index c6ae9523..42b1d67f 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java @@ -20,12 +20,18 @@ import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.code_intelligence.jazzer.utils.SimpleGlobMatcher; import com.code_intelligence.jazzer.utils.Utils; import java.io.Closeable; +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -33,7 +39,12 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -public class FuzzTarget { +public final class FuzzTarget { + private static final String AUTOFUZZ_REPRODUCER_TEMPLATE = "public class Crash_%s {\n" + + " public static void main(String[] args) throws Throwable {\n" + + " %s;\n" + + " }\n" + + "}"; private static final long MAX_EXECUTIONS_WITHOUT_INVOCATION = 100; private static String methodReference; @@ -41,7 +52,6 @@ public class FuzzTarget { private static Map[]> throwsDeclarations; private static Set ignoredExceptionMatchers; private static long executionsSinceLastInvocation = 0; - private static AutofuzzCodegenVisitor codegenVisitor; public static void fuzzerInitialize(String[] args) { if (args.length == 0 || !args[0].contains("::")) { @@ -185,9 +195,36 @@ public class FuzzTarget { } public static void fuzzerTestOneInput(FuzzedDataProvider data) throws Throwable { + AutofuzzCodegenVisitor codegenVisitor = null; if (Meta.isDebug()) { codegenVisitor = new AutofuzzCodegenVisitor(); } + fuzzerTestOneInput(data, codegenVisitor); + if (codegenVisitor != null) { + System.err.println(codegenVisitor.generate()); + } + } + + public static void dumpReproducer(FuzzedDataProvider data, String reproducerPath, String sha) { + AutofuzzCodegenVisitor codegenVisitor = new AutofuzzCodegenVisitor(); + try { + fuzzerTestOneInput(data, codegenVisitor); + } catch (Throwable ignored) { + } + String javaSource = String.format(AUTOFUZZ_REPRODUCER_TEMPLATE, sha, codegenVisitor.generate()); + Path javaPath = Paths.get(reproducerPath, String.format("Crash_%s.java", sha)); + try { + Files.write(javaPath, javaSource.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); + } catch (IOException e) { + System.err.printf("ERROR: Failed to write Java reproducer to %s%n", javaPath); + e.printStackTrace(); + } + System.out.printf( + "reproducer_path='%s'; Java reproducer written to %s%n", reproducerPath, javaPath); + } + + private static void fuzzerTestOneInput( + FuzzedDataProvider data, AutofuzzCodegenVisitor codegenVisitor) throws Throwable { Executable targetExecutable; if (FuzzTarget.targetExecutables.length == 1) { targetExecutable = FuzzTarget.targetExecutables[0]; @@ -202,9 +239,6 @@ public class FuzzTarget { returnValue = Meta.autofuzz(data, (Constructor) targetExecutable, codegenVisitor); } executionsSinceLastInvocation = 0; - if (codegenVisitor != null) { - System.err.println(codegenVisitor.generate()); - } } catch (AutofuzzConstructionException e) { if (Meta.isDebug()) { e.printStackTrace(); diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index 2758a158..a03be0b6 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -356,6 +356,35 @@ jthrowable FuzzTargetRunner::GetFinding() const { void FuzzTargetRunner::DumpReproducer(const uint8_t *data, std::size_t size) { auto &env = jvm_.GetEnv(); + std::string data_sha1 = jazzer::Sha1Hash(data, size); + if (!FLAGS_autofuzz.empty()) { + auto autofuzz_fuzz_target_class = env.FindClass(kAutofuzzFuzzTargetClass); + if (env.ExceptionCheck()) { + env.ExceptionDescribe(); + return; + } + auto dump_reproducer = env.GetStaticMethodID( + autofuzz_fuzz_target_class, "dumpReproducer", + "(Lcom/code_intelligence/jazzer/api/FuzzedDataProvider;Ljava/lang/" + "String;Ljava/lang/String;)V"); + if (env.ExceptionCheck()) { + env.ExceptionDescribe(); + return; + } + FeedFuzzedDataProvider(data, size); + auto reproducer_path_jni = env.NewStringUTF(FLAGS_reproducer_path.c_str()); + auto data_sha1_jni = env.NewStringUTF(data_sha1.c_str()); + env.CallStaticVoidMethod(autofuzz_fuzz_target_class, dump_reproducer, + GetFuzzedDataProviderJavaObject(jvm_), + reproducer_path_jni, data_sha1_jni); + if (env.ExceptionCheck()) { + env.ExceptionDescribe(); + return; + } + env.DeleteLocalRef(data_sha1_jni); + env.DeleteLocalRef(reproducer_path_jni); + return; + } std::string base64_data; if (fuzzer_test_one_input_data_) { // Record the data retrieved from the FuzzedDataProvider and supply it to a @@ -376,8 +405,6 @@ void FuzzTargetRunner::DumpReproducer(const uint8_t *data, std::size_t size) { const char *fuzz_target_call = fuzzer_test_one_input_data_ ? kTestOneInputWithData : kTestOneInputWithBytes; - std::string data_sha1 = jazzer::Sha1Hash(data, size); - // The serialization of recorded FuzzedDataProvider invocations can get to // long to be stored in one String variable in the template. This is // mitigated by chunking the data and concatenating it again in the generated diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index b6cea62e..be6639ad 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -5,6 +5,7 @@ load("//bazel:fuzz_target.bzl", "java_fuzz_target_test") java_fuzz_target_test( name = "Autofuzz", + execute_crash_reproducer = True, fuzzer_args = [ "--autofuzz=com.google.json.JsonSanitizer::sanitize", # Exit after the first finding for testing purposes. diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 4be0c40f..d053561d 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -13,3 +13,17 @@ java_fuzz_target_test( target_class = "com.example.LongStringFuzzer", verify_crash_input = False, ) + +java_fuzz_target_test( + name = "JpegImageParserAutofuzz", + execute_crash_reproducer = True, + fuzzer_args = [ + "--autofuzz=org.apache.commons.imaging.formats.jpeg.JpegImageParser::getBufferedImage", + # Exit after the first finding for testing purposes. + "--keep_going=1", + "--autofuzz_ignore=java.lang.NullPointerException", + ], + runtime_deps = [ + "@maven//:org_apache_commons_commons_imaging", + ], +) -- cgit v1.2.3 From 4c458a4986b823de0cc9d16a6a296120047fc2cd Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 16 Feb 2022 14:05:31 +0100 Subject: Fix javadoc generation instructions (#314) --- deploy/BUILD.bazel | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/BUILD.bazel b/deploy/BUILD.bazel index 29f9a5a1..7cceeae5 100644 --- a/deploy/BUILD.bazel +++ b/deploy/BUILD.bazel @@ -2,8 +2,8 @@ load("@rules_jvm_external//:defs.bzl", "java_export") load("//:maven.bzl", "JAZZER_API_COORDINATES") # To publish a new release of the Jazzer API to Maven, run: -# bazel run --config=maven --define "maven_user=..." --define "maven_password=..." --define gpg_sign=true //:api.publish -# Build //:api-docs.jar to generate javadocs for the API. +# bazel run --config=maven --define "maven_user=..." --define "maven_password=..." --define gpg_sign=true //deploy:api.publish +# Build //deploy:api-docs to generate javadocs for the API. java_export( name = "api", maven_coordinates = JAZZER_API_COORDINATES, -- cgit v1.2.3 From f0f0c5940e0a29e7e67633c612736b506207814e Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Wed, 16 Feb 2022 13:34:30 +0100 Subject: Support REPLACE hooks for constructors --- .../code_intelligence/jazzer/instrumentor/Hook.kt | 13 +++++------ .../jazzer/instrumentor/HookMethodVisitor.kt | 6 +++++ .../jazzer/instrumentor/BUILD.bazel | 1 + .../jazzer/instrumentor/InvalidHookMocks.java | 13 +++++++++-- .../jazzer/instrumentor/ReplaceHooks.java | 20 +++++++++++++++++ .../jazzer/instrumentor/ReplaceHooksInit.java | 26 ++++++++++++++++++++++ .../jazzer/instrumentor/ReplaceHooksTarget.java | 3 +++ 7 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksInit.java diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt index a1709495..34d9309a 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt @@ -55,10 +55,6 @@ class Hook private constructor(hookMethod: Method, annotation: MethodHook) { require(Modifier.isPublic(hookMethod.modifiers)) { "$potentialHook: hook method must be public" } require(Modifier.isStatic(hookMethod.modifiers)) { "$potentialHook: hook method must be static" } - require(hookData.targetMethod != "" || hookData.type != HookType.REPLACE) { - "$potentialHook: REPLACE hooks can't be applied to " - } - // Verify the hook method's parameter count. val numParameters = hookMethod.parameters.size when (hookData.type) { @@ -79,12 +75,13 @@ class Hook private constructor(hookMethod: Method, annotation: MethodHook) { "$potentialHook: return type must be void" } HookType.REPLACE -> if (potentialHook.targetReturnTypeDescriptor != null) { - val returnTypeDescriptor = hookMethod.returnType.descriptor - if (potentialHook.targetReturnTypeDescriptor == "V") { - require(returnTypeDescriptor == "V") { "$potentialHook: return type must be void to match targetMethodDescriptor" } + if (hookData.targetMethod == "") { + require(hookMethod.returnType.name == potentialHook.targetClassName) { "$potentialHook: return type must be ${potentialHook.targetClassName} to match target constructor" } + } else if (potentialHook.targetReturnTypeDescriptor == "V") { + require(hookMethod.returnType.descriptor == "V") { "$potentialHook: return type must be void to match targetMethodDescriptor" } } else { require( - returnTypeDescriptor in listOf( + hookMethod.returnType.descriptor in listOf( java.lang.Object::class.java.descriptor, potentialHook.targetReturnTypeDescriptor, potentialHook.targetWrappedReturnTypeDescriptor diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt index 0c0f0d01..b2e2cd04 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt @@ -144,6 +144,12 @@ private class HookMethodVisitor( // Start to build the arguments for the hook method. if (methodName == "") { + // Constructor is invoked on an uninitialized object, and that's still on the stack. + // In case of REPLACE pop it from the stack and replace it afterwards with the returned + // one from the hook. + if (hook.hookType == HookType.REPLACE) { + mv.visitInsn(Opcodes.POP) + } // Special case for constructors: // We cannot create a MethodHandle for a constructor, so we push null instead. mv.visitInsn(Opcodes.ACONST_NULL) // push nullref diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel index 472d2b98..1bdcd194 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -130,6 +130,7 @@ wrapped_kt_jvm_test( size = "small", srcs = [ "ReplaceHooks.java", + "ReplaceHooksInit.java", "ReplaceHooksPatchTest.kt", "ReplaceHooksTarget.java", "ReplaceHooksTargetContract.java", diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java index 524c1eb0..551e7781 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java @@ -18,6 +18,7 @@ import com.code_intelligence.jazzer.api.HookType; import com.code_intelligence.jazzer.api.MethodHook; import java.lang.invoke.MethodHandle; +@SuppressWarnings({"unused", "RedundantThrows"}) class InvalidHookMocks { @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.String", targetMethod = "equals") public static void incorrectHookIdType( @@ -62,6 +63,14 @@ class InvalidHookMocks { @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.StringBuilder", targetMethod = "", targetMethodDescriptor = "(Ljava/lang/String;)V") public static void - replaceOnInit(MethodHandle method, Object thisObject, Object[] arguments, int hookId) - throws Throwable {} + replaceOnInitWithoutReturnType( + MethodHandle method, Object thisObject, Object[] arguments, int hookId) throws Throwable {} + + @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.StringBuilder", + targetMethod = "", targetMethodDescriptor = "(Ljava/lang/String;)V") + public static Object + replaceOnInitWithIncompatibleType( + MethodHandle method, Object thisObject, Object[] arguments, int hookId) throws Throwable { + return new Object(); + } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java index a71e1180..f33cdc43 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java @@ -18,6 +18,7 @@ import com.code_intelligence.jazzer.api.HookType; import com.code_intelligence.jazzer.api.MethodHook; import java.lang.invoke.MethodHandle; +@SuppressWarnings("unused") public class ReplaceHooks { @MethodHook(type = HookType.REPLACE, targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget", @@ -106,4 +107,23 @@ public class ReplaceHooks { patchAbstractListGet(MethodHandle method, Object thisObject, Object[] arguments, int hookId) { return true; } + + @MethodHook(type = HookType.REPLACE, + targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksInit", + targetMethod = "", targetMethodDescriptor = "()V") + public static ReplaceHooksInit + patchInit(MethodHandle method, Object thisObject, Object[] arguments, int hookId) { + // Test with subclass + return new ReplaceHooksInit() { + { initialized = true; } + }; + } + + @MethodHook(type = HookType.REPLACE, + targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksInit", + targetMethod = "", targetMethodDescriptor = "(ZLjava/lang/String;)V") + public static ReplaceHooksInit + patchInitWithParams(MethodHandle method, Object thisObject, Object[] arguments, int hookId) { + return new ReplaceHooksInit(true, ""); + } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksInit.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksInit.java new file mode 100644 index 00000000..da77be81 --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksInit.java @@ -0,0 +1,26 @@ +// 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.instrumentor; + +public class ReplaceHooksInit { + public boolean initialized; + + public ReplaceHooksInit() {} + + @SuppressWarnings("unused") + public ReplaceHooksInit(boolean initialized, String ignored) { + this.initialized = initialized; + } +} diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java index 7a4b89f8..19e89ff1 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java @@ -60,6 +60,9 @@ public class ReplaceHooksTarget implements ReplaceHooksTargetContract { boolList.add(false); results.put("arrayListGet", boolList.get(0)); + results.put("shouldInitialize", new ReplaceHooksInit().initialized); + results.put("shouldInitializeWithParams", new ReplaceHooksInit(false, "foo").initialized); + return results; } -- cgit v1.2.3 From 86d67a6810cf983ce5361592580aa5862dccca2a Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Mon, 14 Feb 2022 15:29:17 +0100 Subject: Add additionalClassesToHook field to MethodHook annotation This field allows the declaration of classes to additionally include in the hook instrumentation, that would normally be ignored. --- .../com/code_intelligence/jazzer/agent/Agent.kt | 8 ++++---- .../jazzer/agent/RuntimeInstrumentor.kt | 23 +++++++++++++++++----- .../code_intelligence/jazzer/api/MethodHook.java | 20 +++++++++++++++---- .../code_intelligence/jazzer/instrumentor/Hook.kt | 7 ++++--- .../jazzer/utils/ClassNameGlobber.kt | 5 ++++- 5 files changed, 46 insertions(+), 17 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index 33d02263..e9db0187 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -83,7 +83,7 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { (argumentMap["instrumentation_excludes"] ?: emptyList()) + customHookNames ) CoverageRecorder.classNameGlobber = classNameGlobber - val dependencyClassNameGlobber = ClassNameGlobber( + val customHookClassNameGlobber = ClassNameGlobber( argumentMap["custom_hook_includes"] ?: emptyList(), (argumentMap["custom_hook_excludes"] ?: emptyList()) + customHookNames ) @@ -123,7 +123,7 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { val runtimeInstrumentor = RuntimeInstrumentor( instrumentation, classNameGlobber, - dependencyClassNameGlobber, + customHookClassNameGlobber, instrumentationTypes, idSyncFile, dumpClassesDir, @@ -134,7 +134,7 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { val relevantClassesLoadedBeforeCustomHooks = instrumentation.allLoadedClasses .map { it.name } - .filter { classNameGlobber.includes(it) || dependencyClassNameGlobber.includes(it) } + .filter { classNameGlobber.includes(it) || customHookClassNameGlobber.includes(it) } .toSet() val customHooks = customHookNames.toSet().flatMap { hookClassName -> try { @@ -148,7 +148,7 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { } val relevantClassesLoadedAfterCustomHooks = instrumentation.allLoadedClasses .map { it.name } - .filter { classNameGlobber.includes(it) || dependencyClassNameGlobber.includes(it) } + .filter { classNameGlobber.includes(it) || customHookClassNameGlobber.includes(it) } .toSet() val nonHookClassesLoadedByHooks = relevantClassesLoadedAfterCustomHooks - relevantClassesLoadedBeforeCustomHooks if (nonHookClassesLoadedByHooks.isNotEmpty()) { diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt index b4afdcf5..5c577eab 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt @@ -34,8 +34,8 @@ import kotlin.time.measureTimedValue internal class RuntimeInstrumentor( private val instrumentation: Instrumentation, - private val classesToInstrument: ClassNameGlobber, - private val dependencyClassesToInstrument: ClassNameGlobber, + private val classesToFullyInstrument: ClassNameGlobber, + private val classesToHookInstrument: ClassNameGlobber, private val instrumentationTypes: Set, idSyncFile: Path?, private val dumpClassesDir: Path?, @@ -57,10 +57,22 @@ internal class RuntimeInstrumentor( } } .flatMap { loadHooks(it) } - private val customHooks = emptyList().toMutableList() + + // Dedicated name globber for additional classes to hook stated in hook annotations is needed due to + // existing include and exclude pattern of classesToHookInstrument. All classes are included in hook + // instrumentation except the ones from default excludes, like JDK and Kotlin classes. But additional + // classes to hook, based on annotations, are allowed to reference normally ignored ones, like JDK + // and Kotlin internals. + // FIXME: Adding an additional class to hook will apply _all_ hooks to it and not only the one it's + // defined in. At some point we might want to track the list of classes per custom hook rather than globally. + private var additionalClassesToHookInstrument: ClassNameGlobber = ClassNameGlobber(emptyList(), listOf()) + private val customHooks = mutableListOf() fun registerCustomHooks(hooks: List) { customHooks.addAll(hooks) + additionalClassesToHookInstrument = additionalClassesToHookInstrument.withAdditionalIncludes( + hooks.flatMap(Hook::additionalClassesToHook) + ) } @OptIn(kotlin.time.ExperimentalTime::class) @@ -128,8 +140,9 @@ internal class RuntimeInstrumentor( @OptIn(kotlin.time.ExperimentalTime::class) fun transformInternal(internalClassName: String, classfileBuffer: ByteArray): ByteArray? { val fullInstrumentation = when { - classesToInstrument.includes(internalClassName) -> true - dependencyClassesToInstrument.includes(internalClassName) -> false + classesToFullyInstrument.includes(internalClassName) -> true + classesToHookInstrument.includes(internalClassName) -> false + additionalClassesToHookInstrument.includes(internalClassName) -> false else -> return null } val prettyClassName = internalClassName.replace('/', '.') diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java b/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java index 0d17a4a0..108d1151 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java @@ -23,11 +23,12 @@ import java.lang.annotation.Target; import java.lang.invoke.MethodType; /** - * Registers this method as a hook that should run after the method - * specified by the annotation parameters has returned. + * Registers the annotated method as a hook that should run before, instead or + * after the method specified by the annotation parameters. *

- * This method will be called after every call to the target method and has - * access to its return value. The target method is specified by + * Depending on {@link #type()} this method will be called after, instead or + * before every call to the target method and has + * access to its parameters and return value. The target method is specified by * {@link #targetClassName()} and {@link #targetMethod()}. In case of an * overloaded method, {@link #targetMethodDescriptor()} can be used to restrict * the application of the hook to a particular overload. @@ -180,4 +181,15 @@ public @interface MethodHook { * @return the descriptor of the method to be hooked */ String targetMethodDescriptor() default ""; + + /** + * Array of additional classes to hook. + *

+ * Hooks are applied on call sites. This means that classes calling the one + * defined in this annotation need to be instrumented to actually execute + * the hook. This property can be used to hook normally ignored classes. + * + * @return fully qualified class names to hook + */ + String[] additionalClassesToHook() default {}; } diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt index 34d9309a..46800f82 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt @@ -28,9 +28,10 @@ class Hook private constructor(hookMethod: Method, annotation: MethodHook) { // Allowing arbitrary exterior whitespace in the target class name allows for an easy workaround // for mangled hooks due to shading applied to hooks. private val targetClassName = annotation.targetClassName.trim() - val targetMethodName = annotation.targetMethod - val targetMethodDescriptor = annotation.targetMethodDescriptor.takeIf { it.isNotEmpty() } val hookType = annotation.type + val targetMethodName = annotation.targetMethod + val targetMethodDescriptor = annotation.targetMethodDescriptor.takeIf { it.isNotBlank() } + val additionalClassesToHook = annotation.additionalClassesToHook.asList() val targetInternalClassName = targetClassName.replace('.', '/') private val targetReturnTypeDescriptor = targetMethodDescriptor?.let { extractReturnTypeDescriptor(it) } @@ -42,7 +43,7 @@ class Hook private constructor(hookMethod: Method, annotation: MethodHook) { val hookMethodDescriptor = hookMethod.descriptor override fun toString(): String { - return "$hookType $targetClassName.$targetMethodName: $hookClassName.$hookMethodName" + return "$hookType $targetClassName.$targetMethodName: $hookClassName.$hookMethodName $additionalClassesToHook" } companion object { diff --git a/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt b/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt index 1f09afe3..daf4409e 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt @@ -33,7 +33,7 @@ private val BASE_EXCLUDED_CLASS_NAME_GLOBS = listOf( "sun.**", ) -class ClassNameGlobber(includes: List, excludes: List) { +class ClassNameGlobber(private val includes: List, private val excludes: List) { // If no include globs are provided, start with all classes. private val includeMatchers = (if (includes.isEmpty()) BASE_INCLUDED_CLASS_NAME_GLOBS else includes) .map(::SimpleGlobMatcher) @@ -45,6 +45,9 @@ class ClassNameGlobber(includes: List, excludes: List) { fun includes(className: String): Boolean { return includeMatchers.any { it.matches(className) } && excludeMatchers.none { it.matches(className) } } + + fun withAdditionalIncludes(includes: List): ClassNameGlobber = + ClassNameGlobber(this.includes + includes, excludes) } class SimpleGlobMatcher(val glob: String) { -- cgit v1.2.3 From fe0d99327ad02ba02b0882c767ae5a67364d90b4 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Mon, 14 Feb 2022 15:37:24 +0100 Subject: Add OS command injection bug detector This bug detector checks if fuzzer provided data reaches os command execution code. --- sanitizers/sanitizers.bzl | 1 + .../jazzer/sanitizers/BUILD.bazel | 1 + .../jazzer/sanitizers/OsCommandInjection.kt | 58 ++++++++++++++++++++++ sanitizers/src/test/java/com/example/BUILD.bazel | 16 ++++++ .../example/OsCommandInjectionProcessBuilder.java | 34 +++++++++++++ .../com/example/OsCommandInjectionRuntimeExec.java | 36 ++++++++++++++ 6 files changed, 146 insertions(+) create mode 100644 sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/OsCommandInjection.kt create mode 100644 sanitizers/src/test/java/com/example/OsCommandInjectionProcessBuilder.java create mode 100644 sanitizers/src/test/java/com/example/OsCommandInjectionRuntimeExec.java diff --git a/sanitizers/sanitizers.bzl b/sanitizers/sanitizers.bzl index 8bdea7a9..c29a5926 100644 --- a/sanitizers/sanitizers.bzl +++ b/sanitizers/sanitizers.bzl @@ -18,6 +18,7 @@ _sanitizer_class_names = [ "Deserialization", "ExpressionLanguageInjection", "NamingContextLookup", + "OsCommandInjection", "ReflectiveCall", ] diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel index 65480653..f0628493 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel @@ -6,6 +6,7 @@ kt_jvm_library( "Deserialization.kt", "ExpressionLanguageInjection.kt", "NamingContextLookup.kt", + "OsCommandInjection.kt", "ReflectiveCall.kt", "Utils.kt", ], diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/OsCommandInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/OsCommandInjection.kt new file mode 100644 index 00000000..f058c9fc --- /dev/null +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/OsCommandInjection.kt @@ -0,0 +1,58 @@ +// 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.sanitizers + +import com.code_intelligence.jazzer.api.* +import java.lang.invoke.MethodHandle + +/** + * Detects unsafe execution of OS commands using [ProcessBuilder]. + * Executing OS commands based on attacker-controlled data could lead to arbitrary could execution. + * + * All public methods providing the command to execute end up in [java.lang.ProcessImpl.start], + * so calls to this method are hooked. + * Only the first entry of the given command array is analyzed. It states the executable and must + * not include attacker provided data. + */ +@Suppress("unused_parameter", "unused") +object OsCommandInjection { + + // Short and probably non-existing command name + private const val COMMAND = "jazze" + + @MethodHook( + type = HookType.BEFORE, + targetClassName = "java.lang.ProcessImpl", + targetMethod = "start", + additionalClassesToHook = ["java.lang.ProcessBuilder"] + ) + @JvmStatic + fun processImplStartHook(method: MethodHandle?, alwaysNull: Any?, args: Array, hookId: Int) { + // Calling ProcessBuilder already checks if command array is empty + @Suppress("UNCHECKED_CAST") + (args[0] as? Array)?.first().let { cmd -> + if (cmd == COMMAND) { + Jazzer.reportFindingFromHook( + FuzzerSecurityIssueCritical( + """OS Command Injection +Executing OS commands with attacker-controlled data can lead to remote code execution.""" + ) + ) + } else { + Jazzer.guideTowardsEquality(cmd, COMMAND, hookId) + } + } + } +} diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index d148545a..2b3d577a 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -30,3 +30,19 @@ java_fuzz_target_test( "@maven//:org_hibernate_hibernate_validator", ], ) + +java_fuzz_target_test( + name = "OsCommandInjectionProcessBuilder", + srcs = [ + "OsCommandInjectionProcessBuilder.java", + ], + target_class = "com.example.OsCommandInjectionProcessBuilder", +) + +java_fuzz_target_test( + name = "OsCommandInjectionRuntimeExec", + srcs = [ + "OsCommandInjectionRuntimeExec.java", + ], + target_class = "com.example.OsCommandInjectionRuntimeExec", +) diff --git a/sanitizers/src/test/java/com/example/OsCommandInjectionProcessBuilder.java b/sanitizers/src/test/java/com/example/OsCommandInjectionProcessBuilder.java new file mode 100644 index 00000000..6e0a046c --- /dev/null +++ b/sanitizers/src/test/java/com/example/OsCommandInjectionProcessBuilder.java @@ -0,0 +1,34 @@ +// 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.example; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +public class OsCommandInjectionProcessBuilder { + public static void fuzzerTestOneInput(FuzzedDataProvider data) { + String input = data.consumeRemainingAsAsciiString(); + try { + Process process = new ProcessBuilder(input).start(); + // This should be way faster, but we have to wait until the call is done + if (!process.waitFor(10, TimeUnit.MILLISECONDS)) { + process.destroyForcibly(); + } + } catch (Exception ignored) { + // Ignore execution and setup exceptions + } + } +} diff --git a/sanitizers/src/test/java/com/example/OsCommandInjectionRuntimeExec.java b/sanitizers/src/test/java/com/example/OsCommandInjectionRuntimeExec.java new file mode 100644 index 00000000..1f6aeb58 --- /dev/null +++ b/sanitizers/src/test/java/com/example/OsCommandInjectionRuntimeExec.java @@ -0,0 +1,36 @@ +// 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.example; + +import static java.lang.Runtime.getRuntime; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +public class OsCommandInjectionRuntimeExec { + public static void fuzzerTestOneInput(FuzzedDataProvider data) { + String input = data.consumeRemainingAsAsciiString(); + try { + Process process = getRuntime().exec(input); + // This should be way faster, but we have to wait until the call is done + if (!process.waitFor(10, TimeUnit.MILLISECONDS)) { + process.destroyForcibly(); + } + } catch (Exception ignored) { + // Ignore execution and setup exceptions + } + } +} -- cgit v1.2.3 From 3341515f2228816404a11756e0cf5c99766a6761 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 16 Feb 2022 16:01:10 +0100 Subject: Fall back to non-rlocation paths in FuzzTargetTestWrapper Without this fallback, users can no longer pass in e.g. paths to corpus directories into the examples supplied with Jazzer. --- .../jazzer/tools/FuzzTargetTestWrapper.java | 31 +++++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java b/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java index 14eaf0ec..c78464ea 100644 --- a/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java +++ b/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java @@ -45,9 +45,9 @@ public class FuzzTargetTestWrapper { boolean executeCrashReproducer; try { runfiles = Runfiles.create(); - driverActualPath = runfiles.rlocation(rlocationPath(args[0])); - apiActualPath = runfiles.rlocation(rlocationPath(args[1])); - jarActualPath = runfiles.rlocation(rlocationPath(args[2])); + driverActualPath = lookUpRunfile(runfiles, args[0]); + apiActualPath = lookUpRunfile(runfiles, args[1]); + jarActualPath = lookUpRunfile(runfiles, args[2]); verifyCrashInput = Boolean.parseBoolean(args[3]); verifyCrashReproducer = Boolean.parseBoolean(args[4]); executeCrashReproducer = Boolean.parseBoolean(args[5]); @@ -68,7 +68,7 @@ public class FuzzTargetTestWrapper { // Map all files/dirs to real location Stream arguments = Arrays.stream(args).skip(6).map( - arg -> arg.startsWith("-") ? arg : runfiles.rlocation(rlocationPath(arg))); + arg -> arg.startsWith("-") ? arg : lookUpRunfileWithFallback(runfiles, arg)); List command = Stream @@ -119,6 +119,29 @@ public class FuzzTargetTestWrapper { System.exit(0); } + // Looks up a Bazel "rootpath" in this binary's runfiles and returns the resulting path. + private static String lookUpRunfile(Runfiles runfiles, String rootpath) { + return runfiles.rlocation(rlocationPath(rootpath)); + } + + // Looks up a Bazel "rootpath" in this binary's runfiles and returns the resulting path if it + // exists. If not, returns the original path unmodified. + private static String lookUpRunfileWithFallback(Runfiles runfiles, String rootpath) { + String candidatePath; + try { + candidatePath = lookUpRunfile(runfiles, rootpath); + } catch (IllegalArgumentException unused) { + // The argument to Runfiles.rlocation had an invalid format, which indicates that rootpath + // is not a Bazel "rootpath" but a user-supplied path that should be returned unchanged. + return rootpath; + } + if (new File(candidatePath).exists()) { + return candidatePath; + } else { + return rootpath; + } + } + // Turns the result of Bazel's `$(rootpath ...)` into the correct format for rlocation. private static String rlocationPath(String rootpath) { if (rootpath.startsWith("external/")) { -- cgit v1.2.3 From 9cd3da6848f736b4d436df72173809135cf02df1 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 16 Feb 2022 15:47:04 +0100 Subject: Add a sanitizer for regex injection with CANON_EQ flag --- sanitizers/sanitizers.bzl | 1 + .../jazzer/sanitizers/BUILD.bazel | 1 + .../jazzer/sanitizers/RegexInjection.kt | 66 ++++++++++++++++++++++ sanitizers/src/test/java/com/example/BUILD.bazel | 6 ++ .../java/com/example/RegexCanonEqInjection.java | 29 ++++++++++ 5 files changed, 103 insertions(+) create mode 100644 sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt create mode 100644 sanitizers/src/test/java/com/example/RegexCanonEqInjection.java diff --git a/sanitizers/sanitizers.bzl b/sanitizers/sanitizers.bzl index c29a5926..34367d0a 100644 --- a/sanitizers/sanitizers.bzl +++ b/sanitizers/sanitizers.bzl @@ -20,6 +20,7 @@ _sanitizer_class_names = [ "NamingContextLookup", "OsCommandInjection", "ReflectiveCall", + "RegexInjection", ] SANITIZER_CLASSES = [_sanitizer_package_prefix + class_name for class_name in _sanitizer_class_names] diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel index f0628493..f4d0f7ae 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel @@ -8,6 +8,7 @@ kt_jvm_library( "NamingContextLookup.kt", "OsCommandInjection.kt", "ReflectiveCall.kt", + "RegexInjection.kt", "Utils.kt", ], visibility = ["//sanitizers:__pkg__"], diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt new file mode 100644 index 00000000..34a2d7e8 --- /dev/null +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt @@ -0,0 +1,66 @@ +// Copyright 2022 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.sanitizers + +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow +import com.code_intelligence.jazzer.api.HookType +import com.code_intelligence.jazzer.api.Jazzer +import com.code_intelligence.jazzer.api.MethodHook +import java.lang.invoke.MethodHandle + +@Suppress("unused_parameter", "unused") +object RegexInjection { + // Inlined value of java.util.regex.Pattern.CANON_EQ to prevent a runtime dependency on Pattern. + private const val PATTERN_CANON_EQ = 0x80 + + /** + * Part of an OOM "exploit" for [java.util.regex.Pattern.compile] with the + * [java.util.regex.Pattern.CANON_EQ] flag, formed by three consecutive combining marks, in this + * case grave accents: ◌̀. + * See [patternCompileWithFlagsHook] for details. + */ + private const val CANON_EQ_ALMOST_EXPLOIT = "\u0300\u0300\u0300" + + // With CANON_EQ enabled, Pattern.compile allocates an array with a size that is + // (super-)exponential in the number of consecutive Unicode combining marks. We use a mild case + // of this as a magic string based on which we trigger a finding. + // Note: The fuzzer might trigger an OutOfMemoryError or NegativeArraySizeException (if the size + // of the array overflows an int) by chance before it correctly emits this "exploit". In that + // case, we report the original exception instead. + @MethodHook( + type = HookType.BEFORE, + targetClassName = "java.util.regex.Pattern", + targetMethod = "compile", + targetMethodDescriptor = "(Ljava/lang/String;I)Ljava/util/regex/Pattern;" + ) + @JvmStatic + fun patternCompileWithFlagsHook(method: MethodHandle?, alwaysNull: Any?, args: Array, hookId: Int) { + val pattern = args[0] as? String ?: return + val flags = args[1] as? Int ?: return + if (flags and PATTERN_CANON_EQ == 0) return + if (pattern.contains(CANON_EQ_ALMOST_EXPLOIT)) { + Jazzer.reportFindingFromHook( + FuzzerSecurityIssueLow( + """Regular Expression Injection with CANON_EQ +When java.util.regex.Pattern.compile is used with the Pattern.CANON_EQ flag, +every injection into the regular expression pattern can cause arbitrarily large +memory allocations, even when wrapped with Pattern.quote(...).""" + ) + ) + } else { + Jazzer.guideTowardsContainment(pattern, CANON_EQ_ALMOST_EXPLOIT, hookId) + } + } +} diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index 2b3d577a..d1edfab7 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -46,3 +46,9 @@ java_fuzz_target_test( ], target_class = "com.example.OsCommandInjectionRuntimeExec", ) + +java_fuzz_target_test( + name = "RegexCanonEqInjection", + srcs = ["RegexCanonEqInjection.java"], + target_class = "com.example.RegexCanonEqInjection", +) diff --git a/sanitizers/src/test/java/com/example/RegexCanonEqInjection.java b/sanitizers/src/test/java/com/example/RegexCanonEqInjection.java new file mode 100644 index 00000000..f7b906e7 --- /dev/null +++ b/sanitizers/src/test/java/com/example/RegexCanonEqInjection.java @@ -0,0 +1,29 @@ +// Copyright 2022 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.example; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +public class RegexCanonEqInjection { + public static void fuzzerTestOneInput(FuzzedDataProvider data) { + String input = data.consumeRemainingAsString(); + try { + Pattern.compile(Pattern.quote(input), Pattern.CANON_EQ); + } catch (PatternSyntaxException ignored) { + } + } +} -- cgit v1.2.3 From 27d6cabe670629fe3079ea9d6fe99cb407a49f28 Mon Sep 17 00:00:00 2001 From: yawkat Date: Thu, 17 Feb 2022 15:26:34 +0100 Subject: Fix infinite loop in CoverageMap --- .../src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java index 9bf0b395..cdd27ef3 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java @@ -73,7 +73,7 @@ final public class CoverageMap { public static void enlargeIfNeeded(int nextId) { int newNumCounters = currentNumCounters; while (nextId >= newNumCounters) { - newNumCounters = 2 * currentNumCounters; + newNumCounters = 2 * newNumCounters; if (newNumCounters > MAX_NUM_COUNTERS) { System.out.printf("ERROR: Maximum number (%s) of coverage counters exceeded. Try to%n" + " limit the scope of a single fuzz target as much as possible to keep the%n" -- cgit v1.2.3 From 0651b5bcc9b97db8202a7036f0ce72b0ae581769 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 16 Feb 2022 22:41:21 +0100 Subject: Only trim reflection and Autofuzz classes from stack trace Trimming all classes in java.** and jdk.** from the stack trace made it useless when fuzzing the Java standard library itself. Also fixes the matching of Autofuzz classes. --- .../java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java index 42b1d67f..b036f538 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java @@ -285,8 +285,8 @@ public final class FuzzTarget { } } - // Removes all stack trace elements that live in the Java standard library, internal JDK classes - // or the autofuzz package from the bottom of all stack frames. + // Removes all stack trace elements that live in the Java reflection packages or the autofuzz + // package from the bottom of all stack frames. private static void cleanStackTraces(Throwable t) { Throwable cause = t; while (cause != null) { @@ -295,8 +295,9 @@ public final class FuzzTarget { for (firstInterestingPos = elements.length - 1; firstInterestingPos > 0; firstInterestingPos--) { String className = elements[firstInterestingPos].getClassName(); - if (!className.startsWith("com.code_intelligence.jazzer.autofuzz") - && !className.startsWith("java.") && !className.startsWith("jdk.")) { + if (!className.startsWith("com.code_intelligence.jazzer.autofuzz.") + && !className.startsWith("java.lang.reflect.") + && !className.startsWith("jdk.internal.reflect.")) { break; } } -- cgit v1.2.3 From f44705b94dd6448674c167e1614189fb2ad610d1 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sun, 6 Feb 2022 16:03:35 +0100 Subject: Fix Kotlin runtime version mismatch in Kotlin fuzz targets --- WORKSPACE.bazel | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index 7a7d4b5a..8cf3953f 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -97,6 +97,10 @@ maven_install( artifacts = MAVEN_ARTIFACTS, fail_if_repin_required = True, maven_install_json = "//:maven_install.json", + override_targets = { + "org.jetbrains.kotlin:kotlin-reflect": "@com_github_jetbrains_kotlin//:kotlin-reflect", + "org.jetbrains.kotlin:kotlin-stdlib": "@com_github_jetbrains_kotlin//:kotlin-stdlib", + }, repositories = [ "https://repo1.maven.org/maven2", ], -- cgit v1.2.3 From 4c1ad3f1508c9587b81f779c8173585acef0a16f Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 18 Feb 2022 17:44:39 +0100 Subject: Warn when additionalClassesToHook are not hooked This can happen when a hook depends on the classes to hook, e.g. because it references the classes in a static initializer. If it turns out that this is necessary, we would have to consider reinstrumentation. --- .../main/java/com/code_intelligence/jazzer/agent/Agent.kt | 14 +++++++++----- .../code_intelligence/jazzer/agent/RuntimeInstrumentor.kt | 5 ++++- .../code_intelligence/jazzer/sanitizers/utils/BUILD.bazel | 0 3 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils/BUILD.bazel diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index e9db0187..3c20e4b7 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -132,7 +132,7 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { addTransformer(runtimeInstrumentor) } - val relevantClassesLoadedBeforeCustomHooks = instrumentation.allLoadedClasses + val classesLoadedBeforeCustomHooks = instrumentation.allLoadedClasses .map { it.name } .filter { classNameGlobber.includes(it) || customHookClassNameGlobber.includes(it) } .toSet() @@ -146,15 +146,19 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { emptySet() } } + val additionalClassesToHookInstrumentor = runtimeInstrumentor.registerCustomHooks(customHooks) val relevantClassesLoadedAfterCustomHooks = instrumentation.allLoadedClasses .map { it.name } - .filter { classNameGlobber.includes(it) || customHookClassNameGlobber.includes(it) } + .filter { + classNameGlobber.includes(it) || + customHookClassNameGlobber.includes(it) || + additionalClassesToHookInstrumentor.includes(it) + } .toSet() - val nonHookClassesLoadedByHooks = relevantClassesLoadedAfterCustomHooks - relevantClassesLoadedBeforeCustomHooks + val nonHookClassesLoadedByHooks = + relevantClassesLoadedAfterCustomHooks - classesLoadedBeforeCustomHooks if (nonHookClassesLoadedByHooks.isNotEmpty()) { println("WARN: Hooks were not applied to the following classes as they are dependencies of hooks:") println("WARN: ${nonHookClassesLoadedByHooks.joinToString()}") } - - runtimeInstrumentor.registerCustomHooks(customHooks) } diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt index 5c577eab..e3f4fec6 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt @@ -68,11 +68,14 @@ internal class RuntimeInstrumentor( private var additionalClassesToHookInstrument: ClassNameGlobber = ClassNameGlobber(emptyList(), listOf()) private val customHooks = mutableListOf() - fun registerCustomHooks(hooks: List) { + // Returns the globber of all classes to be instrumented with hooks that were added upon a + // hook's request. + fun registerCustomHooks(hooks: List): ClassNameGlobber { customHooks.addAll(hooks) additionalClassesToHookInstrument = additionalClassesToHookInstrument.withAdditionalIncludes( hooks.flatMap(Hook::additionalClassesToHook) ) + return additionalClassesToHookInstrument } @OptIn(kotlin.time.ExperimentalTime::class) diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils/BUILD.bazel b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils/BUILD.bazel new file mode 100644 index 00000000..e69de29b -- cgit v1.2.3 From 0f1952ec2be21100be6e1a26a4fab65652ce08e4 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 18 Feb 2022 17:47:11 +0100 Subject: Reformat Agent.kt --- .../main/java/com/code_intelligence/jazzer/agent/Agent.kt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index 3c20e4b7..ccd416e9 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -41,11 +41,13 @@ val KNOWN_ARGUMENTS = listOf( ) private object AgentJarFinder { - private val agentJarPath = AgentJarFinder::class.java.protectionDomain?.codeSource?.location?.toURI() + private val agentJarPath = + AgentJarFinder::class.java.protectionDomain?.codeSource?.location?.toURI() val agentJarFile = agentJarPath?.let { JarFile(File(it)) } } -private val argumentDelimiter = if (System.getProperty("os.name").startsWith("Windows")) ";" else ":" +private val argumentDelimiter = + if (System.getProperty("os.name").startsWith("Windows")) ";" else ":" @OptIn(ExperimentalPathApi::class) fun premain(agentArgs: String?, instrumentation: Instrumentation) { @@ -74,9 +76,10 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { else -> splitArg[0] to splitArg[1].split(argumentDelimiter) } }.toMap() - val manifestCustomHookNames = ManifestUtils.combineManifestValues(ManifestUtils.HOOK_CLASSES).flatMap { - it.split(':') - } + val manifestCustomHookNames = + ManifestUtils.combineManifestValues(ManifestUtils.HOOK_CLASSES).flatMap { + it.split(':') + } val customHookNames = manifestCustomHookNames + (argumentMap["custom_hooks"] ?: emptyList()) val classNameGlobber = ClassNameGlobber( argumentMap["instrumentation_includes"] ?: emptyList(), -- cgit v1.2.3 From 74089e5d64e3b4e48c55ac00ad9236906e58a614 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sun, 20 Feb 2022 16:57:57 +0100 Subject: Do not emit unnecessarily large fake PCs Now that our trampoline rightfully only forwards the lower 9 bits of the fake PC, we don't have to emit PCs larger than this in the first place. --- .../code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentor.kt index 7d4d06dc..65f11e52 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentor.kt @@ -133,7 +133,7 @@ internal class TraceDataFlowInstrumentor(private val types: Set Date: Fri, 18 Feb 2022 13:32:42 +0100 Subject: Remove unused signal_handler_ variable --- driver/libfuzzer_driver.h | 3 --- driver/signal_handler.h | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/driver/libfuzzer_driver.h b/driver/libfuzzer_driver.h index 557277a5..7f9ca4b7 100644 --- a/driver/libfuzzer_driver.h +++ b/driver/libfuzzer_driver.h @@ -49,9 +49,6 @@ class AbstractLibfuzzerDriver { std::unique_ptr jvm_; private: - // forwards signals caught while the JVM is running - std::unique_ptr signal_handler_; - void initJvm(const std::string &executable_path); }; diff --git a/driver/signal_handler.h b/driver/signal_handler.h index d0d17121..4b8659d2 100644 --- a/driver/signal_handler.h +++ b/driver/signal_handler.h @@ -23,6 +23,7 @@ namespace jazzer { // notifies the driver via native callbacks when the handlers fire. class SignalHandler { public: + SignalHandler() = delete; // Set up handlers for signal in Java. static void Setup(JNIEnv &env); }; -- cgit v1.2.3 From 70dc0f3748af937adf32020c27304afadab25fb0 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 18 Feb 2022 13:35:01 +0100 Subject: Clean up libFuzzer command-line modification logic This will make it easier to add another modification in a follow-up commit. Also replaces a number of non-idiomatic usages of string references or string_view references with pass-by-value string_views. --- driver/jvm_tooling.cpp | 10 +++++----- driver/jvm_tooling.h | 2 +- driver/libfuzzer_driver.cpp | 35 +++++++++++++++++++---------------- driver/libfuzzer_driver.h | 2 +- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index 97f35555..30af6b25 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -155,9 +155,9 @@ void DumpJvmStackTraces() { // Do not detach as we may be the main thread (but the JVM exits anyway). } -std::string dirFromFullPath(const std::string &path) { +std::string_view dirFromFullPath(std::string_view path) { const auto pos = path.rfind(kPathSeparator); - if (pos != std::string::npos) { + if (pos != std::string_view::npos) { return path.substr(0, pos); } return ""; @@ -165,7 +165,7 @@ std::string dirFromFullPath(const std::string &path) { // getInstrumentorAgentPath searches for the fuzzing instrumentation agent and // returns the location if it is found. Otherwise it calls exit(0). -std::string getInstrumentorAgentPath(const std::string &executable_path) { +std::string getInstrumentorAgentPath(std::string_view executable_path) { // User provided agent location takes precedence. if (!FLAGS_agent_path.empty()) { if (std::ifstream(FLAGS_agent_path).good()) return FLAGS_agent_path; @@ -179,7 +179,7 @@ std::string getInstrumentorAgentPath(const std::string &executable_path) { using bazel::tools::cpp::runfiles::Runfiles; std::string error; std::unique_ptr runfiles( - Runfiles::Create(executable_path, &error)); + Runfiles::Create(std::string(executable_path), &error)); if (runfiles != nullptr) { auto bazel_path = runfiles->Rlocation(kAgentBazelRunfilesPath); if (!bazel_path.empty() && std::ifstream(bazel_path).good()) @@ -248,7 +248,7 @@ std::vector splitEscaped(const std::string &str) { return parts; } -JVM::JVM(const std::string &executable_path) { +JVM::JVM(std::string_view executable_path) { // combine class path from command line flags and JAVA_FUZZER_CLASSPATH env // variable std::string class_path = absl::StrFormat("-Djava.class.path=%s", FLAGS_cp); diff --git a/driver/jvm_tooling.h b/driver/jvm_tooling.h index be9582de..054dddef 100644 --- a/driver/jvm_tooling.h +++ b/driver/jvm_tooling.h @@ -35,7 +35,7 @@ class JVM { public: // Creates a JVM instance with default options + options that were provided as // command line flags. - explicit JVM(const std::string &executable_path); + explicit JVM(std::string_view executable_path); // Destroy the running JVM instance. ~JVM(); diff --git a/driver/libfuzzer_driver.cpp b/driver/libfuzzer_driver.cpp index 57beef58..94bfc902 100644 --- a/driver/libfuzzer_driver.cpp +++ b/driver/libfuzzer_driver.cpp @@ -57,7 +57,6 @@ extern "C" [[maybe_unused]] void __jazzer_set_death_callback( } namespace { -char *additional_arg; std::vector modified_argv; std::string GetNewTempFilePath() { @@ -90,9 +89,11 @@ AbstractLibfuzzerDriver::AbstractLibfuzzerDriver( google::InitGoogleLogging((*argv)[0]); rules_jni_init((*argv)[0]); - auto argv_start = *argv; - auto argv_end = *argv + *argc; + const auto argv_start = *argv; + const auto argv_end = *argv + *argc; + // Parse libFuzzer flags to determine Jazzer flag defaults before letting + // gflags parse the command line. if (std::find(argv_start, argv_end, "-use_value_profile=1"s) != argv_end) { FLAGS_fake_pcs = true; } @@ -110,7 +111,12 @@ AbstractLibfuzzerDriver::AbstractLibfuzzerDriver( // libFuzzer forwards the command line (e.g. with -jobs or -minimize_crash). gflags::ParseCommandLineFlags(&our_argc, &our_argv, false); - if (std::any_of(argv_start, argv_end, [](const std::string_view &arg) { + // Perform modifications of the command line arguments passed to libFuzzer if + // necessary by modifying this copy, which will be made the regular argv at + // the end of this function. + modified_argv = std::vector(argv_start, argv_end); + + if (std::any_of(argv_start, argv_end, [](std::string_view arg) { return absl::StartsWith(arg, "-fork=") || absl::StartsWith(arg, "-jobs=") || absl::StartsWith(arg, "-merge="); @@ -130,17 +136,8 @@ AbstractLibfuzzerDriver::AbstractLibfuzzerDriver( absl::StrFormat("--id_sync_file=%s", FLAGS_id_sync_file); // This argument can be accessed by libFuzzer at any (later) time and thus // cannot be safely freed by us. - additional_arg = strdup(new_arg.c_str()); - modified_argv = std::vector(argv_start, argv_end); - modified_argv.push_back(additional_arg); - // Terminate modified_argv. - modified_argv.push_back(nullptr); - // Modify argv and argc for libFuzzer. modified_argv must not be changed - // after this point. + modified_argv.push_back(strdup(new_arg.c_str())); *argc += 1; - *argv = modified_argv.data(); - argv_start = *argv; - argv_end = *argv + *argc; } // Creates the file, truncating it if it exists. std::ofstream touch_file(FLAGS_id_sync_file, std::ios_base::trunc); @@ -155,10 +152,16 @@ AbstractLibfuzzerDriver::AbstractLibfuzzerDriver( std::atexit(cleanup_fn); } - initJvm(*argv_start); + // Terminate modified_argv. + modified_argv.push_back(nullptr); + // Modify argv and argc for libFuzzer. modified_argv must not be changed + // after this point. + *argv = modified_argv.data(); + + initJvm(**argv); } -void AbstractLibfuzzerDriver::initJvm(const std::string &executable_path) { +void AbstractLibfuzzerDriver::initJvm(std::string_view executable_path) { jvm_ = std::make_unique(executable_path); } diff --git a/driver/libfuzzer_driver.h b/driver/libfuzzer_driver.h index 7f9ca4b7..b7eb0b20 100644 --- a/driver/libfuzzer_driver.h +++ b/driver/libfuzzer_driver.h @@ -49,7 +49,7 @@ class AbstractLibfuzzerDriver { std::unique_ptr jvm_; private: - void initJvm(const std::string &executable_path); + void initJvm(std::string_view executable_path); }; class LibfuzzerDriver : public AbstractLibfuzzerDriver { -- cgit v1.2.3 From c548561640f3716bf886cf1f1f258e1d33b27529 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 18 Feb 2022 13:44:56 +0100 Subject: Make libFuzzer's seed available to hooks The seed comes with an appropriate Javadoc warning describing appropriate and inappropriate use cases. --- .../com/code_intelligence/jazzer/api/Jazzer.java | 26 +++++++++++++++ driver/fuzzed_data_provider_test.cpp | 2 +- driver/jvm_tooling.cpp | 7 +++- driver/jvm_tooling.h | 2 +- driver/jvm_tooling_test.cpp | 2 +- driver/libfuzzer_driver.cpp | 39 ++++++++++++++++++++-- driver/libfuzzer_driver.h | 2 +- 7 files changed, 72 insertions(+), 8 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java index e45f7600..b68b1f35 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java @@ -18,11 +18,25 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.InvocationTargetException; +import java.security.SecureRandom; /** * Helper class with static methods that interact with Jazzer at runtime. */ final public class Jazzer { + /** + * A 32-bit random number that hooks can use to make pseudo-random choices + * between multiple possible mutations they could guide the fuzzer towards. + * Hooks must not base the decision whether or not to report a finding + * on this number as this will make findings non-reproducible. + * + * This is the same number that libFuzzer uses as a seed internally, which + * makes it possible to deterministically reproduce a previous fuzzing run by + * supplying the seed value printed by libFuzzer as the value of the + * {@code -seed}. + */ + public static final int SEED = getLibFuzzerSeed(); + private static Class jazzerInternal = null; private static MethodHandle traceStrcmp = null; @@ -491,6 +505,18 @@ final public class Jazzer { } } + private static int getLibFuzzerSeed() { + // The Jazzer driver sets this property based on the value of libFuzzer's -seed command-line + // option, which allows for fully reproducible fuzzing runs if set. If not running in the + // context of the driver, fall back to a random number instead. + String rawSeed = System.getProperty("jazzer.seed"); + if (rawSeed == null) { + return new SecureRandom().nextInt(); + } + // If jazzer.seed is set, we expect it to be a valid integer. + return Integer.parseUnsignedInt(rawSeed); + } + // Rethrows a (possibly checked) exception while avoiding a throws declaration. @SuppressWarnings("unchecked") private static void rethrowUnchecked(Throwable t) throws T { diff --git a/driver/fuzzed_data_provider_test.cpp b/driver/fuzzed_data_provider_test.cpp index 33ef29ec..05656584 100644 --- a/driver/fuzzed_data_provider_test.cpp +++ b/driver/fuzzed_data_provider_test.cpp @@ -121,7 +121,7 @@ class FuzzedDataProviderTest : public ::testing::Test { Runfiles* runfiles = Runfiles::CreateForTest(); FLAGS_cp = runfiles->Rlocation(FLAGS_cp); - jvm_ = std::make_unique("test_executable"); + jvm_ = std::make_unique("test_executable", "1234"); } static void TearDownTestCase() { jvm_.reset(nullptr); } diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index 30af6b25..9c5bc5ce 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -248,7 +248,7 @@ std::vector splitEscaped(const std::string &str) { return parts; } -JVM::JVM(std::string_view executable_path) { +JVM::JVM(std::string_view executable_path, std::string_view seed) { // combine class path from command line flags and JAVA_FUZZER_CLASSPATH env // variable std::string class_path = absl::StrFormat("-Djava.class.path=%s", FLAGS_cp); @@ -273,6 +273,11 @@ JVM::JVM(std::string_view executable_path) { JavaVMOption{.optionString = (char *)"-XX:-OmitStackTraceInFastThrow"}); // Optimize GC for high throughput rather than low latency. options.push_back(JavaVMOption{.optionString = (char *)"-XX:+UseParallelGC"}); + // Forward libFuzzer's random seed so that Jazzer hooks can base their + // mutations on it. + std::string seed_property = absl::StrFormat("-Djazzer.seed=%s", seed); + options.push_back( + JavaVMOption{.optionString = const_cast(seed_property.c_str())}); // Add additional JVM options set through JAVA_OPTS. std::vector java_opts_args; diff --git a/driver/jvm_tooling.h b/driver/jvm_tooling.h index 054dddef..88ce177f 100644 --- a/driver/jvm_tooling.h +++ b/driver/jvm_tooling.h @@ -35,7 +35,7 @@ class JVM { public: // Creates a JVM instance with default options + options that were provided as // command line flags. - explicit JVM(std::string_view executable_path); + explicit JVM(std::string_view executable_path, std::string_view seed); // Destroy the running JVM instance. ~JVM(); diff --git a/driver/jvm_tooling_test.cpp b/driver/jvm_tooling_test.cpp index adef2cd1..5a6f99d3 100644 --- a/driver/jvm_tooling_test.cpp +++ b/driver/jvm_tooling_test.cpp @@ -55,7 +55,7 @@ class JvmToolingTest : public ::testing::Test { Runfiles *runfiles = Runfiles::CreateForTest(); FLAGS_cp = runfiles->Rlocation(FLAGS_cp); - jvm_ = std::make_unique("test_executable"); + jvm_ = std::make_unique("test_executable", "1234"); } static void TearDownTestCase() { jvm_.reset(nullptr); } diff --git a/driver/libfuzzer_driver.cpp b/driver/libfuzzer_driver.cpp index 94bfc902..d4f8803b 100644 --- a/driver/libfuzzer_driver.cpp +++ b/driver/libfuzzer_driver.cpp @@ -25,6 +25,7 @@ #include "absl/strings/match.h" #include "absl/strings/str_format.h" +#include "absl/strings/strip.h" #include "fuzz_target_runner.h" #include "gflags/gflags.h" #include "glog/logging.h" @@ -116,11 +117,13 @@ AbstractLibfuzzerDriver::AbstractLibfuzzerDriver( // the end of this function. modified_argv = std::vector(argv_start, argv_end); + bool spawns_subprocesses = false; if (std::any_of(argv_start, argv_end, [](std::string_view arg) { return absl::StartsWith(arg, "-fork=") || absl::StartsWith(arg, "-jobs=") || absl::StartsWith(arg, "-merge="); })) { + spawns_subprocesses = true; if (!FLAGS_coverage_report.empty()) { LOG(WARNING) << "WARN: --coverage_report does not support parallel " "fuzzing and has been disabled"; @@ -152,17 +155,47 @@ AbstractLibfuzzerDriver::AbstractLibfuzzerDriver( std::atexit(cleanup_fn); } + std::string seed; + // Search for the last occurence of a "-seed" argument as that is the one that + // is used by libFuzzer. + auto seed_pos = std::find_if( + std::reverse_iterator(argv_end), std::reverse_iterator(argv_start), + [](std::string_view arg) { return absl::StartsWith(arg, "-seed="); }); + if (seed_pos != std::reverse_iterator(argv_start)) { + // An explicit seed has been provided on the command-line, record its value + // so that it can be forwarded to the agent. + seed = absl::StripPrefix(*seed_pos, "-seed="); + } else { + // No explicit seed has been set. Since Jazzer hooks might still want to use + // a seed and we have to ensure that a fuzzing run can be reproduced by + // setting the seed printed by libFuzzer, we generate a seed for it here so + // that the two stay in sync. + unsigned int random_seed = std::random_device()(); + seed = std::to_string(random_seed); + // Only add the -seed argument to the command line if not running in a mode + // that spawns subprocesses. These would inherit the same seed, which might + // make them less effective. + if (!spawns_subprocesses) { + std::string seed_arg = "-seed=" + seed; + // This argument can be accessed by libFuzzer at any (later) time and thus + // cannot be safely freed by us. + modified_argv.push_back(strdup(seed_arg.c_str())); + *argc += 1; + } + } + // Terminate modified_argv. modified_argv.push_back(nullptr); // Modify argv and argc for libFuzzer. modified_argv must not be changed // after this point. *argv = modified_argv.data(); - initJvm(**argv); + initJvm(**argv, seed); } -void AbstractLibfuzzerDriver::initJvm(std::string_view executable_path) { - jvm_ = std::make_unique(executable_path); +void AbstractLibfuzzerDriver::initJvm(std::string_view executable_path, + std::string_view seed) { + jvm_ = std::make_unique(executable_path, seed); } LibfuzzerDriver::LibfuzzerDriver(int *argc, char ***argv) diff --git a/driver/libfuzzer_driver.h b/driver/libfuzzer_driver.h index b7eb0b20..223ca1fb 100644 --- a/driver/libfuzzer_driver.h +++ b/driver/libfuzzer_driver.h @@ -49,7 +49,7 @@ class AbstractLibfuzzerDriver { std::unique_ptr jvm_; private: - void initJvm(std::string_view executable_path); + void initJvm(std::string_view executable_path, std::string_view seed); }; class LibfuzzerDriver : public AbstractLibfuzzerDriver { -- cgit v1.2.3 From 5b96d3a2a7fa7fb811d6bc247ace6f88e4dabc9a Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sat, 19 Feb 2022 00:02:51 +0100 Subject: Make MethodHandles in Jazzer final This unlocks JIT optimizations. --- .../com/code_intelligence/jazzer/api/Jazzer.java | 90 ++++++++++++++-------- 1 file changed, 60 insertions(+), 30 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java index b68b1f35..b9109093 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java @@ -37,25 +37,40 @@ final public class Jazzer { */ public static final int SEED = getLibFuzzerSeed(); - private static Class jazzerInternal = null; + private static final Class JAZZER_INTERNAL; - private static MethodHandle traceStrcmp = null; - private static MethodHandle traceStrstr = null; - private static MethodHandle traceMemcmp = null; + private static final MethodHandle TRACE_STRCMP; + private static final MethodHandle TRACE_STRSTR; + private static final MethodHandle TRACE_MEMCMP; - private static MethodHandle consume = null; - private static MethodHandle autofuzzFunction1 = null; - private static MethodHandle autofuzzFunction2 = null; - private static MethodHandle autofuzzFunction3 = null; - private static MethodHandle autofuzzFunction4 = null; - private static MethodHandle autofuzzFunction5 = null; - private static MethodHandle autofuzzConsumer1 = null; - private static MethodHandle autofuzzConsumer2 = null; - private static MethodHandle autofuzzConsumer3 = null; - private static MethodHandle autofuzzConsumer4 = null; - private static MethodHandle autofuzzConsumer5 = null; + private static final MethodHandle CONSUME; + private static final MethodHandle AUTOFUZZ_FUNCTION_1; + private static final MethodHandle AUTOFUZZ_FUNCTION_2; + private static final MethodHandle AUTOFUZZ_FUNCTION_3; + private static final MethodHandle AUTOFUZZ_FUNCTION_4; + private static final MethodHandle AUTOFUZZ_FUNCTION_5; + private static final MethodHandle AUTOFUZZ_CONSUMER_1; + private static final MethodHandle AUTOFUZZ_CONSUMER_2; + private static final MethodHandle AUTOFUZZ_CONSUMER_3; + private static final MethodHandle AUTOFUZZ_CONSUMER_4; + private static final MethodHandle AUTOFUZZ_CONSUMER_5; static { + Class jazzerInternal = null; + MethodHandle traceStrcmp = null; + MethodHandle traceStrstr = null; + MethodHandle traceMemcmp = null; + MethodHandle consume = null; + MethodHandle autofuzzFunction1 = null; + MethodHandle autofuzzFunction2 = null; + MethodHandle autofuzzFunction3 = null; + MethodHandle autofuzzFunction4 = null; + MethodHandle autofuzzFunction5 = null; + MethodHandle autofuzzConsumer1 = null; + MethodHandle autofuzzConsumer2 = null; + MethodHandle autofuzzConsumer3 = null; + MethodHandle autofuzzConsumer4 = null; + MethodHandle autofuzzConsumer5 = null; try { jazzerInternal = Class.forName("com.code_intelligence.jazzer.runtime.JazzerInternal"); Class traceDataFlowNativeCallbacks = @@ -110,6 +125,21 @@ final public class Jazzer { e.printStackTrace(); System.exit(1); } + JAZZER_INTERNAL = jazzerInternal; + TRACE_STRCMP = traceStrcmp; + TRACE_STRSTR = traceStrstr; + TRACE_MEMCMP = traceMemcmp; + CONSUME = consume; + AUTOFUZZ_FUNCTION_1 = autofuzzFunction1; + AUTOFUZZ_FUNCTION_2 = autofuzzFunction2; + AUTOFUZZ_FUNCTION_3 = autofuzzFunction3; + AUTOFUZZ_FUNCTION_4 = autofuzzFunction4; + AUTOFUZZ_FUNCTION_5 = autofuzzFunction5; + AUTOFUZZ_CONSUMER_1 = autofuzzConsumer1; + AUTOFUZZ_CONSUMER_2 = autofuzzConsumer2; + AUTOFUZZ_CONSUMER_3 = autofuzzConsumer3; + AUTOFUZZ_CONSUMER_4 = autofuzzConsumer4; + AUTOFUZZ_CONSUMER_5 = autofuzzConsumer5; } private Jazzer() {} @@ -134,7 +164,7 @@ final public class Jazzer { @SuppressWarnings("unchecked") public static R autofuzz(FuzzedDataProvider data, Function1 func) { try { - return (R) autofuzzFunction1.invoke(data, func); + return (R) AUTOFUZZ_FUNCTION_1.invoke(data, func); } catch (AutofuzzInvocationException e) { rethrowUnchecked(e.getCause()); } catch (Throwable t) { @@ -164,7 +194,7 @@ final public class Jazzer { @SuppressWarnings("unchecked") public static R autofuzz(FuzzedDataProvider data, Function2 func) { try { - return (R) autofuzzFunction2.invoke(data, func); + return (R) AUTOFUZZ_FUNCTION_2.invoke(data, func); } catch (AutofuzzInvocationException e) { rethrowUnchecked(e.getCause()); } catch (Throwable t) { @@ -194,7 +224,7 @@ final public class Jazzer { @SuppressWarnings("unchecked") public static R autofuzz(FuzzedDataProvider data, Function3 func) { try { - return (R) autofuzzFunction3.invoke(data, func); + return (R) AUTOFUZZ_FUNCTION_3.invoke(data, func); } catch (AutofuzzInvocationException e) { rethrowUnchecked(e.getCause()); } catch (Throwable t) { @@ -225,7 +255,7 @@ final public class Jazzer { public static R autofuzz( FuzzedDataProvider data, Function4 func) { try { - return (R) autofuzzFunction4.invoke(data, func); + return (R) AUTOFUZZ_FUNCTION_4.invoke(data, func); } catch (AutofuzzInvocationException e) { rethrowUnchecked(e.getCause()); } catch (Throwable t) { @@ -256,7 +286,7 @@ final public class Jazzer { public static R autofuzz( FuzzedDataProvider data, Function5 func) { try { - return (R) autofuzzFunction5.invoke(data, func); + return (R) AUTOFUZZ_FUNCTION_5.invoke(data, func); } catch (AutofuzzInvocationException e) { rethrowUnchecked(e.getCause()); } catch (Throwable t) { @@ -283,7 +313,7 @@ final public class Jazzer { */ public static void autofuzz(FuzzedDataProvider data, Consumer1 func) { try { - autofuzzConsumer1.invoke(data, func); + AUTOFUZZ_CONSUMER_1.invoke(data, func); } catch (AutofuzzInvocationException e) { rethrowUnchecked(e.getCause()); } catch (Throwable t) { @@ -308,7 +338,7 @@ final public class Jazzer { */ public static void autofuzz(FuzzedDataProvider data, Consumer2 func) { try { - autofuzzConsumer2.invoke(data, func); + AUTOFUZZ_CONSUMER_2.invoke(data, func); } catch (AutofuzzInvocationException e) { rethrowUnchecked(e.getCause()); } catch (Throwable t) { @@ -333,7 +363,7 @@ final public class Jazzer { */ public static void autofuzz(FuzzedDataProvider data, Consumer3 func) { try { - autofuzzConsumer3.invoke(data, func); + AUTOFUZZ_CONSUMER_3.invoke(data, func); } catch (AutofuzzInvocationException e) { rethrowUnchecked(e.getCause()); } catch (Throwable t) { @@ -359,7 +389,7 @@ final public class Jazzer { public static void autofuzz( FuzzedDataProvider data, Consumer4 func) { try { - autofuzzConsumer4.invoke(data, func); + AUTOFUZZ_CONSUMER_4.invoke(data, func); } catch (AutofuzzInvocationException e) { rethrowUnchecked(e.getCause()); } catch (Throwable t) { @@ -385,7 +415,7 @@ final public class Jazzer { public static void autofuzz( FuzzedDataProvider data, Consumer5 func) { try { - autofuzzConsumer5.invoke(data, func); + AUTOFUZZ_CONSUMER_5.invoke(data, func); } catch (AutofuzzInvocationException e) { rethrowUnchecked(e.getCause()); } catch (Throwable t) { @@ -408,7 +438,7 @@ final public class Jazzer { @SuppressWarnings("unchecked") public static T consume(FuzzedDataProvider data, Class type) { try { - return (T) consume.invokeExact(data, type); + return (T) CONSUME.invokeExact(data, type); } catch (AutofuzzConstructionException ignored) { return null; } catch (Throwable t) { @@ -432,7 +462,7 @@ final public class Jazzer { */ public static void guideTowardsEquality(String current, String target, int id) { try { - traceStrcmp.invokeExact(current, target, 1, id); + TRACE_STRCMP.invokeExact(current, target, 1, id); } catch (Throwable e) { e.printStackTrace(); } @@ -452,7 +482,7 @@ final public class Jazzer { */ public static void guideTowardsEquality(byte[] current, byte[] target, int id) { try { - traceMemcmp.invokeExact(current, target, 1, id); + TRACE_MEMCMP.invokeExact(current, target, 1, id); } catch (Throwable e) { e.printStackTrace(); } @@ -473,7 +503,7 @@ final public class Jazzer { */ public static void guideTowardsContainment(String haystack, String needle, int id) { try { - traceStrstr.invokeExact(haystack, needle, id); + TRACE_STRSTR.invokeExact(haystack, needle, id); } catch (Throwable e) { e.printStackTrace(); } @@ -488,7 +518,7 @@ final public class Jazzer { */ public static void reportFindingFromHook(Throwable finding) { try { - jazzerInternal.getMethod("reportFindingFromHook", Throwable.class).invoke(null, finding); + JAZZER_INTERNAL.getMethod("reportFindingFromHook", Throwable.class).invoke(null, finding); } catch (NullPointerException | IllegalAccessException | NoSuchMethodException e) { // We can only reach this point if the runtime is not in the classpath, but it must be if // hooks work and this function should only be called from them. -- cgit v1.2.3 From 00c416849c60d2af267661f4b0586d2e41d1381f Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 21 Feb 2022 10:02:40 +0100 Subject: Break up wildcard import to please ktlint --- .../com/code_intelligence/jazzer/sanitizers/OsCommandInjection.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/OsCommandInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/OsCommandInjection.kt index f058c9fc..d3adc207 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/OsCommandInjection.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/OsCommandInjection.kt @@ -14,7 +14,10 @@ package com.code_intelligence.jazzer.sanitizers -import com.code_intelligence.jazzer.api.* +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical +import com.code_intelligence.jazzer.api.HookType +import com.code_intelligence.jazzer.api.Jazzer +import com.code_intelligence.jazzer.api.MethodHook import java.lang.invoke.MethodHandle /** -- cgit v1.2.3 From 14ecb9338da282cfec01ebb5b12b8a1f5d214219 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 21 Feb 2022 17:28:22 +0100 Subject: Make OsCommandInjection tests safer By clearing out the environment, especially PATH, it should become extremely unlikely that the fuzzer ever executes a valid command. Installing a SecurityManager would be even safer, but it's terminally deprecated and incompatible with the Bazel test runner, which itself uses a SecurityManager. --- .../src/test/java/com/example/OsCommandInjectionProcessBuilder.java | 5 +++-- .../src/test/java/com/example/OsCommandInjectionRuntimeExec.java | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sanitizers/src/test/java/com/example/OsCommandInjectionProcessBuilder.java b/sanitizers/src/test/java/com/example/OsCommandInjectionProcessBuilder.java index 6e0a046c..f5d52782 100644 --- a/sanitizers/src/test/java/com/example/OsCommandInjectionProcessBuilder.java +++ b/sanitizers/src/test/java/com/example/OsCommandInjectionProcessBuilder.java @@ -15,14 +15,15 @@ package com.example; import com.code_intelligence.jazzer.api.FuzzedDataProvider; -import java.io.IOException; import java.util.concurrent.TimeUnit; public class OsCommandInjectionProcessBuilder { public static void fuzzerTestOneInput(FuzzedDataProvider data) { String input = data.consumeRemainingAsAsciiString(); try { - Process process = new ProcessBuilder(input).start(); + ProcessBuilder processBuilder = new ProcessBuilder(input); + processBuilder.environment().clear(); + Process process = processBuilder.start(); // This should be way faster, but we have to wait until the call is done if (!process.waitFor(10, TimeUnit.MILLISECONDS)) { process.destroyForcibly(); diff --git a/sanitizers/src/test/java/com/example/OsCommandInjectionRuntimeExec.java b/sanitizers/src/test/java/com/example/OsCommandInjectionRuntimeExec.java index 1f6aeb58..c620a751 100644 --- a/sanitizers/src/test/java/com/example/OsCommandInjectionRuntimeExec.java +++ b/sanitizers/src/test/java/com/example/OsCommandInjectionRuntimeExec.java @@ -17,14 +17,13 @@ package com.example; import static java.lang.Runtime.getRuntime; import com.code_intelligence.jazzer.api.FuzzedDataProvider; -import java.io.IOException; import java.util.concurrent.TimeUnit; public class OsCommandInjectionRuntimeExec { public static void fuzzerTestOneInput(FuzzedDataProvider data) { String input = data.consumeRemainingAsAsciiString(); try { - Process process = getRuntime().exec(input); + Process process = getRuntime().exec(input, new String[] {}); // This should be way faster, but we have to wait until the call is done if (!process.waitFor(10, TimeUnit.MILLISECONDS)) { process.destroyForcibly(); -- cgit v1.2.3 From 20aac1c909b3cbdb970cead1e1ef5de1dba3bbeb Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 21 Feb 2022 16:27:24 +0100 Subject: Expose value profile bitmap via Jazzer#exploreState This makes it possible to track partial progress and hidden state from fuzz targets and hooks. --- .../com/code_intelligence/jazzer/api/Jazzer.java | 54 ++++++++++++++++++++++ .../runtime/TraceDataFlowNativeCallbacks.java | 2 +- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java index b9109093..afc18287 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java @@ -42,6 +42,7 @@ final public class Jazzer { private static final MethodHandle TRACE_STRCMP; private static final MethodHandle TRACE_STRSTR; private static final MethodHandle TRACE_MEMCMP; + private static final MethodHandle TRACE_PC_INDIR; private static final MethodHandle CONSUME; private static final MethodHandle AUTOFUZZ_FUNCTION_1; @@ -60,6 +61,7 @@ final public class Jazzer { MethodHandle traceStrcmp = null; MethodHandle traceStrstr = null; MethodHandle traceMemcmp = null; + MethodHandle tracePcIndir = null; MethodHandle consume = null; MethodHandle autofuzzFunction1 = null; MethodHandle autofuzzFunction2 = null; @@ -89,6 +91,9 @@ final public class Jazzer { MethodType.methodType(void.class, byte[].class, byte[].class, int.class, int.class); traceMemcmp = MethodHandles.publicLookup().findStatic( traceDataFlowNativeCallbacks, "traceMemcmp", traceMemcmpType); + MethodType tracePcIndirType = MethodType.methodType(void.class, int.class, int.class); + tracePcIndir = MethodHandles.publicLookup().findStatic( + traceDataFlowNativeCallbacks, "tracePcIndir", tracePcIndirType); Class metaClass = Class.forName("com.code_intelligence.jazzer.autofuzz.Meta"); MethodType consumeType = @@ -129,6 +134,7 @@ final public class Jazzer { TRACE_STRCMP = traceStrcmp; TRACE_STRSTR = traceStrstr; TRACE_MEMCMP = traceMemcmp; + TRACE_PC_INDIR = tracePcIndir; CONSUME = consume; AUTOFUZZ_FUNCTION_1 = autofuzzFunction1; AUTOFUZZ_FUNCTION_2 = autofuzzFunction2; @@ -509,6 +515,54 @@ final public class Jazzer { } } + /** + * Instructs the fuzzer to attain as many possible values for the absolute value of {@code state} + * as possible. + * + * Call this function from a fuzz target or a hook to help the fuzzer track partial progress + * (e.g. by passing the length of a common prefix of two lists that should become equal) or + * explore different values of state that is not directly related to code coverage (see the + * MazeFuzzer example). + * + * Note: This hint only takes effect if the fuzzer is run with the argument + * {@code -use_value_profile=1}. + * + * @param state a numeric encoding of a state that should be varied by the fuzzer + * @param id a (probabilistically) unique identifier for this particular state hint + */ + public static void exploreState(byte state, int id) { + // We only use the lower 7 bits of state, which allows for 128 different state values tracked + // per id. The particular amount of 7 bits of state is also used in libFuzzer's + // TracePC::HandleCmp: + // https://github.com/llvm/llvm-project/blob/c12d49c4e286fa108d4d69f1c6d2b8d691993ffd/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp#L390 + // This value should be large enough for most use cases (e.g. tracking the length of a prefix in + // a comparison) while being small enough that the bitmap isn't filled up too quickly + // (65536 bits/ 128 bits per id = 512 ids). + + // We use tracePcIndir as a way to set a bit in libFuzzer's value profile bitmap. In + // TracePC::HandleCallerCallee, which is what this function ultimately calls through to, the + // lower 12 bits of each argument are combined into a 24-bit index into the bitmap, which is + // then reduced modulo a 16-bit prime. To keep the modulo bias small, we should fill as many + // of the relevant bits as possible. However, there are the following restrictions: + // 1. Since we use the return address trampoline to set the caller address indirectly, its + // upper 3 bits are fixed, which leaves a total of 21 variable bits on x86_64. + // 2. On arm64 macOS, where every instruction is aligned to 4 bytes, the lower 2 bits of the + // caller address will always be zero, further reducing the number of variable bits in the + // caller parameter to 7. + // https://github.com/llvm/llvm-project/blob/c12d49c4e286fa108d4d69f1c6d2b8d691993ffd/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp#L121 + // Even taking these restrictions into consideration, we pass state in the lowest bits of the + // caller address, which is used to form the lowest bits of the bitmap index. This should result + // in the best caching behavior as state is expected to change quickly in consecutive runs and + // in this way all its bitmap entries would be located close to each other in memory. + int lowerBits = (state & 0x7f) | (id << 7); + int upperBits = id >>> 5; + try { + TRACE_PC_INDIR.invokeExact(upperBits, lowerBits); + } catch (Throwable e) { + e.printStackTrace(); + } + } + /** * Make Jazzer report the provided {@link Throwable} as a finding. * diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java index ac10af94..46837c14 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java @@ -57,7 +57,7 @@ final public class TraceDataFlowNativeCallbacks { /* indirect-calls */ // Calls: void __sanitizer_cov_trace_pc_indir(uintptr_t Callee); - private static native void tracePcIndir(int callee, int caller); + public static native void tracePcIndir(int callee, int caller); public static void traceReflectiveCall(Executable callee, int pc) { String className = callee.getDeclaringClass().getCanonicalName(); -- cgit v1.2.3 From 197c593fe832995e36d410f845e895bb1da927d8 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 21 Feb 2022 16:28:21 +0100 Subject: Add MazeFuzzer as an example for Jazzer#exploreState --- examples/BUILD.bazel | 9 ++ examples/src/main/java/com/example/MazeFuzzer.java | 147 +++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 examples/src/main/java/com/example/MazeFuzzer.java diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index be6639ad..fe78196d 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -86,6 +86,15 @@ java_fuzz_target_test( target_class = "com.example.ExampleValueProfileFuzzer", ) +java_fuzz_target_test( + name = "MazeFuzzer", + srcs = [ + "src/main/java/com/example/MazeFuzzer.java", + ], + fuzzer_args = ["-use_value_profile=1"], + target_class = "com.example.MazeFuzzer", +) + java_fuzz_target_test( name = "ExampleOutOfMemoryFuzzer", srcs = [ diff --git a/examples/src/main/java/com/example/MazeFuzzer.java b/examples/src/main/java/com/example/MazeFuzzer.java new file mode 100644 index 00000000..8a3c590a --- /dev/null +++ b/examples/src/main/java/com/example/MazeFuzzer.java @@ -0,0 +1,147 @@ +// Copyright 2022 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.example; + +import com.code_intelligence.jazzer.api.Consumer3; +import com.code_intelligence.jazzer.api.Jazzer; +import java.util.Arrays; +import java.util.stream.Collectors; + +// A fuzz target that shows how manually informing the fuzzer about important state can make a fuzz +// target much more effective. +// This is a Java version of the famous "maze game" discussed in +// "IJON: Exploring Deep State Spaces via Fuzzing", available at: +// https://wcventure.github.io/FuzzingPaper/Paper/SP20_IJON.pdf +public final class MazeFuzzer { + private static final String[] MAZE_STRING = new String[] { + " ███████████████████", + " █ █ █ █ █ █", + "█ █ █ █ ███ █ █ █ ███", + "█ █ █ █ █ █", + "█ █████ ███ ███ █ ███", + "█ █ █ █ █ █", + "█ ███ ███████ █ ███ █", + "█ █ █ █ █ █", + "███████ █ █ █████ ███", + "█ █ █ █ █", + "█ ███████ █ ███ ███ █", + "█ █ █ █ █ █ █", + "███ ███ █ ███ █ ███ █", + "█ █ █ █ █ █", + "█ ███████ █ █ █ █ █ █", + "█ █ █ █ █ █ █", + "█ █ █████████ ███ ███", + "█ █ █ █ █ █ █", + "█ █ █ ███ █████ ███ █", + "█ █ █ ", + "███████████████████ #", + }; + + private static final char[][] MAZE = parseMaze(); + private static final char[][] REACHED_FIELDS = parseMaze(); + + public static void fuzzerTestOneInput(byte[] commands) { + executeCommands(commands, (x, y, won) -> { + if (won) { + throw new TreasureFoundException(commands); + } + // This is the key line that makes this fuzz target work: It instructs the fuzzer to track + // every new combination of x and y as a new feature. Without it, the fuzzer would be + // completely lost in the maze as guessing an escaping path by chance is close to impossible. + Jazzer.exploreState(hash(x, y), 0); + if (REACHED_FIELDS[x][y] == ' ') { + // Fuzzer reached a new field in the maze, print its progress. + REACHED_FIELDS[x][y] = '.'; + System.out.println(renderMaze(REACHED_FIELDS)); + } + }); + } + + // Hash function with good mixing properties published by Thomas Mueller + // under the terms of CC BY-SA 4.0 at + // https://stackoverflow.com/a/12996028 + // https://creativecommons.org/licenses/by-sa/4.0/ + private static byte hash(byte x, byte y) { + int h = (x << 8) | y; + h = ((h >> 16) ^ h) * 0x45d9f3b; + h = ((h >> 16) ^ h) * 0x45d9f3b; + h = (h >> 16) ^ h; + return (byte) h; + } + + private static class TreasureFoundException extends RuntimeException { + TreasureFoundException(byte[] commands) { + super(renderPath(commands)); + } + } + + private static void executeCommands(byte[] commands, Consumer3 callback) { + byte x = 0; + byte y = 0; + callback.accept(x, y, false); + + for (byte command : commands) { + byte nextX = x; + byte nextY = y; + switch (command % 4) { + case 0: + nextX--; + break; + case 1: + nextX++; + break; + case 2: + nextY--; + break; + case 3: + nextY++; + break; + } + char nextFieldType; + try { + nextFieldType = MAZE[nextX][nextY]; + } catch (IndexOutOfBoundsException e) { + // Fuzzer tried to walk through the exterior walls of the maze. + continue; + } + if (nextFieldType != ' ' && nextFieldType != '#') { + // Fuzzer tried to walk through the interior walls of the maze. + continue; + } + // Fuzzer performed a valid move. + x = nextX; + y = nextY; + callback.accept(x, y, nextFieldType == '#'); + } + } + + private static char[][] parseMaze() { + return Arrays.stream(MazeFuzzer.MAZE_STRING).map(String::toCharArray).toArray(char[][] ::new); + } + + private static String renderMaze(char[][] maze) { + return Arrays.stream(maze).map(String::new).collect(Collectors.joining("\n", "\n", "\n")); + } + + private static String renderPath(byte[] commands) { + char[][] mutableMaze = parseMaze(); + executeCommands(commands, (x, y, won) -> { + if (!won) { + mutableMaze[x][y] = '.'; + } + }); + return renderMaze(mutableMaze); + } +} -- cgit v1.2.3 From 8c8e87b22645ba7681e72cef0caaf05bab492b75 Mon Sep 17 00:00:00 2001 From: Khaled Yakdan Date: Mon, 21 Feb 2022 20:48:54 +0100 Subject: Make MazeFuzzer more challenging --- examples/src/main/java/com/example/MazeFuzzer.java | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/examples/src/main/java/com/example/MazeFuzzer.java b/examples/src/main/java/com/example/MazeFuzzer.java index 8a3c590a..9d3448c7 100644 --- a/examples/src/main/java/com/example/MazeFuzzer.java +++ b/examples/src/main/java/com/example/MazeFuzzer.java @@ -61,9 +61,9 @@ public final class MazeFuzzer { // every new combination of x and y as a new feature. Without it, the fuzzer would be // completely lost in the maze as guessing an escaping path by chance is close to impossible. Jazzer.exploreState(hash(x, y), 0); - if (REACHED_FIELDS[x][y] == ' ') { + if (REACHED_FIELDS[y][x] == ' ') { // Fuzzer reached a new field in the maze, print its progress. - REACHED_FIELDS[x][y] = '.'; + REACHED_FIELDS[y][x] = '.'; System.out.println(renderMaze(REACHED_FIELDS)); } }); @@ -95,23 +95,25 @@ public final class MazeFuzzer { for (byte command : commands) { byte nextX = x; byte nextY = y; - switch (command % 4) { - case 0: + switch (command) { + case 'L': nextX--; break; - case 1: + case 'R': nextX++; break; - case 2: + case 'U': nextY--; break; - case 3: + case 'D': nextY++; break; + default: + return; } char nextFieldType; try { - nextFieldType = MAZE[nextX][nextY]; + nextFieldType = MAZE[nextY][nextX]; } catch (IndexOutOfBoundsException e) { // Fuzzer tried to walk through the exterior walls of the maze. continue; @@ -139,7 +141,7 @@ public final class MazeFuzzer { char[][] mutableMaze = parseMaze(); executeCommands(commands, (x, y, won) -> { if (!won) { - mutableMaze[x][y] = '.'; + mutableMaze[y][x] = '.'; } }); return renderMaze(mutableMaze); -- cgit v1.2.3 From dc18757f7bc75d44b9e7fa2110a399bee7ab1fd3 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 22 Feb 2022 17:31:33 +0100 Subject: Add recent JDK findings --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9a91270e..bed85106 100644 --- a/README.md +++ b/README.md @@ -340,6 +340,8 @@ Jazzer has so far uncovered the following vulnerabilities and bugs: | Project | Bug | Status | CVE | found by | | ------- | -------- | ------ | --- | -------- | +| [OpenJDK](https://github.com/openjdk/jdk) | `OutOfMemoryError` via a small BMP image | [fixed](https://openjdk.java.net/groups/vulnerability/advisories/2022-01-18) | [CVE-2022-21360](https://nvd.nist.gov/vuln/detail/CVE-2022-21360) | [Code Intelligence](https://code-intelligence.com) | +| [OpenJDK](https://github.com/openjdk/jdk) | `OutOfMemoryError` via a small TIFF image | [fixed](https://openjdk.java.net/groups/vulnerability/advisories/2022-01-18) | [CVE-2022-21366](https://nvd.nist.gov/vuln/detail/CVE-2022-21366) | [Code Intelligence](https://code-intelligence.com) | | [protocolbuffers/protobuf](https://github.com/protocolbuffers/protobuf) | Small protobuf messages can consume minutes of CPU time | [fixed](https://github.com/protocolbuffers/protobuf/security/advisories/GHSA-wrvw-hg22-4m67) | [CVE-2021-22569](https://nvd.nist.gov/vuln/detail/CVE-2021-22569) | [OSS-Fuzz](https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=39330) | | [jhy/jsoup](https://github.com/jhy/jsoup) | More than 19 Bugs found in HTML and XML parser | [fixed](https://github.com/jhy/jsoup/security/advisories/GHSA-m72m-mhq2-9p6c) | [CVE-2021-37714](https://nvd.nist.gov/vuln/detail/CVE-2021-37714) | [Code Intelligence](https://code-intelligence.com) | | [Apache/commons-compress](https://commons.apache.org/proper/commons-compress/) | Infinite loop when loading a crafted 7z | fixed | [CVE-2021-35515](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-35515) | [Code Intelligence](https://code-intelligence.com) | -- cgit v1.2.3 From 6e66f16316eb02a533728bc13209fea7d2993b83 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 22 Feb 2022 17:27:49 +0100 Subject: Clarify the extent of the list of contributors --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bed85106..b85ee195 100644 --- a/README.md +++ b/README.md @@ -506,7 +506,7 @@ LD_PRELOAD=libcustom_mutator.so ./jazzer ## Credit -The following developers have contributed to Jazzer: +The following developers have contributed to Jazzer before its public release: [Sergej Dechand](https://github.com/serj), [Christian Hartlage](https://github.com/dende), -- cgit v1.2.3 From 5c2495388edc6f7a27c2b18233041786f1d1526c Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Wed, 23 Feb 2022 09:10:47 +0100 Subject: Enable BuildBuddy integration Enable BuildBuddy integration when the API key is available in the workflow. Forks should still be able to execute the workflow without piping logs to BuildBuddy. This is done by setting the environment variable BUILD_BUDDY_CONFIG based on the API secret. The script echoBuildBuddyConfig returns an environment config stating the needed bazel settings if the API token is passed in as first argument, otherwise an empty string is returned. The return value is used to update the workflow environment settings. The passed around secret is filtered out in the GitHub logs. --- .bazelrc | 6 ++++++ .github/scripts/echoBuildBuddyConfig.sh | 7 +++++++ .github/workflows/release.yml | 7 +++++-- .github/workflows/run-all-tests.yml | 8 ++++++-- 4 files changed, 24 insertions(+), 4 deletions(-) create mode 100755 .github/scripts/echoBuildBuddyConfig.sh diff --git a/.bazelrc b/.bazelrc index fec99468..6c60e6c2 100644 --- a/.bazelrc +++ b/.bazelrc @@ -33,6 +33,12 @@ run:windows --noincompatible_strict_action_env # Since the toolchain is conditional on OS and architecture, set it on the particular GitHub Action. build:toolchain --//third_party:toolchain +# CI tests (not using the toolchain to test OSS-Fuzz & local compatibility) +build:ci --bes_results_url=https://app.buildbuddy.io/invocation/ +build:ci --bes_backend=grpcs://cloud.buildbuddy.io +build:ci --remote_cache=grpcs://cloud.buildbuddy.io +build:ci --remote_timeout=3600 + # Maven publishing (local only, requires GPG signature) build:maven --config=toolchain build:maven --stamp diff --git a/.github/scripts/echoBuildBuddyConfig.sh b/.github/scripts/echoBuildBuddyConfig.sh new file mode 100755 index 00000000..7953549b --- /dev/null +++ b/.github/scripts/echoBuildBuddyConfig.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +if [ -n "${1}" ]; then + echo "BUILD_BUDDY_CONFIG=--config=ci --remote_header=x-buildbuddy-api-key=${1}"; +else + echo ""; +fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 700f83b6..4667727c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,9 +29,13 @@ jobs: with: java-version: 8 + - name: Set Build Buddy config + run: .github/scripts/echoBuildBuddyConfig.sh ${{ secrets.BUILDBUDDY_API_KEY }} >> $GITHUB_ENV + shell: bash + - name: Build run: | - bazelisk build --java_runtime_version=local_jdk_8 ${{ matrix.bazel_args }} //agent/src/main/java/com/code_intelligence/jazzer/replay:Replayer_deploy.jar //:jazzer_release + bazelisk build ${{env.BUILD_BUDDY_CONFIG}} --java_runtime_version=local_jdk_8 ${{ matrix.bazel_args }} //agent/src/main/java/com/code_intelligence/jazzer/replay:Replayer_deploy.jar //:jazzer_release cp -L bazel-bin/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer_deploy.jar replayer.jar cp -L bazel-bin/jazzer_release.tar.gz release-${{ matrix.arch }}.tar.gz @@ -87,4 +91,3 @@ jobs: with: name: replayer path: replayer.jar - diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-all-tests.yml index be053d87..78518a91 100644 --- a/.github/workflows/run-all-tests.yml +++ b/.github/workflows/run-all-tests.yml @@ -49,11 +49,15 @@ jobs: path: ${{ matrix.cache }} key: bazel-disk-cache-${{ matrix.arch }}-${{ matrix.jdk }} + - name: Set Build Buddy config + run: .github/scripts/echoBuildBuddyConfig.sh ${{ secrets.BUILDBUDDY_API_KEY }} >> $GITHUB_ENV + shell: bash + - name: Build - run: bazelisk build --java_runtime_version=local_jdk_${{ matrix.jdk }} --disk_cache=${{ matrix.cache }} ${{ matrix.bazel_args }} //... + run: bazelisk build ${{env.BUILD_BUDDY_CONFIG}} --java_runtime_version=local_jdk_${{ matrix.jdk }} --disk_cache=${{ matrix.cache }} ${{ matrix.bazel_args }} //... - name: Test - run: bazelisk test --java_runtime_version=local_jdk_${{ matrix.jdk }} --disk_cache=${{ matrix.cache }} ${{ matrix.bazel_args }} //... + run: bazelisk test ${{env.BUILD_BUDDY_CONFIG}} --java_runtime_version=local_jdk_${{ matrix.jdk }} --disk_cache=${{ matrix.cache }} ${{ matrix.bazel_args }} //... - name: Print JDK used if: matrix.os != 'windows-latest' -- cgit v1.2.3 From 2c10a86ed8d869c2f6e7a7a956bfe269fd3d5d9e Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 21 Feb 2022 17:08:04 +0100 Subject: Allow custom hooks to statically reference classes to hook By retransforming classes that are subject to hook instrumentation after all hooks have been loaded, we ensure that classes don't fail to be instrumented with hooks because they are statically referenced by hooks. This makes it easier to write efficient and readable hooks as it removes the need for using lazy reflection to access members of classes that should be hooked. --- agent/BUILD.bazel | 1 + .../com/code_intelligence/jazzer/agent/Agent.kt | 35 +++++++++++++++------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/agent/BUILD.bazel b/agent/BUILD.bazel index 9f157cd2..5868b02f 100644 --- a/agent/BUILD.bazel +++ b/agent/BUILD.bazel @@ -7,6 +7,7 @@ java_binary( create_executable = False, deploy_manifest_lines = [ "Premain-Class: com.code_intelligence.jazzer.agent.Agent", + "Can-Retransform-Classes: true", "Jazzer-Hook-Classes: {}".format(":".join(SANITIZER_CLASSES)), ], runtime_deps = [ diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index ccd416e9..f7aa1cca 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -132,12 +132,12 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { dumpClassesDir, ) instrumentation.apply { - addTransformer(runtimeInstrumentor) + addTransformer(runtimeInstrumentor, true) } - val classesLoadedBeforeCustomHooks = instrumentation.allLoadedClasses + val classesToHookBeforeLoadingCustomHooks = instrumentation.allLoadedClasses .map { it.name } - .filter { classNameGlobber.includes(it) || customHookClassNameGlobber.includes(it) } + .filter { customHookClassNameGlobber.includes(it) } .toSet() val customHooks = customHookNames.toSet().flatMap { hookClassName -> try { @@ -150,18 +150,31 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { } } val additionalClassesToHookInstrumentor = runtimeInstrumentor.registerCustomHooks(customHooks) - val relevantClassesLoadedAfterCustomHooks = instrumentation.allLoadedClasses + val classesToHookAfterLoadingCustomHooks = instrumentation.allLoadedClasses .map { it.name } .filter { - classNameGlobber.includes(it) || - customHookClassNameGlobber.includes(it) || + customHookClassNameGlobber.includes(it) || additionalClassesToHookInstrumentor.includes(it) } .toSet() - val nonHookClassesLoadedByHooks = - relevantClassesLoadedAfterCustomHooks - classesLoadedBeforeCustomHooks - if (nonHookClassesLoadedByHooks.isNotEmpty()) { - println("WARN: Hooks were not applied to the following classes as they are dependencies of hooks:") - println("WARN: ${nonHookClassesLoadedByHooks.joinToString()}") + val classesMissingHooks = + (classesToHookAfterLoadingCustomHooks - classesToHookBeforeLoadingCustomHooks).toMutableSet() + if (classesMissingHooks.isNotEmpty()) { + if (instrumentation.isRetransformClassesSupported) { + // Only retransform classes that are not subject to coverage instrumentation since + // our coverage instrumentation does not support retransformation yet. + val classesToHook = classesMissingHooks + .filter { !classNameGlobber.includes(it) } + .map { Class.forName(it) } + .toTypedArray() + if (classesToHook.isNotEmpty()) { + instrumentation.retransformClasses(*classesToHook) + } + classesMissingHooks -= classesToHook.map { it.name }.toSet() + } + if (classesMissingHooks.isNotEmpty()) { + println("WARN: Hooks were not applied to the following classes as they are dependencies of hooks:") + println("WARN: ${classesMissingHooks.joinToString()}") + } } } -- cgit v1.2.3 From 2ba6dffbb8c196ee99eca2111ea2c442c0bad4e0 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 23 Feb 2022 09:38:23 +0100 Subject: Add custom hooks to bootstrap class loader search This allows third-party custom hooks not shipped in the agent JAR to hook Java standard library classes. --- README.md | 1 + .../com/code_intelligence/jazzer/agent/Agent.kt | 30 ++++++++++++++++------ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b85ee195..1330cf1b 100644 --- a/README.md +++ b/README.md @@ -459,6 +459,7 @@ for more details. To use the compiled method hooks they have to be available on the classpath provided by `--cp` and can then be loaded by providing the flag `--custom_hooks`, which takes a colon-separated list of names of classes to load hooks from. +If a hook is meant to be applied to a class in the Java standard library, it has to be loaded from a JAR file so that Jazzer can [add it to the bootstrap class loader search](https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html#appendToBootstrapClassLoaderSearch-java.util.jar.JarFile-). This list of custom hooks can alternatively be specified via the `Jazzer-Hook-Classes` attribute in the fuzz target JAR's manifest. diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index f7aa1cca..53f63f79 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -23,6 +23,7 @@ import com.code_intelligence.jazzer.runtime.ManifestUtils import com.code_intelligence.jazzer.utils.ClassNameGlobber import java.io.File import java.lang.instrument.Instrumentation +import java.net.URI import java.nio.file.Paths import java.util.jar.JarFile import kotlin.io.path.ExperimentalPathApi @@ -41,9 +42,11 @@ val KNOWN_ARGUMENTS = listOf( ) private object AgentJarFinder { - private val agentJarPath = - AgentJarFinder::class.java.protectionDomain?.codeSource?.location?.toURI() - val agentJarFile = agentJarPath?.let { JarFile(File(it)) } + val agentJarFile = jarUriForClass(AgentJarFinder::class.java)?.let { JarFile(File(it)) } +} + +fun jarUriForClass(clazz: Class<*>): URI? { + return clazz.protectionDomain?.codeSource?.location?.toURI() } private val argumentDelimiter = @@ -141,20 +144,31 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { .toSet() val customHooks = customHookNames.toSet().flatMap { hookClassName -> try { - loadHooks(Class.forName(hookClassName)).also { + val hookClass = Class.forName(hookClassName) + loadHooks(hookClass).also { println("INFO: Loaded ${it.size} hooks from $hookClassName") - } + }.map { Pair(it, hookClass) } } catch (_: ClassNotFoundException) { println("WARN: Failed to load hooks from $hookClassName") - emptySet() + emptyList() } } - val additionalClassesToHookInstrumentor = runtimeInstrumentor.registerCustomHooks(customHooks) + // If we don't append the JARs containing the custom hooks to the bootstrap class loader, + // third-party hooks not contained in the agent JAR will not be able to instrument Java standard + // library classes. These classes are loaded by the bootstrap / system class loader and would + // not be considered when resolving references to hook methods, leading to NoClassDefFoundError + // being thrown. + customHooks + .mapNotNull { jarUriForClass(it.second) } + .toSet() + .map { JarFile(File(it)) } + .forEach { instrumentation.appendToBootstrapClassLoaderSearch(it) } + val additionalClassesToHookInstrument = runtimeInstrumentor.registerCustomHooks(customHooks.map { it.first }) val classesToHookAfterLoadingCustomHooks = instrumentation.allLoadedClasses .map { it.name } .filter { customHookClassNameGlobber.includes(it) || - additionalClassesToHookInstrumentor.includes(it) + additionalClassesToHookInstrument.includes(it) } .toSet() val classesMissingHooks = -- cgit v1.2.3 From 85e1d20a2f185241db02d523461ee728e7a969cb Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 23 Feb 2022 10:55:19 +0100 Subject: Verify hook dependency handling in a test --- tests/BUILD.bazel | 7 +++ .../java/com/example/HookDependenciesFuzzer.java | 67 ++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 tests/src/test/java/com/example/HookDependenciesFuzzer.java diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index d053561d..c83b325c 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -27,3 +27,10 @@ java_fuzz_target_test( "@maven//:org_apache_commons_commons_imaging", ], ) + +java_fuzz_target_test( + name = "HookDependenciesFuzzer", + srcs = ["src/test/java/com/example/HookDependenciesFuzzer.java"], + hook_classes = ["com.example.HookDependenciesFuzzer"], + target_class = "com.example.HookDependenciesFuzzer", +) diff --git a/tests/src/test/java/com/example/HookDependenciesFuzzer.java b/tests/src/test/java/com/example/HookDependenciesFuzzer.java new file mode 100644 index 00000000..9962045c --- /dev/null +++ b/tests/src/test/java/com/example/HookDependenciesFuzzer.java @@ -0,0 +1,67 @@ +// Copyright 2022 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.example; + +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow; +import com.code_intelligence.jazzer.api.HookType; +import com.code_intelligence.jazzer.api.MethodHook; +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.util.regex.Pattern; + +// This fuzzer verifies that: +// 1. a class referenced in a static initializer of a hook is still instrumented with the hook; +// 2. hooks that are not shipped in the Jazzer agent JAR can still instrument Java standard library +// classes. +public class HookDependenciesFuzzer { + private static final Field PATTERN_ROOT; + + static { + Field root; + try { + root = Pattern.class.getDeclaredField("root"); + } catch (NoSuchFieldException e) { + root = null; + } + PATTERN_ROOT = root; + } + + @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Matcher", + targetMethod = "matches", additionalClassesToHook = {"java.util.regex.Pattern"}) + public static void + matcherMatchesHook(MethodHandle method, Object alwaysNull, Object[] alwaysEmpty, int hookId, + boolean returnValue) { + if (PATTERN_ROOT != null) { + throw new FuzzerSecurityIssueLow("Hook applied even though it depends on the class to hook"); + } + } + + public static void fuzzerTestOneInput(byte[] data) { + try { + Pattern.matches("foobar", "foobar"); + } catch (Throwable t) { + if (t instanceof FuzzerSecurityIssueLow) { + throw t; + } else { + // Unexpected exception, exit without producing a finding to let the test fail due to the + // missing Java reproducer. + // FIXME(fabian): This is hacky and will result in false positives as soon as we implement + // Java reproducers for fuzz target exits. Replace this with a more reliable signal. + t.printStackTrace(); + System.exit(1); + } + } + } +} -- cgit v1.2.3 From e8b8b5d383be9784d567c3ccfe5cb22a34ba5bfd Mon Sep 17 00:00:00 2001 From: Khaled Yakdan Date: Fri, 25 Feb 2022 20:45:35 +0100 Subject: Simplify registering PC tables libFuzzer only checks the value of a PC when the corresponding flag is not zero. Therefore, it is safe to set the all PC entries to any value as long as the corresponding flag is set to zero. We set the value of each PC to the index of the corresponding edge ID. This facilitates finding the edge ID of each covered PC reported by libFuzzer. --- driver/coverage_tracker.cpp | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/driver/coverage_tracker.cpp b/driver/coverage_tracker.cpp index 1ab65ec2..47703a82 100644 --- a/driver/coverage_tracker.cpp +++ b/driver/coverage_tracker.cpp @@ -69,22 +69,16 @@ void CoverageTracker::RegisterNewCounters(JNIEnv &env, jint old_num_counters, } std::size_t diff_num_counters = new_num_counters - old_num_counters; // libFuzzer requires an array containing the instruction addresses associated - // with the coverage counters registered above. Given that we are - // instrumenting Java code, we need to synthesize addresses that are known not - // to conflict with any valid instruction address in native code. Just like - // atheris we ensure there are no collisions by using the addresses of an - // allocated buffer. Note: We intentionally never deallocate the allocations - // made here as they have static lifetime and we can't guarantee they wouldn't - // be freed before libFuzzer stops using them. - fake_instructions_ = new uint32_t[diff_num_counters]; - std::fill(fake_instructions_, fake_instructions_ + diff_num_counters, 0); - - // Never deallocated, see above. + // with the coverage counters registered above. This is required to report how + // many edges have been covered. However, libFuzzer only checks these + // addresses when the corresponding flag is set to 1. Therefore, it is safe to + // set the all PC entries to any value as long as the corresponding flag is + // set to zero. We set the value of each PC to the index of the corresponding + // edge ID. This facilitates finding the edge ID of each covered PC reported + // by libFuzzer. pc_entries_ = new PCTableEntry[diff_num_counters]; for (std::size_t i = 0; i < diff_num_counters; ++i) { - pc_entries_[i].PC = reinterpret_cast(fake_instructions_ + i); - // TODO: Label Java PCs corresponding to functions as such. - pc_entries_[i].PCFlags = 0; + pc_entries_[i] = {i, 0}; } __sanitizer_cov_8bit_counters_init(counters_ + old_num_counters, counters_ + new_num_counters); @@ -122,15 +116,8 @@ void CoverageTracker::ReplayInitialCoverage(JNIEnv &env) { std::string CoverageTracker::ComputeCoverage(JNIEnv &env) { uintptr_t *covered_pcs; size_t num_covered_pcs = __sanitizer_cov_get_observed_pcs(&covered_pcs); - std::vector covered_edge_ids{}; - covered_edge_ids.reserve(num_covered_pcs); - const uintptr_t first_pc = pc_entries_[0].PC; - std::for_each(covered_pcs, covered_pcs + num_covered_pcs, - [&covered_edge_ids, first_pc](const uintptr_t pc) { - jint edge_id = - (pc - first_pc) / sizeof(fake_instructions_[0]); - covered_edge_ids.push_back(edge_id); - }); + std::vector covered_edge_ids(covered_pcs, + covered_pcs + num_covered_pcs); delete[] covered_pcs; jclass coverage_recorder = env.FindClass(kCoverageRecorderClass); -- cgit v1.2.3 From 045a92a19918ef0f048c2116aada164ffedfe128 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 23 Feb 2022 10:58:46 +0100 Subject: Centralize signal handler initialization in Java Drastically reduces the amount of JNI code and idiomatically ensures that initialization happens when needed. --- .../com/code_intelligence/jazzer/agent/Agent.kt | 3 ++ .../com/code_intelligence/jazzer/agent/BUILD.bazel | 1 + .../code_intelligence/jazzer/runtime/BUILD.bazel | 10 ++++- .../jazzer/runtime/SignalHandler.java | 12 ++++-- driver/BUILD.bazel | 21 ++++++---- driver/jvm_tooling.cpp | 2 - driver/libfuzzer_driver.h | 1 - driver/signal_handler.cpp | 45 +++++----------------- driver/signal_handler.h | 30 --------------- 9 files changed, 43 insertions(+), 82 deletions(-) delete mode 100644 driver/signal_handler.h diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index 53f63f79..0da9862a 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -20,6 +20,7 @@ import com.code_intelligence.jazzer.instrumentor.CoverageRecorder import com.code_intelligence.jazzer.instrumentor.InstrumentationType import com.code_intelligence.jazzer.instrumentor.loadHooks import com.code_intelligence.jazzer.runtime.ManifestUtils +import com.code_intelligence.jazzer.runtime.SignalHandler import com.code_intelligence.jazzer.utils.ClassNameGlobber import java.io.File import java.lang.instrument.Instrumentation @@ -191,4 +192,6 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { println("WARN: ${classesMissingHooks.joinToString()}") } } + + SignalHandler.initialize() } diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel index 2d5eec5c..4559dbba 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel @@ -11,5 +11,6 @@ kt_jvm_library( deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor", "//agent/src/main/java/com/code_intelligence/jazzer/runtime", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:signal_handler", ], ) 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 e2718c36..fb0d9d3b 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,3 +1,4 @@ +load("@fmeum_rules_jni//jni:defs.bzl", "jni_headers") load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") java_library( @@ -28,6 +29,13 @@ java_library( java_library( name = "signal_handler", srcs = ["SignalHandler.java"], + visibility = ["//agent/src/main/java/com/code_intelligence/jazzer/agent:__pkg__"], +) + +jni_headers( + name = "signal_handler.hdrs", + lib = ":signal_handler", + visibility = ["//driver:__pkg__"], ) java_library( @@ -51,12 +59,12 @@ kt_jvm_library( ], visibility = ["//visibility:public"], runtime_deps = [ + ":signal_handler", "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz", ], deps = [ ":coverage_map", ":fuzzed_data_provider", - ":signal_handler", "//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/SignalHandler.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java index 06821d47..ad4c291a 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java @@ -16,10 +16,14 @@ package com.code_intelligence.jazzer.runtime; import sun.misc.Signal; -final class SignalHandler { - public static native void handleInterrupt(); - - public static void setupSignalHandlers() { +public final class SignalHandler { + static { Signal.handle(new Signal("INT"), sig -> handleInterrupt()); } + + public static void initialize() { + // Implicitly runs the static initializer. + } + + private static native void handleInterrupt(); } diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index becd4fe1..0a3ae536 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -35,6 +35,18 @@ cc_library( ], ) +cc_library( + name = "signal_handler", + srcs = ["signal_handler.cpp"], + linkstatic = True, + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:signal_handler.hdrs", + "@fmeum_rules_jni//jni", + ], + # Symbols are only referenced dynamically via JNI. + alwayslink = True, +) + cc_library( name = "jvm_tooling_lib", srcs = [ @@ -47,8 +59,6 @@ cc_library( "libfuzzer_callbacks.cpp", "libfuzzer_callbacks.h", "libfuzzer_driver.cpp", - "signal_handler.cpp", - "signal_handler.h", "utils.cpp", "utils.h", ], @@ -66,12 +76,6 @@ cc_library( # Needs to be linked statically for JNI_OnLoad_jazzer_initialize to be found # by the JVM. linkstatic = True, - local_defines = select({ - # Windows does not have SIGUSR1, which triggers a graceful exit of - # libFuzzer. Instead, trigger a hard exit. - "@platforms//os:windows": ["SIGUSR1=SIGTERM"], - "//conditions:default": [], - }), tags = [ # Should be built through the cc_17_library driver_lib. "manual", @@ -80,6 +84,7 @@ cc_library( deps = [ ":fuzzed_data_provider", ":sanitizer_hooks_with_pc", + ":signal_handler", "@bazel_tools//tools/cpp/runfiles", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index 9c5bc5ce..8bffb6f2 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -29,7 +29,6 @@ #include "gflags/gflags.h" #include "glog/logging.h" #include "libfuzzer_callbacks.h" -#include "signal_handler.h" #include "tools/cpp/runfiles/runfiles.h" #include "utils.h" @@ -111,7 +110,6 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad_jazzer_initialize(JavaVM *vm, exit(1); } jazzer::registerFuzzerCallbacks(*env); - jazzer::SignalHandler::Setup(*env); return JNI_VERSION_1_8; } diff --git a/driver/libfuzzer_driver.h b/driver/libfuzzer_driver.h index 223ca1fb..3f269c8b 100644 --- a/driver/libfuzzer_driver.h +++ b/driver/libfuzzer_driver.h @@ -24,7 +24,6 @@ #include "fuzzed_data_provider.h" #include "jvm_tooling.h" #include "libfuzzer_callbacks.h" -#include "signal_handler.h" namespace jazzer { diff --git a/driver/signal_handler.cpp b/driver/signal_handler.cpp index 05e5953a..0b984978 100644 --- a/driver/signal_handler.cpp +++ b/driver/signal_handler.cpp @@ -12,19 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "signal_handler.h" - #include #include #include -#include -constexpr auto kSignalHandlerClass = - "com/code_intelligence/jazzer/runtime/SignalHandler"; +#include "com_code_intelligence_jazzer_runtime_SignalHandler.h" + +#ifdef _WIN32 +// Windows does not have SIGUSR1, which triggers a graceful exit of libFuzzer. +// Instead, trigger a hard exit. +#define SIGUSR1 SIGTERM +#endif // Handles SIGINT raised while running Java code. -void JNICALL handleInterrupt(JNIEnv, jclass) { +void Java_com_code_1intelligence_jazzer_runtime_SignalHandler_handleInterrupt( + JNIEnv *, jclass) { static std::atomic already_exiting{false}; if (!already_exiting.exchange(true)) { // Let libFuzzer exit gracefully when the JVM received SIGINT. @@ -34,33 +37,3 @@ void JNICALL handleInterrupt(JNIEnv, jclass) { raise(SIGTERM); } } - -namespace jazzer { -void SignalHandler::Setup(JNIEnv &env) { - jclass signal_handler_class = env.FindClass(kSignalHandlerClass); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - throw std::runtime_error("could not find signal handler class"); - } - JNINativeMethod signal_handler_methods[]{ - {(char *)"handleInterrupt", (char *)"()V", (void *)&handleInterrupt}, - }; - env.RegisterNatives(signal_handler_class, signal_handler_methods, 1); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - throw std::runtime_error( - "could not register native callbacks 'handleInterrupt'"); - } - jmethodID setup_signal_handlers_method_ = - env.GetStaticMethodID(signal_handler_class, "setupSignalHandlers", "()V"); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - throw std::runtime_error("could not find setupSignalHandlers method"); - } - env.CallStaticVoidMethod(signal_handler_class, setup_signal_handlers_method_); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - throw std::runtime_error("failed to set up signal handlers"); - } -} -} // namespace jazzer diff --git a/driver/signal_handler.h b/driver/signal_handler.h deleted file mode 100644 index 4b8659d2..00000000 --- a/driver/signal_handler.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include - -namespace jazzer { -// SignalHandler registers handlers for signals (e.g. SIGINT) in Java and -// notifies the driver via native callbacks when the handlers fire. -class SignalHandler { - public: - SignalHandler() = delete; - // Set up handlers for signal in Java. - static void Setup(JNIEnv &env); -}; -} // namespace jazzer -- cgit v1.2.3 From 891dcc2b91b17175b96e158456c48c17c654a12f Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 23 Feb 2022 15:08:05 +0100 Subject: Extract coverage tracker into its own cc_library and use JNI headers --- .../com/code_intelligence/jazzer/runtime/BUILD.bazel | 6 ++++++ driver/BUILD.bazel | 17 +++++++++++++++-- driver/coverage_tracker.cpp | 16 ++++++++-------- driver/coverage_tracker.h | 7 ++----- driver/jvm_tooling.cpp | 1 - 5 files changed, 31 insertions(+), 16 deletions(-) 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 fb0d9d3b..5fa6a87c 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 @@ -26,6 +26,12 @@ java_library( ], ) +jni_headers( + name = "coverage_map.hdrs", + lib = ":coverage_map", + visibility = ["//driver:__pkg__"], +) + java_library( name = "signal_handler", srcs = ["SignalHandler.java"], diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index 0a3ae536..30b33ab0 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -47,10 +47,22 @@ cc_library( alwayslink = True, ) +cc_library( + name = "coverage_tracker", + srcs = ["coverage_tracker.cpp"], + hdrs = ["coverage_tracker.h"], + linkstatic = True, + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:coverage_map.hdrs", + "@fmeum_rules_jni//jni", + ], + # Symbols are only referenced dynamically via JNI. + alwayslink = True, +) + cc_library( name = "jvm_tooling_lib", srcs = [ - "coverage_tracker.cpp", "fuzz_target_runner.cpp", "java_reproducer.cpp", "java_reproducer.h", @@ -63,7 +75,6 @@ cc_library( "utils.h", ], hdrs = [ - "coverage_tracker.h", "fuzz_target_runner.h", "fuzzed_data_provider.h", "jvm_tooling.h", @@ -82,6 +93,7 @@ cc_library( ], visibility = ["//visibility:public"], deps = [ + ":coverage_tracker", ":fuzzed_data_provider", ":sanitizer_hooks_with_pc", ":signal_handler", @@ -227,6 +239,7 @@ cc_test( ], }), deps = [ + ":coverage_tracker", ":jvm_tooling_lib", ":test_main", "@bazel_tools//tools/cpp/runfiles", diff --git a/driver/coverage_tracker.cpp b/driver/coverage_tracker.cpp index 47703a82..d2c881aa 100644 --- a/driver/coverage_tracker.cpp +++ b/driver/coverage_tracker.cpp @@ -21,6 +21,8 @@ #include #include +#include "com_code_intelligence_jazzer_runtime_CoverageMap.h" + extern "C" void __sanitizer_cov_8bit_counters_init(uint8_t *start, uint8_t *end); extern "C" void __sanitizer_cov_pcs_init(const uintptr_t *pcs_beg, @@ -143,17 +145,15 @@ std::string CoverageTracker::ComputeCoverage(JNIEnv &env) { } } // namespace jazzer -extern "C" { -JNIEXPORT void JNICALL +[[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_CoverageMap_initialize( - JNIEnv &env, jclass cls, jlong counters) { - ::jazzer::CoverageTracker::Initialize(env, counters); + JNIEnv *env, jclass cls, jlong counters) { + ::jazzer::CoverageTracker::Initialize(*env, counters); } -JNIEXPORT void JNICALL +[[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_CoverageMap_registerNewCounters( - JNIEnv &env, jclass cls, jint old_num_counters, jint new_num_counters) { - ::jazzer::CoverageTracker::RegisterNewCounters(env, old_num_counters, + JNIEnv *env, jclass cls, jint old_num_counters, jint new_num_counters) { + ::jazzer::CoverageTracker::RegisterNewCounters(*env, old_num_counters, new_num_counters); } -} diff --git a/driver/coverage_tracker.h b/driver/coverage_tracker.h index e520aa12..55bb5b05 100644 --- a/driver/coverage_tracker.h +++ b/driver/coverage_tracker.h @@ -20,8 +20,6 @@ #include -#include "jvm_tooling.h" - namespace jazzer { // The members of this struct are only accessed by libFuzzer. @@ -30,9 +28,8 @@ struct __attribute__((packed)) PCTableEntry { }; // CoverageTracker registers an array of 8-bit coverage counters with -// libFuzzer. The array is backed by a MappedByteBuffer on the Java -// side, where it is populated with the actual coverage information. -class CoverageTracker : public ExceptionPrinter { +// libFuzzer. The array is populated from Java using Unsafe. +class CoverageTracker { private: static uint8_t *counters_; diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index 8bffb6f2..c9a91ed3 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -25,7 +25,6 @@ #include "absl/strings/str_join.h" #include "absl/strings/str_replace.h" #include "absl/strings/str_split.h" -#include "coverage_tracker.h" #include "gflags/gflags.h" #include "glog/logging.h" #include "libfuzzer_callbacks.h" -- cgit v1.2.3 From d280ca8813b3b515114f7e8d0a5f5c647a61d98f Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 23 Feb 2022 15:18:22 +0100 Subject: Use dynamic linking for libFuzzer callbacks This reduces the amount of JNI code and enables us to use JavaCritical to further optimize the callback invocations. --- .../code_intelligence/jazzer/runtime/BUILD.bazel | 14 +- .../runtime/TraceDataFlowNativeCallbacks.java | 115 +++++-- driver/BUILD.bazel | 20 +- driver/jvm_tooling.cpp | 21 +- driver/libfuzzer_callbacks.cpp | 345 ++++++++------------- driver/libfuzzer_callbacks.h | 25 -- driver/libfuzzer_driver.h | 1 - driver/sanitizer_hooks_with_pc.cpp | 2 +- 8 files changed, 261 insertions(+), 282 deletions(-) delete mode 100644 driver/libfuzzer_callbacks.h 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 5fa6a87c..c3ed9856 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 @@ -44,6 +44,18 @@ jni_headers( visibility = ["//driver:__pkg__"], ) +java_library( + name = "trace_data_flow_native_callbacks", + srcs = ["TraceDataFlowNativeCallbacks.java"], + deps = ["//agent/src/main/java/com/code_intelligence/jazzer/utils"], +) + +jni_headers( + name = "trace_data_flow_native_callbacks.hdrs", + lib = ":trace_data_flow_native_callbacks", + visibility = ["//driver:__pkg__"], +) + java_library( name = "unsafe_provider", srcs = ["UnsafeProvider.java"], @@ -59,7 +71,6 @@ kt_jvm_library( "NativeLibHooks.java", "RecordingFuzzedDataProvider.java", "TraceCmpHooks.java", - "TraceDataFlowNativeCallbacks.java", "TraceDivHooks.java", "TraceIndirHooks.java", ], @@ -71,6 +82,7 @@ kt_jvm_library( deps = [ ":coverage_map", ":fuzzed_data_provider", + ":trace_data_flow_native_callbacks", "//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/TraceDataFlowNativeCallbacks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java index 46837c14..e862d76a 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java @@ -19,47 +19,90 @@ import java.lang.reflect.Executable; @SuppressWarnings("unused") final public class TraceDataFlowNativeCallbacks { + // Making this static final ensures that the JIT will eliminate the dead branch of a construct + // such as: + // if (USE_FAKE_PCS) ... else ... + private static final boolean USE_FAKE_PCS = useFakePcs(); + /* trace-cmp */ - // Calls: void __sanitizer_cov_trace_cmp4(uint32_t Arg1, uint32_t Arg2); - public static native void traceCmpInt(int arg1, int arg2, int pc); + public static void traceCmpInt(int arg1, int arg2, int pc) { + if (USE_FAKE_PCS) { + traceCmpIntWithPc(arg1, arg2, pc); + } else { + traceCmpInt(arg1, arg2); + } + } - // Calls: void __sanitizer_cov_trace_const_cmp4(uint32_t Arg1, uint32_t Arg2); - public static native void traceConstCmpInt(int arg1, int arg2, int pc); + public static void traceConstCmpInt(int arg1, int arg2, int pc) { + if (USE_FAKE_PCS) { + traceConstCmpIntWithPc(arg1, arg2, pc); + } else { + traceConstCmpInt(arg1, arg2); + } + } - // Calls: void __sanitizer_cov_trace_cmp4(uint32_t Arg1, uint32_t Arg2); - public static native void traceCmpLong(long arg1, long arg2, int pc); + public static void traceCmpLong(long arg1, long arg2, int pc) { + if (USE_FAKE_PCS) { + traceCmpLongWithPc(arg1, arg2, pc); + } else { + traceCmpLong(arg1, arg2); + } + } - // Calls: void __sanitizer_cov_trace_switch(uint64_t Val, uint64_t *Cases); - public static native void traceSwitch(long val, long[] cases, int pc); + public static void traceSwitch(long val, long[] cases, int pc) { + if (USE_FAKE_PCS) { + traceSwitchWithPc(val, cases, pc); + } else { + traceSwitch(val, cases); + } + } - // Calls: void __sanitizer_weak_hook_memcmp(void *caller_pc, const void *b1, const void *b2, - // size_t n, int result); public static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc); - - // Calls: void __sanitizer_weak_hook_strcmp(void *called_pc, const char *s1, const char *s2, int - // result); public static native void traceStrcmp(String s1, String s2, int result, int pc); - - // Calls: void __sanitizer_weak_hook_strstr(void *called_pc, const char *s1, const char *s2, char - // *result); public static native void traceStrstr(String s1, String s2, int pc); /* trace-div */ - // Calls: void __sanitizer_cov_trace_div4(uint32_t Val); - public static native void traceDivInt(int val, int pc); + public static void traceDivInt(int val, int pc) { + if (USE_FAKE_PCS) { + traceDivIntWithPc(val, pc); + } else { + traceDivInt(val); + } + } - // Calls: void __sanitizer_cov_trace_div8(uint64_t Val); - public static native void traceDivLong(long val, int pc); + public static void traceDivLong(long val, int pc) { + if (USE_FAKE_PCS) { + traceDivLongWithPc(val, pc); + } else { + traceDivLong(val); + } + } /* trace-gep */ - // Calls: void __sanitizer_cov_trace_gep(uintptr_t Idx); - public static native void traceGep(long val, int pc); + public static void traceGep(long val, int pc) { + if (USE_FAKE_PCS) { + traceGepWithPc(val, pc); + } else { + traceGep(val); + } + } /* indirect-calls */ - // Calls: void __sanitizer_cov_trace_pc_indir(uintptr_t Callee); - public static native void tracePcIndir(int callee, int caller); + public static void tracePcIndir(int callee, int caller) { + if (!USE_FAKE_PCS) { + // Without fake PCs, tracePcIndir will not record the relation between callee and pc, which + // makes it useless. + return; + } + tracePcIndir0(callee, caller); + } public static void traceReflectiveCall(Executable callee, int pc) { + if (!USE_FAKE_PCS) { + // Without fake PCs, tracePcIndir will not record the relation between callee and pc, which + // makes it useless. + return; + } String className = callee.getDeclaringClass().getCanonicalName(); String executableName = callee.getName(); String descriptor = Utils.getDescriptor(callee); @@ -95,4 +138,28 @@ final public class TraceDataFlowNativeCallbacks { } public static native void handleLibraryLoad(); + + private static boolean useFakePcs() { + String rawFakePcs = System.getProperty("jazzer.fake_pcs"); + if (rawFakePcs == null) { + return false; + } + return Boolean.parseBoolean(rawFakePcs); + } + + private static native void traceCmpInt(int arg1, int arg2); + private static native void traceCmpIntWithPc(int arg1, int arg2, int pc); + private static native void traceConstCmpInt(int arg1, int arg2); + private static native void traceConstCmpIntWithPc(int arg1, int arg2, int pc); + private static native void traceCmpLong(long arg1, long arg2); + private static native void traceCmpLongWithPc(long arg1, long arg2, int pc); + private static native void traceSwitch(long val, long[] cases); + private static native void traceSwitchWithPc(long val, long[] cases, int pc); + private static native void traceDivInt(int val); + private static native void traceDivIntWithPc(int val, int pc); + private static native void traceDivLong(long val); + private static native void traceDivLongWithPc(long val, int pc); + private static native void traceGep(long val); + private static native void traceGepWithPc(long val, int pc); + private static native void tracePcIndir0(int callee, int caller); } diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index 30b33ab0..24abeb01 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -60,6 +60,22 @@ cc_library( alwayslink = True, ) +cc_library( + name = "libfuzzer_callbacks", + srcs = ["libfuzzer_callbacks.cpp"], + linkstatic = True, + deps = [ + ":sanitizer_hooks_with_pc", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:trace_data_flow_native_callbacks.hdrs", + "@com_google_absl//absl/strings", + "@com_google_glog//:glog", + "@fmeum_rules_jni//jni", + "@jazzer_com_github_gflags_gflags//:gflags", + ], + # Symbols are only referenced dynamically via JNI. + alwayslink = True, +) + cc_library( name = "jvm_tooling_lib", srcs = [ @@ -68,8 +84,6 @@ cc_library( "java_reproducer.h", "java_reproducer_templates.h", "jvm_tooling.cpp", - "libfuzzer_callbacks.cpp", - "libfuzzer_callbacks.h", "libfuzzer_driver.cpp", "utils.cpp", "utils.h", @@ -95,7 +109,7 @@ cc_library( deps = [ ":coverage_tracker", ":fuzzed_data_provider", - ":sanitizer_hooks_with_pc", + ":libfuzzer_callbacks", ":signal_handler", "@bazel_tools//tools/cpp/runfiles", "@com_google_absl//absl/strings", diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index c9a91ed3..9ae954d2 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -27,7 +27,6 @@ #include "absl/strings/str_split.h" #include "gflags/gflags.h" #include "glog/logging.h" -#include "libfuzzer_callbacks.h" #include "tools/cpp/runfiles/runfiles.h" #include "utils.h" @@ -87,28 +86,16 @@ DEFINE_bool(hooks, true, "coverage information will be processed. This can be useful for " "running a regression test on non-instrumented bytecode."); +DECLARE_bool(fake_pcs); + #ifdef _WIN32 #define ARG_SEPARATOR ";" #else #define ARG_SEPARATOR ":" #endif -// Called by the agent when -// com.code_intelligence.jazzer.instrumentor.ClassInstrumentor is initialized. -// This only happens when FLAGS_hooks is true. extern "C" JNIEXPORT jint JNICALL JNI_OnLoad_jazzer_initialize(JavaVM *vm, void *) { - if (!FLAGS_hooks) { - LOG(ERROR) << "JNI_OnLoad_jazzer_initialize called with --nohooks"; - exit(1); - } - JNIEnv *env = nullptr; - jint result = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_8); - if (result != JNI_OK) { - LOG(FATAL) << "Failed to get JNI environment"; - exit(1); - } - jazzer::registerFuzzerCallbacks(*env); return JNI_VERSION_1_8; } @@ -275,6 +262,10 @@ JVM::JVM(std::string_view executable_path, std::string_view seed) { std::string seed_property = absl::StrFormat("-Djazzer.seed=%s", seed); options.push_back( JavaVMOption{.optionString = const_cast(seed_property.c_str())}); + std::string fake_pcs_property = absl::StrFormat( + "-Djazzer.fake_pcs=%s", FLAGS_fake_pcs ? "true" : "false"); + options.push_back(JavaVMOption{ + .optionString = const_cast(fake_pcs_property.c_str())}); // Add additional JVM options set through JAVA_OPTS. std::vector java_opts_args; diff --git a/driver/libfuzzer_callbacks.cpp b/driver/libfuzzer_callbacks.cpp index 5b7813dd..cbf513a0 100644 --- a/driver/libfuzzer_callbacks.cpp +++ b/driver/libfuzzer_callbacks.cpp @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "libfuzzer_callbacks.h" - #include #include @@ -23,8 +21,8 @@ #include #include "absl/strings/match.h" -#include "absl/strings/str_format.h" #include "absl/strings/str_split.h" +#include "com_code_intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks.h" #include "gflags/gflags.h" #include "glog/logging.h" #include "sanitizer_hooks_with_pc.h" @@ -37,10 +35,6 @@ DEFINE_bool( namespace { -const char kLibfuzzerTraceDataFlowHooksClass[] = - "com/code_intelligence/jazzer/runtime/" - "TraceDataFlowNativeCallbacks"; - extern "C" { void __sanitizer_weak_hook_memcmp(void *caller_pc, const void *s1, const void *s2, std::size_t n, int result); @@ -65,166 +59,174 @@ void __sanitizer_cov_trace_gep(uintptr_t idx); inline __attribute__((always_inline)) void *idToPc(jint id) { return reinterpret_cast(static_cast(id)); } +} // namespace -void JNICALL libfuzzerStringCompareCallback(JNIEnv &env, jclass cls, jstring s1, - jstring s2, jint result, jint id) { - const char *s1_native = env.GetStringUTFChars(s1, nullptr); - if (env.ExceptionCheck()) env.ExceptionDescribe(); - std::size_t n1 = env.GetStringUTFLength(s1); - if (env.ExceptionCheck()) env.ExceptionDescribe(); - const char *s2_native = env.GetStringUTFChars(s2, nullptr); - if (env.ExceptionCheck()) env.ExceptionDescribe(); - std::size_t n2 = env.GetStringUTFLength(s2); - if (env.ExceptionCheck()) env.ExceptionDescribe(); +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceStrcmp( + JNIEnv *env, jclass cls, jstring s1, jstring s2, jint result, jint id) { + const char *s1_native = env->GetStringUTFChars(s1, nullptr); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + std::size_t n1 = env->GetStringUTFLength(s1); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + const char *s2_native = env->GetStringUTFChars(s2, nullptr); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + std::size_t n2 = env->GetStringUTFLength(s2); + if (env->ExceptionCheck()) env->ExceptionDescribe(); __sanitizer_weak_hook_compare_bytes(idToPc(id), s1_native, s2_native, n1, n2, result); - env.ReleaseStringUTFChars(s1, s1_native); - if (env.ExceptionCheck()) env.ExceptionDescribe(); - env.ReleaseStringUTFChars(s2, s2_native); - if (env.ExceptionCheck()) env.ExceptionDescribe(); + env->ReleaseStringUTFChars(s1, s1_native); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + env->ReleaseStringUTFChars(s2, s2_native); + if (env->ExceptionCheck()) env->ExceptionDescribe(); } -void JNICALL libfuzzerStringContainCallback(JNIEnv &env, jclass cls, jstring s1, - jstring s2, jint id) { - const char *s1_native = env.GetStringUTFChars(s1, nullptr); - if (env.ExceptionCheck()) env.ExceptionDescribe(); - const char *s2_native = env.GetStringUTFChars(s2, nullptr); - if (env.ExceptionCheck()) env.ExceptionDescribe(); +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceStrstr( + JNIEnv *env, jclass cls, jstring s1, jstring s2, jint id) { + const char *s1_native = env->GetStringUTFChars(s1, nullptr); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + const char *s2_native = env->GetStringUTFChars(s2, nullptr); + if (env->ExceptionCheck()) env->ExceptionDescribe(); // libFuzzer currently ignores the result, which allows us to simply pass a // valid but arbitrary pointer here instead of performing an actual strstr // operation. __sanitizer_weak_hook_strstr(idToPc(id), s1_native, s2_native, s1_native); - env.ReleaseStringUTFChars(s1, s1_native); - if (env.ExceptionCheck()) env.ExceptionDescribe(); - env.ReleaseStringUTFChars(s2, s2_native); - if (env.ExceptionCheck()) env.ExceptionDescribe(); + env->ReleaseStringUTFChars(s1, s1_native); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + env->ReleaseStringUTFChars(s2, s2_native); + if (env->ExceptionCheck()) env->ExceptionDescribe(); } -void JNICALL libfuzzerByteCompareCallback(JNIEnv &env, jclass cls, - jbyteArray b1, jbyteArray b2, - jint result, jint id) { - jbyte *b1_native = env.GetByteArrayElements(b1, nullptr); - if (env.ExceptionCheck()) env.ExceptionDescribe(); - jbyte *b2_native = env.GetByteArrayElements(b2, nullptr); - if (env.ExceptionCheck()) env.ExceptionDescribe(); - jint b1_length = env.GetArrayLength(b1); - if (env.ExceptionCheck()) env.ExceptionDescribe(); - jint b2_length = env.GetArrayLength(b2); - if (env.ExceptionCheck()) env.ExceptionDescribe(); +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceMemcmp( + JNIEnv *env, jclass cls, jbyteArray b1, jbyteArray b2, jint result, + jint id) { + jbyte *b1_native = env->GetByteArrayElements(b1, nullptr); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + jbyte *b2_native = env->GetByteArrayElements(b2, nullptr); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + jint b1_length = env->GetArrayLength(b1); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + jint b2_length = env->GetArrayLength(b2); + if (env->ExceptionCheck()) env->ExceptionDescribe(); __sanitizer_weak_hook_compare_bytes(idToPc(id), b1_native, b2_native, b1_length, b2_length, result); - env.ReleaseByteArrayElements(b1, b1_native, JNI_ABORT); - if (env.ExceptionCheck()) env.ExceptionDescribe(); - env.ReleaseByteArrayElements(b2, b2_native, JNI_ABORT); - if (env.ExceptionCheck()) env.ExceptionDescribe(); + env->ReleaseByteArrayElements(b1, b1_native, JNI_ABORT); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + env->ReleaseByteArrayElements(b2, b2_native, JNI_ABORT); + if (env->ExceptionCheck()) env->ExceptionDescribe(); } -void JNICALL libfuzzerLongCompareCallback(JNIEnv &env, jclass cls, jlong value1, - jlong value2, jint id) { +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpLong( + JNIEnv *env, jclass cls, jlong value1, jlong value2) { __sanitizer_cov_trace_cmp8(value1, value2); } -void JNICALL libfuzzerLongCompareCallbackWithPc(JNIEnv &env, jclass cls, - jlong value1, jlong value2, - jint id) { +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpLongWithPc( + JNIEnv *env, jclass cls, jlong value1, jlong value2, jint id) { __sanitizer_cov_trace_cmp8_with_pc(idToPc(id), value1, value2); } -void JNICALL libfuzzerIntCompareCallback(JNIEnv &env, jclass cls, jint value1, - jint value2, jint id) { +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpInt( + JNIEnv *env, jclass cls, jint value1, jint value2) { + __sanitizer_cov_trace_cmp4(value1, value2); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpIntWithPc( + JNIEnv *env, jclass cls, jint value1, jint value2, jint id) { + __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceConstCmpInt( + JNIEnv *env, jclass cls, jint value1, jint value2) { __sanitizer_cov_trace_cmp4(value1, value2); } -void JNICALL libfuzzerIntCompareCallbackWithPc(JNIEnv &env, jclass cls, - jint value1, jint value2, - jint id) { +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceConstCmpIntWithPc( + JNIEnv *env, jclass cls, jint value1, jint value2, jint id) { __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); } -void JNICALL libfuzzerSwitchCaseCallback(JNIEnv &env, jclass cls, - jlong switch_value, - jlongArray libfuzzer_case_values, - jint id) { - jlong *case_values = env.GetLongArrayElements(libfuzzer_case_values, nullptr); - if (env.ExceptionCheck()) env.ExceptionDescribe(); +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitch( + JNIEnv *env, jclass cls, jlong switch_value, + jlongArray libfuzzer_case_values) { + jlong *case_values = + env->GetLongArrayElements(libfuzzer_case_values, nullptr); + if (env->ExceptionCheck()) env->ExceptionDescribe(); __sanitizer_cov_trace_switch(switch_value, reinterpret_cast(case_values)); - env.ReleaseLongArrayElements(libfuzzer_case_values, case_values, JNI_ABORT); - if (env.ExceptionCheck()) env.ExceptionDescribe(); + env->ReleaseLongArrayElements(libfuzzer_case_values, case_values, JNI_ABORT); + if (env->ExceptionCheck()) env->ExceptionDescribe(); } -void JNICALL libfuzzerSwitchCaseCallbackWithPc(JNIEnv &env, jclass cls, - jlong switch_value, - jlongArray libfuzzer_case_values, - jint id) { - jlong *case_values = env.GetLongArrayElements(libfuzzer_case_values, nullptr); - if (env.ExceptionCheck()) env.ExceptionDescribe(); +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitchWithPc( + JNIEnv *env, jclass cls, jlong switch_value, + jlongArray libfuzzer_case_values, jint id) { + jlong *case_values = + env->GetLongArrayElements(libfuzzer_case_values, nullptr); + if (env->ExceptionCheck()) env->ExceptionDescribe(); __sanitizer_cov_trace_switch_with_pc( idToPc(id), switch_value, reinterpret_cast(case_values)); - env.ReleaseLongArrayElements(libfuzzer_case_values, case_values, JNI_ABORT); - if (env.ExceptionCheck()) env.ExceptionDescribe(); + env->ReleaseLongArrayElements(libfuzzer_case_values, case_values, JNI_ABORT); + if (env->ExceptionCheck()) env->ExceptionDescribe(); } -void JNICALL libfuzzerLongDivCallback(JNIEnv &env, jclass cls, jlong value, - jint id) { +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivLong( + JNIEnv *env, jclass cls, jlong value) { __sanitizer_cov_trace_div8(value); } -void JNICALL libfuzzerLongDivCallbackWithPc(JNIEnv &env, jclass cls, - jlong value, jint id) { +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivLongWithPc( + JNIEnv *env, jclass cls, jlong value, jint id) { __sanitizer_cov_trace_div8_with_pc(idToPc(id), value); } -void JNICALL libfuzzerIntDivCallback(JNIEnv &env, jclass cls, jint value, - jint id) { +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivInt( + JNIEnv *env, jclass cls, jint value) { __sanitizer_cov_trace_div4(value); } -void JNICALL libfuzzerIntDivCallbackWithPc(JNIEnv &env, jclass cls, jint value, - jint id) { +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivIntWithPc( + JNIEnv *env, jclass cls, jint value, jint id) { __sanitizer_cov_trace_div4_with_pc(idToPc(id), value); } -void JNICALL libfuzzerGepCallback(JNIEnv &env, jclass cls, jlong idx, jint id) { +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceGep( + JNIEnv *env, jclass cls, jlong idx) { __sanitizer_cov_trace_gep(static_cast(idx)); } -void JNICALL libfuzzerGepCallbackWithPc(JNIEnv &env, jclass cls, jlong idx, - jint id) { +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceGepWithPc( + JNIEnv *env, jclass cls, jlong idx, jint id) { __sanitizer_cov_trace_gep_with_pc(idToPc(id), static_cast(idx)); } -void JNICALL libfuzzerPcIndirCallback(JNIEnv &env, jclass cls, jint caller_id, - jint callee_id) { +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_tracePcIndir0( + JNIEnv *env, jclass cls, jint caller_id, jint callee_id) { __sanitizer_cov_trace_pc_indir_with_pc(idToPc(caller_id), static_cast(callee_id)); } +namespace { bool is_using_native_libraries = false; std::once_flag ignore_list_flag; std::vector> ignore_for_interception_ranges; -extern "C" [[maybe_unused]] bool __sanitizer_weak_is_relevant_pc( - void *caller_pc) { - // If the fuzz target is not using native libraries, calls to strcmp, memcmp, - // etc. should never be intercepted. The values reported if they were at best - // duplicate the values received from our bytecode instrumentation and at - // worst pollute the table of recent compares with string internal to the JDK. - if (!is_using_native_libraries) return false; - // If the fuzz target is using native libraries, intercept calls only if they - // don't originate from those address ranges that are known to belong to the - // JDK. - return std::none_of(ignore_for_interception_ranges.cbegin(), - ignore_for_interception_ranges.cend(), - [caller_pc](const auto &range) { - uintptr_t start; - uintptr_t end; - std::tie(start, end) = range; - auto address = reinterpret_cast(caller_pc); - return start <= address && address <= end; - }); -} - /** * Adds the address ranges of executable segmentes of the library lib_name to * the ignorelist for C standard library function interception (strcmp, memcmp, @@ -291,8 +293,32 @@ const std::vector kLibrariesToIgnoreForInterception = { "libjimage.so", "libjli.so", "libjvm.so", "libnet.so", "libverify.so", "libzip.so", }; +} // namespace + +extern "C" [[maybe_unused]] bool __sanitizer_weak_is_relevant_pc( + void *caller_pc) { + // If the fuzz target is not using native libraries, calls to strcmp, memcmp, + // etc. should never be intercepted. The values reported if they were at best + // duplicate the values received from our bytecode instrumentation and at + // worst pollute the table of recent compares with string internal to the JDK. + if (!is_using_native_libraries) return false; + // If the fuzz target is using native libraries, intercept calls only if they + // don't originate from those address ranges that are known to belong to the + // JDK. + return std::none_of(ignore_for_interception_ranges.cbegin(), + ignore_for_interception_ranges.cend(), + [caller_pc](const auto &range) { + uintptr_t start; + uintptr_t end; + std::tie(start, end) = range; + auto address = reinterpret_cast(caller_pc); + return start <= address && address <= end; + }); +} -void JNICALL handleLibraryLoad(JNIEnv &env, jclass cls) { +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_handleLibraryLoad( + JNIEnv *env, jclass cls) { std::call_once(ignore_list_flag, [] { LOG(INFO) << "detected a native library load, enabling interception for libc " @@ -304,108 +330,3 @@ void JNICALL handleLibraryLoad(JNIEnv &env, jclass cls) { is_using_native_libraries = true; }); } - -void registerCallback(JNIEnv &env, const char *java_hooks_class_name, - const JNINativeMethod *methods, int num_methods) { - auto java_hooks_class = env.FindClass(java_hooks_class_name); - if (java_hooks_class == nullptr) { - env.ExceptionDescribe(); - throw std::runtime_error( - absl::StrFormat("could not find class %s", java_hooks_class_name)); - } - LOG(INFO) << "registering hooks for class " << java_hooks_class_name; - env.RegisterNatives(java_hooks_class, methods, num_methods); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - throw std::runtime_error("could not register native callbacks"); - } -} -} // namespace - -namespace jazzer { - -bool registerFuzzerCallbacks(JNIEnv &env) { - if (FLAGS_fake_pcs) { - LOG(INFO) << "using callback variants with fake pcs"; - CalibrateTrampoline(); - } - { - JNINativeMethod string_methods[]{ - {(char *)"traceMemcmp", (char *)"([B[BII)V", - (void *)&libfuzzerByteCompareCallback}, - {(char *)"traceStrcmp", - (char *)"(Ljava/lang/String;Ljava/lang/String;II)V", - (void *)&libfuzzerStringCompareCallback}, - {(char *)"traceStrstr", - (char *)"(Ljava/lang/String;Ljava/lang/String;I)V", - (void *)&libfuzzerStringContainCallback}}; - - registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, string_methods, - sizeof(string_methods) / sizeof(string_methods[0])); - } - - { - JNINativeMethod cmp_methods[]{ - {(char *)"traceCmpLong", (char *)"(JJI)V", - (void *)(FLAGS_fake_pcs ? &libfuzzerLongCompareCallbackWithPc - : &libfuzzerLongCompareCallback)}, - {(char *)"traceCmpInt", (char *)"(III)V", - (void *)(FLAGS_fake_pcs ? &libfuzzerIntCompareCallbackWithPc - : &libfuzzerIntCompareCallback)}, - // libFuzzer internally treats const comparisons the same as - // non-constant cmps. - {(char *)"traceConstCmpInt", (char *)"(III)V", - (void *)(FLAGS_fake_pcs ? &libfuzzerIntCompareCallbackWithPc - : &libfuzzerIntCompareCallback)}, - {(char *)"traceSwitch", (char *)"(J[JI)V", - (void *)(FLAGS_fake_pcs ? &libfuzzerSwitchCaseCallbackWithPc - : &libfuzzerSwitchCaseCallback)}}; - - registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, cmp_methods, - sizeof(cmp_methods) / sizeof(cmp_methods[0])); - } - - { - JNINativeMethod div_methods[]{ - {(char *)"traceDivLong", (char *)"(JI)V", - (void *)(FLAGS_fake_pcs ? &libfuzzerLongDivCallbackWithPc - : &libfuzzerLongDivCallback)}, - {(char *)"traceDivInt", (char *)"(II)V", - (void *)(FLAGS_fake_pcs ? &libfuzzerIntDivCallbackWithPc - : &libfuzzerIntDivCallback)}}; - - registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, div_methods, - sizeof(div_methods) / sizeof(div_methods[0])); - } - - { - JNINativeMethod gep_methods[]{ - {(char *)"traceGep", (char *)"(JI)V", - (void *)(FLAGS_fake_pcs ? &libfuzzerGepCallbackWithPc - : &libfuzzerGepCallback)}}; - - registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, gep_methods, - sizeof(gep_methods) / sizeof(gep_methods[0])); - } - - { - JNINativeMethod indir_methods[]{{(char *)"tracePcIndir", (char *)"(II)V", - (void *)(&libfuzzerPcIndirCallback)}}; - - registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, indir_methods, - sizeof(indir_methods) / sizeof(indir_methods[0])); - } - - { - JNINativeMethod native_methods[]{{(char *)"handleLibraryLoad", - (char *)"()V", - (void *)(&handleLibraryLoad)}}; - - registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, native_methods, - sizeof(native_methods) / sizeof(native_methods[0])); - } - - return env.ExceptionCheck(); -} - -} // namespace jazzer diff --git a/driver/libfuzzer_callbacks.h b/driver/libfuzzer_callbacks.h deleted file mode 100644 index 985809a7..00000000 --- a/driver/libfuzzer_callbacks.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include - -namespace jazzer { - -bool registerFuzzerCallbacks(JNIEnv &env); - -} // namespace jazzer diff --git a/driver/libfuzzer_driver.h b/driver/libfuzzer_driver.h index 3f269c8b..20fd0280 100644 --- a/driver/libfuzzer_driver.h +++ b/driver/libfuzzer_driver.h @@ -23,7 +23,6 @@ #include "fuzz_target_runner.h" #include "fuzzed_data_provider.h" #include "jvm_tooling.h" -#include "libfuzzer_callbacks.h" namespace jazzer { diff --git a/driver/sanitizer_hooks_with_pc.cpp b/driver/sanitizer_hooks_with_pc.cpp index 08258d8f..ab80ccfd 100644 --- a/driver/sanitizer_hooks_with_pc.cpp +++ b/driver/sanitizer_hooks_with_pc.cpp @@ -148,7 +148,7 @@ void set_trampoline_offset() { // in their lowest 9 bite. This offset is constant for each binary, but may vary // based on code generation specifics. By calibrating the trampoline, the fuzzer // behavior is fully determined by the seed. -void CalibrateTrampoline() { +__attribute__((constructor)) void CalibrateTrampoline() { trampoline(0, 0, reinterpret_cast(&set_trampoline_offset), 0); } -- cgit v1.2.3 From d3aa8c8eb25f2ee02b7ecfa154063c7e544b49b9 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 23 Feb 2022 17:57:32 +0100 Subject: Wrap module-style transform method in try-catch Ensures that Throwables thrown during instrumentation are not silently swallowed. --- .../jazzer/agent/RuntimeInstrumentor.kt | 44 +++++++++++++--------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt index e3f4fec6..fab9aab2 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt @@ -118,26 +118,34 @@ internal class RuntimeInstrumentor( protectionDomain: ProtectionDomain?, classfileBuffer: ByteArray ): ByteArray? { - if (module != null && !module.canRead(RuntimeInstrumentor::class.java.module)) { - // Make all other modules read our (unnamed) module, which allows them to access the classes needed by the - // instrumentations, e.g. CoverageMap. If a module can't be modified, it should not be instrumented as the - // injected bytecode might throw NoClassDefFoundError. - // https://mail.openjdk.java.net/pipermail/jigsaw-dev/2021-May/014663.html - if (!instrumentation.isModifiableModule(module)) { - val prettyClassName = internalClassName.replace('/', '.') - println("WARN: Failed to instrument $prettyClassName in unmodifiable module ${module.name}, skipping") - return null + return try { + if (module != null && !module.canRead(RuntimeInstrumentor::class.java.module)) { + // Make all other modules read our (unnamed) module, which allows them to access the classes needed by the + // instrumentations, e.g. CoverageMap. If a module can't be modified, it should not be instrumented as the + // injected bytecode might throw NoClassDefFoundError. + // https://mail.openjdk.java.net/pipermail/jigsaw-dev/2021-May/014663.html + if (!instrumentation.isModifiableModule(module)) { + val prettyClassName = internalClassName.replace('/', '.') + println("WARN: Failed to instrument $prettyClassName in unmodifiable module ${module.name}, skipping") + return null + } + instrumentation.redefineModule( + module, + /* extraReads */ setOf(RuntimeInstrumentor::class.java.module), + emptyMap(), + emptyMap(), + emptySet(), + emptyMap() + ) } - instrumentation.redefineModule( - module, - /* extraReads */ setOf(RuntimeInstrumentor::class.java.module), - emptyMap(), - emptyMap(), - emptySet(), - emptyMap() - ) + transform(loader, internalClassName, classBeingRedefined, protectionDomain, classfileBuffer) + } catch (t: Throwable) { + // Throwables raised from transform are silently dropped, making it extremely hard to detect instrumentation + // failures. The docs advise to use a top-level try-catch. + // https://docs.oracle.com/javase/9/docs/api/java/lang/instrument/ClassFileTransformer.html + t.printStackTrace() + throw t } - return transform(loader, internalClassName, classBeingRedefined, protectionDomain, classfileBuffer) } @OptIn(kotlin.time.ExperimentalTime::class) -- cgit v1.2.3 From a1a8f93fdeea804c16e45537c36985489b93703e Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 23 Feb 2022 17:58:52 +0100 Subject: Fix UnsatisfiedLinkError when fuzz target has on coverage Previously, CoverageMap could be statically initialized before ClassInstrumentor, resulting in an UnsatisfiedLinkError for its native initializer. --- .../com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt | 8 ++++++++ .../code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt | 9 --------- tests/BUILD.bazel | 10 ++++++++++ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt index fab9aab2..a5b34ea3 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt @@ -205,4 +205,12 @@ internal class RuntimeInstrumentor( instrumentedBytecode } } + + companion object { + init { + // Calls JNI_OnLoad_jazzer_initialize in the driver, which ensures that dynamically + // linked JNI methods are resolved against it. + System.loadLibrary("jazzer_initialize") + } + } } diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt index e3fec450..4c3eabcb 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt @@ -47,15 +47,6 @@ class ClassInstrumentor constructor(bytecode: ByteArray) { } companion object { - init { - try { - // Calls JNI_OnLoad_jazzer_initialize in the driver, which registers the native methods. - System.loadLibrary("jazzer_initialize") - } catch (_: UnsatisfiedLinkError) { - // Make it possible to use (parts of) the agent without the driver. - } - } - val defaultEdgeCoverageStrategy = StaticMethodStrategy() val defaultCoverageMap = CoverageMap::class.java } diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index c83b325c..e3f6cb6b 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -34,3 +34,13 @@ java_fuzz_target_test( hook_classes = ["com.example.HookDependenciesFuzzer"], target_class = "com.example.HookDependenciesFuzzer", ) + +java_fuzz_target_test( + name = "AutofuzzWithoutCoverage", + fuzzer_args = [ + # Autofuzz a method that triggers no coverage instrumentation (the Java standard library is + # excluded by default). + "--autofuzz=java.util.regex.Pattern::compile", + "--keep_going=1", + ], +) -- cgit v1.2.3 From c2d268761c6a80681f7bda52becd0cd4463abe7b Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sat, 19 Feb 2022 21:35:59 +0100 Subject: Replace nop with ret sled on ARM64 Instead of running through 64 nops on average, every instruction in the trampoline now jumps directly to the end of the function. --- driver/sanitizer_hooks_with_pc.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/driver/sanitizer_hooks_with_pc.cpp b/driver/sanitizer_hooks_with_pc.cpp index ab80ccfd..b69e2dd9 100644 --- a/driver/sanitizer_hooks_with_pc.cpp +++ b/driver/sanitizer_hooks_with_pc.cpp @@ -82,7 +82,7 @@ __attribute__((noinline)) void trampoline(uint64_t arg1, uint64_t arg2, [[maybe_unused]] register uint64_t fake_pc_loc asm(REG_4) = fake_pc; #ifdef __aarch64__ asm volatile( - // Load address of the nop sled into the default register for the return + // Load address of the ret sled into the default register for the return // address (offset of four instructions, which means 16 bytes). "adr x30, 16 \n\t" // Clear the lowest 2 bits of fake_pc. All arm64 instructions are four @@ -94,10 +94,11 @@ __attribute__((noinline)) void trampoline(uint64_t arg1, uint64_t arg2, // Call the function by jumping to it and reusing all registers except // for the modified return address register r30. "br %[func] \n\t" - // The nop sled for arm64 consists of 128 nop instructions, each of which - // is 4 bytes long. It thus has the same byte length of 4 * 128 = 512 as - // the x86_64 sled, but coarser granularity. - REPEAT_128("nop \n\t") + // The ret sled for arm64 consists of 128 b instructions jumping to the + // end of the function. Each instruction is 4 bytes long. The sled thus + // has the same byte length of 4 * 128 = 512 as the x86_64 sled, but + // coarser granularity. + REPEAT_128("b end_of_function\n\t") "end_of_function:\n\t" : : "r"(arg1_loc), "r"(arg2_loc), [func] "r"(func_loc), [fake_pc] "r"(fake_pc_loc) -- cgit v1.2.3 From f680c8ff446798bdd510c7c6affac3fa6df08fb3 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sat, 26 Feb 2022 11:26:35 +0100 Subject: Update jazzer-libfuzzer See https://github.com/CodeIntelligenceTesting/llvm-project-jazzer/compare/2021-11-30...2022-02-26 for the only change ("Do not redundantly copy fuzzer input"). --- repositories.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repositories.bzl b/repositories.bzl index 6ad8f9f5..4276db0c 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -145,6 +145,6 @@ def jazzer_dependencies(): http_archive, name = "jazzer_libfuzzer", build_file = Label("//third_party:libFuzzer.BUILD"), - sha256 = "efde37ab5a9e4fff67f8cd43b701be5ea5ddb74a3bc10e4d8e91a614070145c3", - url = "https://github.com/CodeIntelligenceTesting/llvm-project-jazzer/releases/download/2021-11-30/jazzer-libfuzzer-2021-11-30.tar.gz", + sha256 = "630202d393114f828f350da57d42a6d4fa12ed614a578021d87ba8056dbec4c4", + url = "https://github.com/CodeIntelligenceTesting/llvm-project-jazzer/releases/download/2022-02-26/jazzer-libfuzzer-2022-02-26.tar.gz", ) -- cgit v1.2.3 From 4d8b61658243c71181bf9f0ac51c9ae3887f2894 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Fri, 18 Feb 2022 08:52:28 +0100 Subject: Add LDAP sanitizer --- maven.bzl | 1 + maven_install.json | 15 +- sanitizers/sanitizers.bzl | 1 + .../jazzer/sanitizers/BUILD.bazel | 1 + .../jazzer/sanitizers/LdapInjection.kt | 123 ++++++++ sanitizers/src/test/java/com/example/BUILD.bazel | 26 ++ .../src/test/java/com/example/LdapDnInjection.java | 39 +++ .../test/java/com/example/LdapSearchInjection.java | 39 +++ .../example/ldap/MockInitialContextFactory.java | 26 ++ .../java/com/example/ldap/MockLdapContext.java | 316 +++++++++++++++++++++ 10 files changed, 585 insertions(+), 2 deletions(-) create mode 100644 sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt create mode 100644 sanitizers/src/test/java/com/example/LdapDnInjection.java create mode 100644 sanitizers/src/test/java/com/example/LdapSearchInjection.java create mode 100644 sanitizers/src/test/java/com/example/ldap/MockInitialContextFactory.java create mode 100644 sanitizers/src/test/java/com/example/ldap/MockLdapContext.java diff --git a/maven.bzl b/maven.bzl index c7fa4c81..4440bd39 100644 --- a/maven.bzl +++ b/maven.bzl @@ -35,6 +35,7 @@ MAVEN_ARTIFACTS = [ "javax.xml.bind:jaxb-api:2.3.1", "javax.el:javax.el-api:3.0.1-b06", "org.hibernate:hibernate-validator:5.2.4.Final", + "com.unboundid:unboundid-ldapsdk:6.0.3", maven.artifact("org.apache.logging.log4j", "log4j-api", "2.14.1", testonly = True), maven.artifact("org.apache.logging.log4j", "log4j-core", "2.14.1", testonly = True), ] diff --git a/maven_install.json b/maven_install.json index bb140da6..12f6c092 100644 --- a/maven_install.json +++ b/maven_install.json @@ -1,8 +1,8 @@ { "dependency_tree": { "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL", - "__INPUT_ARTIFACTS_HASH": -730060225, - "__RESOLVED_ARTIFACTS_HASH": 1247615742, + "__INPUT_ARTIFACTS_HASH": 1013648735, + "__RESOLVED_ARTIFACTS_HASH": 304464594, "conflict_resolution": {}, "dependencies": [ { @@ -125,6 +125,17 @@ "sha256": "0f7d702ba2cdfebac1d4f1154d4b107f508d5920c268263087a5f4b80ddb7446", "url": "https://repo1.maven.org/maven2/com/mikesamuel/json-sanitizer/1.2.1/json-sanitizer-1.2.1.jar" }, + { + "coord": "com.unboundid:unboundid-ldapsdk:6.0.3", + "dependencies": [], + "directDependencies": [], + "file": "v1/https/repo1.maven.org/maven2/com/unboundid/unboundid-ldapsdk/6.0.3/unboundid-ldapsdk-6.0.3.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/unboundid/unboundid-ldapsdk/6.0.3/unboundid-ldapsdk-6.0.3.jar" + ], + "sha256": "a635f130b482d8b02cc317632de762518d6bfedfecbd6972d1029124aaaf89d8", + "url": "https://repo1.maven.org/maven2/com/unboundid/unboundid-ldapsdk/6.0.3/unboundid-ldapsdk-6.0.3.jar" + }, { "coord": "javax.activation:javax.activation-api:1.2.0", "dependencies": [], diff --git a/sanitizers/sanitizers.bzl b/sanitizers/sanitizers.bzl index 34367d0a..8812e3ee 100644 --- a/sanitizers/sanitizers.bzl +++ b/sanitizers/sanitizers.bzl @@ -17,6 +17,7 @@ _sanitizer_package_prefix = "com.code_intelligence.jazzer.sanitizers." _sanitizer_class_names = [ "Deserialization", "ExpressionLanguageInjection", + "LdapInjection", "NamingContextLookup", "OsCommandInjection", "ReflectiveCall", diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel index f4d0f7ae..ec324526 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel @@ -5,6 +5,7 @@ kt_jvm_library( srcs = [ "Deserialization.kt", "ExpressionLanguageInjection.kt", + "LdapInjection.kt", "NamingContextLookup.kt", "OsCommandInjection.kt", "ReflectiveCall.kt", diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt new file mode 100644 index 00000000..7aee6873 --- /dev/null +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt @@ -0,0 +1,123 @@ +// 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.sanitizers + +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical +import com.code_intelligence.jazzer.api.HookType +import com.code_intelligence.jazzer.api.Jazzer +import com.code_intelligence.jazzer.api.MethodHook +import com.code_intelligence.jazzer.api.MethodHooks +import java.lang.Exception +import java.lang.invoke.MethodHandle +import javax.naming.NamingException +import javax.naming.directory.InvalidSearchFilterException + +/** + * Detects LDAP DN and search filter injections. + * + * Untrusted input has to be escaped in such a way that queries remain valid otherwise an injection + * could be possible. This sanitizer guides the fuzzer to inject insecure characters. If an exception + * is raised during execution the fuzzer was able to inject an invalid pattern, otherwise all input + * was escaped correctly. + * + * Only the search methods are hooked, other methods are not used in injection attacks. Furthermore, + * only string parameters are checked, [javax.naming.Name] already validates inputs according to RFC2253. + * + * [javax.naming.directory.InitialDirContext] creates an initial context through the context factory + * stated in [javax.naming.Context.INITIAL_CONTEXT_FACTORY]. Other method calls are delegated to the + * initial context factory of type [javax.naming.directory.DirContext]. This is also the case for + * subclass [javax.naming.ldap.InitialLdapContext]. + */ +@Suppress("unused_parameter", "unused") +object LdapInjection { + + // Characters to escape in DNs + private const val NAME_CHARACTERS = "\\+<>,;\"=" + + // Characters to escape in search filter queries + private const val FILTER_CHARACTERS = "*()\\\u0000" + + @MethodHooks( + // Single object lookup, possible DN injection + MethodHook( + type = HookType.REPLACE, + targetClassName = "javax.naming.directory.DirContext", + targetMethod = "search", + targetMethodDescriptor = "(Ljava/lang/String;Ljavax/naming.directory/Attributes;)Ljavax/naming/NamingEnumeration;", + additionalClassesToHook = ["javax.naming.directory.InitialDirContext"] + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "javax.naming.directory.DirContext", + targetMethod = "search", + targetMethodDescriptor = "(Ljava/lang/String;Ljavax/naming.directory/Attributes;[Ljava/lang/Sting;)Ljavax/naming/NamingEnumeration;", + additionalClassesToHook = ["javax.naming.directory.InitialDirContext"] + ), + + // Object search, possible DN and search filter injection + MethodHook( + type = HookType.REPLACE, + targetClassName = "javax.naming.directory.DirContext", + targetMethod = "search", + targetMethodDescriptor = "(Ljava/lang/String;Ljava/lang/String;Ljavax/naming/directory/SearchControls;)Ljavax/naming/NamingEnumeration;", + additionalClassesToHook = ["javax.naming.directory.InitialDirContext"] + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "javax.naming.directory.DirContext", + targetMethod = "search", + targetMethodDescriptor = "(Ljavax/naming/Name;Ljava/lang/String;[Ljava.lang.Object;Ljavax/naming/directory/SearchControls;)Ljavax/naming/NamingEnumeration;", + additionalClassesToHook = ["javax.naming.directory.InitialDirContext"] + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "javax.naming.directory.DirContext", + targetMethod = "search", + targetMethodDescriptor = "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;Ljavax/naming/directory/SearchControls;)Ljavax/naming/NamingEnumeration;", + additionalClassesToHook = ["javax.naming.directory.InitialDirContext"] + ) + ) + @JvmStatic + fun searchLdapContext(method: MethodHandle, thisObject: Any?, args: Array, hookId: Int): Any? { + try { + return method.invokeWithArguments(thisObject, *args).also { + (args[0] as? String)?.let { name -> + Jazzer.guideTowardsEquality(name, NAME_CHARACTERS, hookId) + } + (args[1] as? String)?.let { filter -> + Jazzer.guideTowardsEquality(filter, FILTER_CHARACTERS, 31 * hookId) + } + } + } catch (e: Exception) { + return when (e) { + is InvalidSearchFilterException -> + Jazzer.reportFindingFromHook( + FuzzerSecurityIssueCritical( + """LDAP Injection +Search filters based on untrusted data must be escape as specified in RFC 4515.""" + ) + ) + is NamingException -> + Jazzer.reportFindingFromHook( + FuzzerSecurityIssueCritical( + """LDAP Injection +Distinguished Names based on untrusted data must be escaped as specified in RFC 2253.""" + ) + ) + else -> throw e + } + } + } +} diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index d1edfab7..c38bebc1 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -47,6 +47,32 @@ java_fuzz_target_test( target_class = "com.example.OsCommandInjectionRuntimeExec", ) +java_fuzz_target_test( + name = "LdapSearchInjection", + srcs = [ + "LdapSearchInjection.java", + "ldap/MockInitialContextFactory.java", + "ldap/MockLdapContext.java", + ], + target_class = "com.example.LdapSearchInjection", + deps = [ + "@maven//:com_unboundid_unboundid_ldapsdk", + ], +) + +java_fuzz_target_test( + name = "LdapDnInjection", + srcs = [ + "LdapDnInjection.java", + "ldap/MockInitialContextFactory.java", + "ldap/MockLdapContext.java", + ], + target_class = "com.example.LdapDnInjection", + deps = [ + "@maven//:com_unboundid_unboundid_ldapsdk", + ], +) + java_fuzz_target_test( name = "RegexCanonEqInjection", srcs = ["RegexCanonEqInjection.java"], diff --git a/sanitizers/src/test/java/com/example/LdapDnInjection.java b/sanitizers/src/test/java/com/example/LdapDnInjection.java new file mode 100644 index 00000000..911db1dc --- /dev/null +++ b/sanitizers/src/test/java/com/example/LdapDnInjection.java @@ -0,0 +1,39 @@ +// 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.example; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import java.util.Hashtable; +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.SearchControls; + +public class LdapDnInjection { + private static InitialDirContext ctx; + + public static void fuzzerInitialize() throws NamingException { + Hashtable env = new Hashtable<>(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.example.ldap.MockInitialContextFactory"); + ctx = new InitialDirContext(env); + } + + public static void fuzzerTestOneInput(FuzzedDataProvider fuzzedDataProvider) throws Exception { + // Externally provided DN input needs to be escaped properly + String ou = fuzzedDataProvider.consumeRemainingAsString(); + String base = "ou=" + ou + ",dc=example,dc=com"; + ctx.search(base, "(&(uid=foo)(cn=bar))", new SearchControls()); + } +} diff --git a/sanitizers/src/test/java/com/example/LdapSearchInjection.java b/sanitizers/src/test/java/com/example/LdapSearchInjection.java new file mode 100644 index 00000000..b3dfee74 --- /dev/null +++ b/sanitizers/src/test/java/com/example/LdapSearchInjection.java @@ -0,0 +1,39 @@ +// 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.example; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import java.util.Hashtable; +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.directory.SearchControls; +import javax.naming.ldap.InitialLdapContext; + +public class LdapSearchInjection { + private static InitialLdapContext ctx; + + public static void fuzzerInitialize() throws NamingException { + Hashtable env = new Hashtable<>(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.example.ldap.MockInitialContextFactory"); + ctx = new InitialLdapContext(env, null); + } + + public static void fuzzerTestOneInput(FuzzedDataProvider fuzzedDataProvider) throws Exception { + // Externally provided LDAP query input needs to be escaped properly + String username = fuzzedDataProvider.consumeRemainingAsAsciiString(); + String filter = "(&(uid=" + username + ")(ou=security))"; + ctx.search("dc=example,dc=com", filter, new SearchControls()); + } +} diff --git a/sanitizers/src/test/java/com/example/ldap/MockInitialContextFactory.java b/sanitizers/src/test/java/com/example/ldap/MockInitialContextFactory.java new file mode 100644 index 00000000..b674f5c5 --- /dev/null +++ b/sanitizers/src/test/java/com/example/ldap/MockInitialContextFactory.java @@ -0,0 +1,26 @@ +// 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.example.ldap; + +import java.util.Hashtable; +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.spi.InitialContextFactory; + +public class MockInitialContextFactory implements InitialContextFactory { + public Context getInitialContext(Hashtable environment) { + return new MockLdapContext(); + } +} diff --git a/sanitizers/src/test/java/com/example/ldap/MockLdapContext.java b/sanitizers/src/test/java/com/example/ldap/MockLdapContext.java new file mode 100644 index 00000000..a51fadcd --- /dev/null +++ b/sanitizers/src/test/java/com/example/ldap/MockLdapContext.java @@ -0,0 +1,316 @@ +// 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.example.ldap; + +import com.unboundid.ldap.sdk.DN; +import com.unboundid.ldap.sdk.Filter; +import com.unboundid.ldap.sdk.LDAPException; +import java.util.Hashtable; +import javax.naming.*; +import javax.naming.directory.*; +import javax.naming.ldap.*; + +/** + * Mock LdapContex implementation to test LdapInjection hook configuration. + * + * Only {@code com.example.ldap.MockLdapContext#search(java.lang.String, java.lang.String, + * javax.naming.directory.SearchControls)} is implemented to validate DN and filer query. + */ +public class MockLdapContext implements LdapContext { + @Override + public ExtendedResponse extendedOperation(ExtendedRequest request) throws NamingException { + return null; + } + + @Override + public LdapContext newInstance(Control[] requestControls) throws NamingException { + return this; + } + + @Override + public void reconnect(Control[] connCtls) throws NamingException {} + + @Override + public Control[] getConnectControls() throws NamingException { + return new Control[0]; + } + + @Override + public void setRequestControls(Control[] requestControls) throws NamingException {} + + @Override + public Control[] getRequestControls() throws NamingException { + return new Control[0]; + } + + @Override + public Control[] getResponseControls() throws NamingException { + return new Control[0]; + } + + @Override + public Attributes getAttributes(Name name) throws NamingException { + return null; + } + + @Override + public Attributes getAttributes(String name) throws NamingException { + return null; + } + + @Override + public Attributes getAttributes(Name name, String[] attrIds) throws NamingException { + return null; + } + + @Override + public Attributes getAttributes(String name, String[] attrIds) throws NamingException { + return null; + } + + @Override + public void modifyAttributes(Name name, int mod_op, Attributes attrs) throws NamingException {} + + @Override + public void modifyAttributes(String name, int mod_op, Attributes attrs) throws NamingException {} + + @Override + public void modifyAttributes(Name name, ModificationItem[] mods) throws NamingException {} + + @Override + public void modifyAttributes(String name, ModificationItem[] mods) throws NamingException {} + + @Override + public void bind(Name name, Object obj, Attributes attrs) throws NamingException {} + + @Override + public void bind(String name, Object obj, Attributes attrs) throws NamingException {} + + @Override + public void rebind(Name name, Object obj, Attributes attrs) throws NamingException {} + + @Override + public void rebind(String name, Object obj, Attributes attrs) throws NamingException {} + + @Override + public DirContext createSubcontext(Name name, Attributes attrs) throws NamingException { + return this; + } + + @Override + public DirContext createSubcontext(String name, Attributes attrs) throws NamingException { + return this; + } + + @Override + public DirContext getSchema(Name name) throws NamingException { + return this; + } + + @Override + public DirContext getSchema(String name) throws NamingException { + return this; + } + + @Override + public DirContext getSchemaClassDefinition(Name name) throws NamingException { + return this; + } + + @Override + public DirContext getSchemaClassDefinition(String name) throws NamingException { + return this; + } + + @Override + public NamingEnumeration search(Name name, Attributes matchingAttributes, + String[] attributesToReturn) throws NamingException { + return null; + } + + @Override + public NamingEnumeration search(String name, Attributes matchingAttributes, + String[] attributesToReturn) throws NamingException { + return null; + } + + @Override + public NamingEnumeration search(Name name, Attributes matchingAttributes) + throws NamingException { + return null; + } + + @Override + public NamingEnumeration search(String name, Attributes matchingAttributes) + throws NamingException { + return null; + } + + @Override + public NamingEnumeration search(Name name, String filter, SearchControls cons) + throws NamingException { + return null; + } + + @Override + public NamingEnumeration search(String name, String filter, SearchControls cons) + throws NamingException { + // Use UnboundID LDAP to validate DN and filter + if (!DN.isValidDN(name)) { + throw new NamingException("Invalid DN " + name); + } + try { + Filter.create(filter); + } catch (LDAPException e) { + throw new InvalidSearchFilterException("Invalid search filter " + filter); + } + return null; + } + + @Override + public NamingEnumeration search(Name name, String filterExpr, Object[] filterArgs, + SearchControls cons) throws NamingException { + return null; + } + + @Override + public NamingEnumeration search(String name, String filterExpr, Object[] filterArgs, + SearchControls cons) throws NamingException { + return null; + } + + @Override + public Object lookup(Name name) throws NamingException { + return this; + } + + @Override + public Object lookup(String name) throws NamingException { + return this; + } + + @Override + public void bind(Name name, Object obj) throws NamingException {} + + @Override + public void bind(String name, Object obj) throws NamingException {} + + @Override + public void rebind(Name name, Object obj) throws NamingException {} + + @Override + public void rebind(String name, Object obj) throws NamingException {} + + @Override + public void unbind(Name name) throws NamingException {} + + @Override + public void unbind(String name) throws NamingException {} + + @Override + public void rename(Name oldName, Name newName) throws NamingException {} + + @Override + public void rename(String oldName, String newName) throws NamingException {} + + @Override + public NamingEnumeration list(Name name) throws NamingException { + return null; + } + + @Override + public NamingEnumeration list(String name) throws NamingException { + return null; + } + + @Override + public NamingEnumeration listBindings(Name name) throws NamingException { + return null; + } + + @Override + public NamingEnumeration listBindings(String name) throws NamingException { + return null; + } + + @Override + public void destroySubcontext(Name name) throws NamingException {} + + @Override + public void destroySubcontext(String name) throws NamingException {} + + @Override + public Context createSubcontext(Name name) throws NamingException { + return this; + } + + @Override + public Context createSubcontext(String name) throws NamingException { + return this; + } + + @Override + public Object lookupLink(Name name) throws NamingException { + return this; + } + + @Override + public Object lookupLink(String name) throws NamingException { + return this; + } + + @Override + public NameParser getNameParser(Name name) throws NamingException { + return null; + } + + @Override + public NameParser getNameParser(String name) throws NamingException { + return null; + } + + @Override + public Name composeName(Name name, Name prefix) throws NamingException { + return null; + } + + @Override + public String composeName(String name, String prefix) throws NamingException { + return null; + } + + @Override + public Object addToEnvironment(String propName, Object propVal) throws NamingException { + return null; + } + + @Override + public Object removeFromEnvironment(String propName) throws NamingException { + return null; + } + + @Override + public Hashtable getEnvironment() throws NamingException { + return null; + } + + @Override + public void close() throws NamingException {} + + @Override + public String getNameInNamespace() throws NamingException { + return null; + } +} -- cgit v1.2.3 From 954cf99458c1beb49b16e93318c9496b13fc9210 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Mon, 21 Feb 2022 17:49:05 +0100 Subject: Support hooks on interfaces If the target type of a hook is abstract or an interface it is applied on all its implementations. --- .../com/code_intelligence/jazzer/agent/Agent.kt | 65 +++++++----- .../jazzer/agent/RuntimeInstrumentor.kt | 44 ++------ .../code_intelligence/jazzer/api/MethodHook.java | 5 + .../jazzer/instrumentor/BUILD.bazel | 1 + .../code_intelligence/jazzer/instrumentor/Hook.kt | 81 ++++++++------- .../code_intelligence/jazzer/instrumentor/Hooks.kt | 111 +++++++++++++++++++++ .../jazzer/runtime/TraceCmpHooks.java | 19 +--- .../jazzer/utils/ClassNameGlobber.kt | 7 +- .../jazzer/instrumentor/AfterHooksPatchTest.kt | 3 +- .../jazzer/instrumentor/BeforeHooksPatchTest.kt | 3 +- .../jazzer/instrumentor/HookValidationTest.kt | 6 +- .../jazzer/instrumentor/ReplaceHooks.java | 7 ++ .../jazzer/instrumentor/ReplaceHooksPatchTest.kt | 3 +- .../jazzer/instrumentor/ReplaceHooksTarget.java | 7 +- .../jazzer/sanitizers/NamingContextLookup.kt | 36 ------- 15 files changed, 229 insertions(+), 169 deletions(-) create mode 100644 agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index 0da9862a..a9325702 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -17,10 +17,14 @@ package com.code_intelligence.jazzer.agent import com.code_intelligence.jazzer.instrumentor.CoverageRecorder +import com.code_intelligence.jazzer.instrumentor.Hooks import com.code_intelligence.jazzer.instrumentor.InstrumentationType -import com.code_intelligence.jazzer.instrumentor.loadHooks import com.code_intelligence.jazzer.runtime.ManifestUtils +import com.code_intelligence.jazzer.runtime.NativeLibHooks import com.code_intelligence.jazzer.runtime.SignalHandler +import com.code_intelligence.jazzer.runtime.TraceCmpHooks +import com.code_intelligence.jazzer.runtime.TraceDivHooks +import com.code_intelligence.jazzer.runtime.TraceIndirHooks import com.code_intelligence.jazzer.utils.ClassNameGlobber import java.io.File import java.lang.instrument.Instrumentation @@ -127,49 +131,56 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { } } } - val runtimeInstrumentor = RuntimeInstrumentor( - instrumentation, - classNameGlobber, - customHookClassNameGlobber, - instrumentationTypes, - idSyncFile, - dumpClassesDir, - ) - instrumentation.apply { - addTransformer(runtimeInstrumentor, true) - } + val includedHookNames = instrumentationTypes + .mapNotNull { type -> + when (type) { + InstrumentationType.CMP -> TraceCmpHooks::class.java.name + InstrumentationType.DIV -> TraceDivHooks::class.java.name + InstrumentationType.INDIR -> TraceIndirHooks::class.java.name + InstrumentationType.NATIVE -> NativeLibHooks::class.java.name + else -> null + } + } + val coverageIdSynchronizer = if (idSyncFile != null) + FileSyncCoverageIdStrategy(idSyncFile) + else + MemSyncCoverageIdStrategy() val classesToHookBeforeLoadingCustomHooks = instrumentation.allLoadedClasses .map { it.name } .filter { customHookClassNameGlobber.includes(it) } .toSet() - val customHooks = customHookNames.toSet().flatMap { hookClassName -> - try { - val hookClass = Class.forName(hookClassName) - loadHooks(hookClass).also { - println("INFO: Loaded ${it.size} hooks from $hookClassName") - }.map { Pair(it, hookClass) } - } catch (_: ClassNotFoundException) { - println("WARN: Failed to load hooks from $hookClassName") - emptyList() - } - } + + val (includedHooks, customHooks) = Hooks.loadHooks(includedHookNames.toSet(), customHookNames.toSet()) // If we don't append the JARs containing the custom hooks to the bootstrap class loader, // third-party hooks not contained in the agent JAR will not be able to instrument Java standard // library classes. These classes are loaded by the bootstrap / system class loader and would // not be considered when resolving references to hook methods, leading to NoClassDefFoundError // being thrown. - customHooks - .mapNotNull { jarUriForClass(it.second) } + customHooks.hookClasses + .mapNotNull { jarUriForClass(it) } .toSet() .map { JarFile(File(it)) } .forEach { instrumentation.appendToBootstrapClassLoaderSearch(it) } - val additionalClassesToHookInstrument = runtimeInstrumentor.registerCustomHooks(customHooks.map { it.first }) + + val runtimeInstrumentor = RuntimeInstrumentor( + instrumentation, + classNameGlobber, + customHookClassNameGlobber, + instrumentationTypes, + includedHooks.hooks, + customHooks.hooks, + customHooks.additionalHookClassNameGlobber, + coverageIdSynchronizer, + dumpClassesDir, + ) + instrumentation.addTransformer(runtimeInstrumentor, true) + val classesToHookAfterLoadingCustomHooks = instrumentation.allLoadedClasses .map { it.name } .filter { customHookClassNameGlobber.includes(it) || - additionalClassesToHookInstrument.includes(it) + customHooks.additionalHookClassNameGlobber.includes(it) } .toSet() val classesMissingHooks = diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt index a5b34ea3..f29dd7bd 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt @@ -18,11 +18,6 @@ import com.code_intelligence.jazzer.instrumentor.ClassInstrumentor import com.code_intelligence.jazzer.instrumentor.CoverageRecorder import com.code_intelligence.jazzer.instrumentor.Hook import com.code_intelligence.jazzer.instrumentor.InstrumentationType -import com.code_intelligence.jazzer.instrumentor.loadHooks -import com.code_intelligence.jazzer.runtime.NativeLibHooks -import com.code_intelligence.jazzer.runtime.TraceCmpHooks -import com.code_intelligence.jazzer.runtime.TraceDivHooks -import com.code_intelligence.jazzer.runtime.TraceIndirHooks import com.code_intelligence.jazzer.utils.ClassNameGlobber import java.lang.instrument.ClassFileTransformer import java.lang.instrument.Instrumentation @@ -37,27 +32,8 @@ internal class RuntimeInstrumentor( private val classesToFullyInstrument: ClassNameGlobber, private val classesToHookInstrument: ClassNameGlobber, private val instrumentationTypes: Set, - idSyncFile: Path?, - private val dumpClassesDir: Path?, -) : ClassFileTransformer { - - private val coverageIdSynchronizer = if (idSyncFile != null) - FileSyncCoverageIdStrategy(idSyncFile) - else - MemSyncCoverageIdStrategy() - - private val includedHooks = instrumentationTypes - .mapNotNull { type -> - when (type) { - InstrumentationType.CMP -> TraceCmpHooks::class.java - InstrumentationType.DIV -> TraceDivHooks::class.java - InstrumentationType.INDIR -> TraceIndirHooks::class.java - InstrumentationType.NATIVE -> NativeLibHooks::class.java - else -> null - } - } - .flatMap { loadHooks(it) } - + private val includedHooks: List, + private val customHooks: List, // Dedicated name globber for additional classes to hook stated in hook annotations is needed due to // existing include and exclude pattern of classesToHookInstrument. All classes are included in hook // instrumentation except the ones from default excludes, like JDK and Kotlin classes. But additional @@ -65,18 +41,10 @@ internal class RuntimeInstrumentor( // and Kotlin internals. // FIXME: Adding an additional class to hook will apply _all_ hooks to it and not only the one it's // defined in. At some point we might want to track the list of classes per custom hook rather than globally. - private var additionalClassesToHookInstrument: ClassNameGlobber = ClassNameGlobber(emptyList(), listOf()) - private val customHooks = mutableListOf() - - // Returns the globber of all classes to be instrumented with hooks that were added upon a - // hook's request. - fun registerCustomHooks(hooks: List): ClassNameGlobber { - customHooks.addAll(hooks) - additionalClassesToHookInstrument = additionalClassesToHookInstrument.withAdditionalIncludes( - hooks.flatMap(Hook::additionalClassesToHook) - ) - return additionalClassesToHookInstrument - } + private val additionalClassesToHookInstrument: ClassNameGlobber, + private val coverageIdSynchronizer: CoverageIdStrategy, + private val dumpClassesDir: Path?, +) : ClassFileTransformer { @OptIn(kotlin.time.ExperimentalTime::class) override fun transform( diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java b/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java index 108d1151..5638c232 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java @@ -143,6 +143,11 @@ public @interface MethodHook { * The name of the class that contains the method that should be hooked, * as returned by {@link Class#getName()}. *

+ * If an interface or abstract class is specified, also calls to all + * implementations and subclasses available on the classpath during startup + * are hooked, respectively. Interfaces and subclasses are not taken into + * account for concrete classes. + *

* Examples: *

    *
  • {@link String}: {@code "java.lang.String"} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel index f57cc758..4ebe9b79 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -12,6 +12,7 @@ kt_jvm_library( "Hook.kt", "HookInstrumentor.kt", "HookMethodVisitor.kt", + "Hooks.kt", "Instrumentor.kt", "StaticMethodStrategy.java", "TraceDataFlowInstrumentor.kt", diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt index 46800f82..5a6aa0ad 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt @@ -18,47 +18,65 @@ package com.code_intelligence.jazzer.instrumentor import com.code_intelligence.jazzer.api.HookType import com.code_intelligence.jazzer.api.MethodHook -import com.code_intelligence.jazzer.api.MethodHooks import com.code_intelligence.jazzer.utils.descriptor import java.lang.invoke.MethodHandle import java.lang.reflect.Method import java.lang.reflect.Modifier -class Hook private constructor(hookMethod: Method, annotation: MethodHook) { - // Allowing arbitrary exterior whitespace in the target class name allows for an easy workaround - // for mangled hooks due to shading applied to hooks. - private val targetClassName = annotation.targetClassName.trim() - val hookType = annotation.type - val targetMethodName = annotation.targetMethod - val targetMethodDescriptor = annotation.targetMethodDescriptor.takeIf { it.isNotBlank() } - val additionalClassesToHook = annotation.additionalClassesToHook.asList() - - val targetInternalClassName = targetClassName.replace('.', '/') - private val targetReturnTypeDescriptor = targetMethodDescriptor?.let { extractReturnTypeDescriptor(it) } - private val targetWrappedReturnTypeDescriptor = targetReturnTypeDescriptor?.let { getWrapperTypeDescriptor(it) } - - private val hookClassName: String = hookMethod.declaringClass.name - val hookInternalClassName = hookClassName.replace('.', '/') - val hookMethodName: String = hookMethod.name - val hookMethodDescriptor = hookMethod.descriptor +class Hook private constructor( + private val targetClassName: String, + val hookType: HookType, + val targetMethodName: String, + val targetMethodDescriptor: String?, + val additionalClassesToHook: List, + val targetInternalClassName: String, + private val targetReturnTypeDescriptor: String?, + private val targetWrappedReturnTypeDescriptor: String?, + private val hookClassName: String, + val hookInternalClassName: String, + val hookMethodName: String, + val hookMethodDescriptor: String +) { override fun toString(): String { return "$hookType $targetClassName.$targetMethodName: $hookClassName.$hookMethodName $additionalClassesToHook" } companion object { + fun createAndVerifyHook(hookMethod: Method, hookData: MethodHook, className: String): Hook { + return createHook(hookMethod, hookData, className).also { + verify(hookMethod, it) + } + } - fun verifyAndGetHook(hookMethod: Method, hookData: MethodHook): Hook { - // Verify the annotation type and extract information for debug statements. - val potentialHook = Hook(hookMethod, hookData) + private fun createHook(hookMethod: Method, annotation: MethodHook, targetClassName: String): Hook { + val targetReturnTypeDescriptor = annotation.targetMethodDescriptor + .takeIf { it.isNotBlank() }?.let { extractReturnTypeDescriptor(it) } + val hookClassName: String = hookMethod.declaringClass.name + return Hook( + targetClassName = targetClassName, + hookType = annotation.type, + targetMethodName = annotation.targetMethod, + targetMethodDescriptor = annotation.targetMethodDescriptor.takeIf { it.isNotBlank() }, + additionalClassesToHook = annotation.additionalClassesToHook.asList(), + targetInternalClassName = targetClassName.replace('.', '/'), + targetReturnTypeDescriptor = targetReturnTypeDescriptor, + targetWrappedReturnTypeDescriptor = targetReturnTypeDescriptor?.let { getWrapperTypeDescriptor(it) }, + hookClassName = hookClassName, + hookInternalClassName = hookClassName.replace('.', '/'), + hookMethodName = hookMethod.name, + hookMethodDescriptor = hookMethod.descriptor + ) + } + private fun verify(hookMethod: Method, potentialHook: Hook) { // Verify the hook method's modifiers (public static). require(Modifier.isPublic(hookMethod.modifiers)) { "$potentialHook: hook method must be public" } require(Modifier.isStatic(hookMethod.modifiers)) { "$potentialHook: hook method must be static" } // Verify the hook method's parameter count. val numParameters = hookMethod.parameters.size - when (hookData.type) { + when (potentialHook.hookType) { HookType.BEFORE, HookType.REPLACE -> require(numParameters == 4) { "$potentialHook: incorrect number of parameters (expected 4)" } HookType.AFTER -> require(numParameters == 5) { "$potentialHook: incorrect number of parameters (expected 5)" } } @@ -71,12 +89,12 @@ class Hook private constructor(hookMethod: Method, annotation: MethodHook) { require(parameterTypes[3] == Int::class.javaPrimitiveType) { "$potentialHook: fourth parameter must have type int" } // Verify the hook method's return type if possible. - when (hookData.type) { + when (potentialHook.hookType) { HookType.BEFORE, HookType.AFTER -> require(hookMethod.returnType == Void.TYPE) { "$potentialHook: return type must be void" } HookType.REPLACE -> if (potentialHook.targetReturnTypeDescriptor != null) { - if (hookData.targetMethod == "") { + if (potentialHook.targetMethodName == "") { require(hookMethod.returnType.name == potentialHook.targetClassName) { "$potentialHook: return type must be ${potentialHook.targetClassName} to match target constructor" } } else if (potentialHook.targetReturnTypeDescriptor == "V") { require(hookMethod.returnType.descriptor == "V") { "$potentialHook: return type must be void to match targetMethodDescriptor" } @@ -95,7 +113,7 @@ class Hook private constructor(hookMethod: Method, annotation: MethodHook) { } // AfterMethodHook only: Verify the type of the last parameter if known. - if (hookData.type == HookType.AFTER && potentialHook.targetReturnTypeDescriptor != null) { + if (potentialHook.hookType == HookType.AFTER && potentialHook.targetReturnTypeDescriptor != null) { require( parameterTypes[4] == java.lang.Object::class.java || parameterTypes[4].descriptor == potentialHook.targetWrappedReturnTypeDescriptor @@ -103,19 +121,6 @@ class Hook private constructor(hookMethod: Method, annotation: MethodHook) { "$potentialHook: fifth parameter must have type Object or match the descriptor ${potentialHook.targetWrappedReturnTypeDescriptor}" } } - - return potentialHook - } - } -} - -fun loadHooks(hookClass: Class<*>): List { - val hooks = mutableListOf() - for (method in hookClass.methods) { - method.getAnnotation(MethodHook::class.java)?.let { hooks.add(Hook.verifyAndGetHook(method, it)) } - method.getAnnotation(MethodHooks::class.java)?.let { - it.value.forEach { hookAnnotation -> hooks.add(Hook.verifyAndGetHook(method, hookAnnotation)) } } } - return hooks } diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt new file mode 100644 index 00000000..676ec5c7 --- /dev/null +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt @@ -0,0 +1,111 @@ +// 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.instrumentor + +import com.code_intelligence.jazzer.api.MethodHook +import com.code_intelligence.jazzer.api.MethodHooks +import com.code_intelligence.jazzer.utils.ClassNameGlobber +import io.github.classgraph.ClassGraph +import io.github.classgraph.ScanResult +import java.lang.reflect.Method + +data class Hooks( + val hooks: List, + val hookClasses: Set>, + val additionalHookClassNameGlobber: ClassNameGlobber +) { + + companion object { + fun loadHooks(vararg hookClassNames: Set): List { + return ClassGraph() + .enableClassInfo() + .enableSystemJarsAndModules() + .rejectPackages("jaz.*", "com.code_intelligence.jazzer.*") + .scan() + .use { scanResult -> + // Capture scanResult in HooksLoader field to not pass it through + // all internal hook loading methods. + val loader = HooksLoader(scanResult) + hookClassNames.map(loader::load) + } + } + + private class HooksLoader(private val scanResult: ScanResult) { + fun load(hookClassNames: Set): Hooks { + val hooksWithHookClasses = hookClassNames.flatMap(::loadHooks) + val hooks = hooksWithHookClasses.map { it.first } + val hookClasses = hooksWithHookClasses.map { it.second }.toSet() + val additionalHookClassNameGlobber = ClassNameGlobber( + hooks.flatMap(Hook::additionalClassesToHook), + emptyList() + ) + return Hooks(hooks, hookClasses, additionalHookClassNameGlobber) + } + + private fun loadHooks(hookClassName: String): List>> { + return try { + // Custom hook classes outside the agent jar can not be found by bootstrap + // class loader, so use the system class loader as that will be the main application + // class loader and can access jars on the classpath. + // Also, don't initialize the hook class to defer further class loading. + val hookClass = Class.forName(hookClassName, false, ClassLoader.getSystemClassLoader()) + loadHooks(hookClass).also { + println("INFO: Loaded ${it.size} hooks from $hookClassName") + }.map { + it to hookClass + } + } catch (e: ClassNotFoundException) { + println("WARN: Failed to load hooks from $hookClassName: ${e.printStackTrace()}") + emptyList() + } + } + + private fun loadHooks(hookClass: Class<*>): List { + val hooks = mutableListOf() + for (method in hookClass.methods) { + method.getAnnotation(MethodHook::class.java)?.let { + hooks.addAll(verifyAndGetHooks(method, it)) + } + method.getAnnotation(MethodHooks::class.java)?.let { + it.value.forEach { hookAnnotation -> + hooks.addAll(verifyAndGetHooks(method, hookAnnotation)) + } + } + } + return hooks + } + + private fun verifyAndGetHooks(hookMethod: Method, hookData: MethodHook): List { + return lookupClassesToHook(hookData.targetClassName) + .map { className -> + Hook.createAndVerifyHook(hookMethod, hookData, className) + } + } + + private fun lookupClassesToHook(annotationTargetClassName: String): List { + // Allowing arbitrary exterior whitespace in the target class name allows for an easy workaround + // for mangled hooks due to shading applied to hooks. + val targetClassName = annotationTargetClassName.trim() + val targetClassInfo = scanResult.getClassInfo(targetClassName) ?: return listOf(targetClassName) + val additionalTargetClasses = when { + targetClassInfo.isInterface -> scanResult.getClassesImplementing(targetClassName) + targetClassInfo.isAbstract -> scanResult.getSubclasses(targetClassName) + else -> emptyList() + } + return listOf(targetClassName) + additionalTargetClasses.map { it.name } + } + } + } +} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java index 1da9f426..5af6d5c3 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java @@ -84,10 +84,6 @@ final public class TraceCmpHooks { @MethodHook( type = HookType.AFTER, targetClassName = "java.lang.CharSequence", targetMethod = "equals") @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.Number", targetMethod = "equals") - @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.Byte", targetMethod = "equals") - @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.Integer", targetMethod = "equals") - @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.Short", targetMethod = "equals") - @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.Long", targetMethod = "equals") public static void genericEquals( MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) { @@ -277,21 +273,8 @@ final public class TraceCmpHooks { private static final int MAX_NUM_KEYS_TO_ENUMERATE = 100; @SuppressWarnings({"rawtypes", "unchecked"}) - @MethodHook(type = HookType.AFTER, targetClassName = "com.google.common.collect.ImmutableMap", - targetMethod = "get") - @MethodHook( - type = HookType.AFTER, targetClassName = "java.util.AbstractMap", targetMethod = "get") - @MethodHook(type = HookType.AFTER, targetClassName = "java.util.EnumMap", targetMethod = "get") - @MethodHook(type = HookType.AFTER, targetClassName = "java.util.HashMap", targetMethod = "get") - @MethodHook( - type = HookType.AFTER, targetClassName = "java.util.LinkedHashMap", targetMethod = "get") @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Map", targetMethod = "get") - @MethodHook(type = HookType.AFTER, targetClassName = "java.util.SortedMap", targetMethod = "get") - @MethodHook(type = HookType.AFTER, targetClassName = "java.util.TreeMap", targetMethod = "get") - @MethodHook(type = HookType.AFTER, targetClassName = "java.util.concurrent.ConcurrentMap", - targetMethod = "get") - public static void - mapGet( + public static void mapGet( MethodHandle method, Object thisObject, Object[] arguments, int hookId, Object returnValue) { if (returnValue != null) return; diff --git a/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt b/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt index daf4409e..78d97230 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt @@ -33,9 +33,9 @@ private val BASE_EXCLUDED_CLASS_NAME_GLOBS = listOf( "sun.**", ) -class ClassNameGlobber(private val includes: List, private val excludes: List) { +class ClassNameGlobber(includes: List, excludes: List) { // If no include globs are provided, start with all classes. - private val includeMatchers = (if (includes.isEmpty()) BASE_INCLUDED_CLASS_NAME_GLOBS else includes) + private val includeMatchers = includes.ifEmpty { BASE_INCLUDED_CLASS_NAME_GLOBS } .map(::SimpleGlobMatcher) // If no include globs are provided, additionally exclude stdlib classes as well as our own classes. @@ -45,9 +45,6 @@ class ClassNameGlobber(private val includes: List, private val excludes: fun includes(className: String): Boolean { return includeMatchers.any { it.matches(className) } && excludeMatchers.none { it.matches(className) } } - - fun withAdditionalIncludes(includes: List): ClassNameGlobber = - ClassNameGlobber(this.includes + includes, excludes) } class SimpleGlobMatcher(val glob: String) { diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt index 53efd200..e47dd30b 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt @@ -18,7 +18,8 @@ import org.junit.Test import java.io.File private fun applyAfterHooks(bytecode: ByteArray): ByteArray { - return HookInstrumentor(loadHooks(AfterHooks::class.java), false).instrument(bytecode) + val hooks = Hooks.loadHooks(setOf(AfterHooks::class.java.name)).first().hooks + return HookInstrumentor(hooks, false).instrument(bytecode) } private fun getOriginalAfterHooksTargetInstance(): AfterHooksTargetContract { diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt index 31e9733c..ced5896c 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt @@ -18,7 +18,8 @@ import org.junit.Test import java.io.File private fun applyBeforeHooks(bytecode: ByteArray): ByteArray { - return HookInstrumentor(loadHooks(BeforeHooks::class.java), false).instrument(bytecode) + val hooks = Hooks.loadHooks(setOf(BeforeHooks::class.java.name)).first().hooks + return HookInstrumentor(hooks, false).instrument(bytecode) } private fun getOriginalBeforeHooksTargetInstance(): BeforeHooksTargetContract { diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt index 7e7c31c9..b03fdbad 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt @@ -22,7 +22,8 @@ import kotlin.test.assertFailsWith class HookValidationTest { @Test fun testValidHooks() { - assertEquals(6, loadHooks(ValidHookMocks::class.java).size) + val hooks = Hooks.loadHooks(setOf(ValidHookMocks::class.java.name)).first().hooks + assertEquals(6, hooks.size) } @Test @@ -30,7 +31,8 @@ class HookValidationTest { for (method in InvalidHookMocks::class.java.methods) { if (method.isAnnotationPresent(MethodHook::class.java)) { assertFailsWith("Expected ${method.name} to be an invalid hook") { - Hook.verifyAndGetHook(method, method.declaredAnnotations.first() as MethodHook) + val methodHook = method.declaredAnnotations.first() as MethodHook + Hook.createAndVerifyHook(method, methodHook, methodHook.targetClassName) } } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java index f33cdc43..7e31b77b 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java @@ -108,6 +108,13 @@ public class ReplaceHooks { return true; } + @MethodHook(type = HookType.REPLACE, targetClassName = "java.util.Set", targetMethod = "contains", + targetMethodDescriptor = "(Ljava/lang/Object;)Z") + public static boolean + patchSetGet(MethodHandle method, Object thisObject, Object[] arguments, int hookId) { + return true; + } + @MethodHook(type = HookType.REPLACE, targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksInit", targetMethod = "", targetMethodDescriptor = "()V") diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt index 76fb53e5..5759246e 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt @@ -18,7 +18,8 @@ import org.junit.Test import java.io.File private fun applyReplaceHooks(bytecode: ByteArray): ByteArray { - return HookInstrumentor(loadHooks(ReplaceHooks::class.java), false).instrument(bytecode) + val hooks = Hooks.loadHooks(setOf(ReplaceHooks::class.java.name)).first().hooks + return HookInstrumentor(hooks, false).instrument(bytecode) } private fun getOriginalReplaceHooksTargetInstance(): ReplaceHooksTargetContract { diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java index 19e89ff1..fadbdf80 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java @@ -15,9 +15,9 @@ package com.code_intelligence.jazzer.instrumentor; import java.security.SecureRandom; -import java.util.AbstractList; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; // selfCheck() only passes with the hooks in ReplaceHooks.java applied. @@ -56,10 +56,13 @@ public class ReplaceHooksTarget implements ReplaceHooksTargetContract { shouldCallPass(); } - AbstractList boolList = new ArrayList<>(); + ArrayList boolList = new ArrayList<>(); boolList.add(false); results.put("arrayListGet", boolList.get(0)); + HashSet boolSet = new HashSet<>(); + results.put("stringSetGet", boolSet.contains(Boolean.TRUE)); + results.put("shouldInitialize", new ReplaceHooksInit().initialized); results.put("shouldInitializeWithParams", new ReplaceHooksInit(false, "foo").initialized); diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt index 1d2dd772..56e12f03 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt @@ -40,48 +40,12 @@ object NamingContextLookup { targetMethod = "lookup", targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;", ), - MethodHook( - type = HookType.REPLACE, - targetClassName = "javax.naming.InitialContext", - targetMethod = "lookup", - targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;", - ), - MethodHook( - type = HookType.REPLACE, - targetClassName = "javax.naming.InitialDirContext", - targetMethod = "lookup", - targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;", - ), - MethodHook( - type = HookType.REPLACE, - targetClassName = "javax.naming.InitialLdapContext", - targetMethod = "lookup", - targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;", - ), MethodHook( type = HookType.REPLACE, targetClassName = "javax.naming.Context", targetMethod = "lookupLink", targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;", ), - MethodHook( - type = HookType.REPLACE, - targetClassName = "javax.naming.InitialContext", - targetMethod = "lookupLink", - targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;", - ), - MethodHook( - type = HookType.REPLACE, - targetClassName = "javax.naming.InitialDirContext", - targetMethod = "lookupLink", - targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;", - ), - MethodHook( - type = HookType.REPLACE, - targetClassName = "javax.naming.InitialLdapContext", - targetMethod = "lookupLink", - targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;", - ), ) @JvmStatic fun lookupHook(method: MethodHandle?, thisObject: Any?, args: Array, hookId: Int): Any { -- cgit v1.2.3 From 7f6060349ea986c6cfd8f0525b5b2cc37553790b Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Wed, 23 Feb 2022 16:14:35 +0100 Subject: Include JVM crash reports in workflow artifacts --- .github/workflows/run-all-tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-all-tests.yml index 78518a91..970117b0 100644 --- a/.github/workflows/run-all-tests.yml +++ b/.github/workflows/run-all-tests.yml @@ -69,4 +69,6 @@ jobs: with: name: testlogs-${{ matrix.arch }}-${{ matrix.jdk }} # https://github.com/actions/upload-artifact/issues/92#issuecomment-711107236 - path: bazel-testlogs*/**/test.log + path: | + bazel-testlogs*/**/test.log + bazel-out*/**/hs_err_pid*.log -- cgit v1.2.3 From 79b6e03d6c0acd23d3a2281c9f8449ce36b7d1bb Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Mon, 28 Feb 2022 09:54:59 +0100 Subject: Reduce JpegImageParserFuzzer fork count to 3 --- examples/BUILD.bazel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index fe78196d..085498b3 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -145,7 +145,7 @@ java_fuzz_target_test( "JAVA_OPTS": "-Dfoo=not_foo -Djava_opts=1", }, fuzzer_args = [ - "-fork=5", + "-fork=3", "--additional_jvm_args=-Dbaz=baz", ] + select({ # \\\\ becomes \\ when evaluated as a Starlark string literal, then \ in -- cgit v1.2.3 From 5393791d2ac47cf6d63e6fc244ffdae491e17212 Mon Sep 17 00:00:00 2001 From: Khaled Yakdan Date: Sat, 26 Feb 2022 15:29:32 +0100 Subject: Minor refactoring and cleanup --- .../main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java | 1 - .../java/com/code_intelligence/jazzer/runtime/UnsafeProvider.java | 4 ++-- driver/coverage_tracker.cpp | 1 - driver/coverage_tracker.h | 2 -- driver/sanitizer_hooks_with_pc.cpp | 2 +- 5 files changed, 3 insertions(+), 7 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java index cdd27ef3..b2238799 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java @@ -14,7 +14,6 @@ package com.code_intelligence.jazzer.runtime; -import java.nio.ByteBuffer; import java.util.Collections; import java.util.HashSet; import java.util.Set; diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/UnsafeProvider.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/UnsafeProvider.java index 62be96e8..81f2a208 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/UnsafeProvider.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/UnsafeProvider.java @@ -18,10 +18,10 @@ import java.lang.reflect.Field; import sun.misc.Unsafe; public final class UnsafeProvider { - private static final Unsafe unsafe = getUnsafeInternal(); + private static final Unsafe UNSAFE = getUnsafeInternal(); public static Unsafe getUnsafe() { - return unsafe; + return UNSAFE; } private static Unsafe getUnsafeInternal() { diff --git a/driver/coverage_tracker.cpp b/driver/coverage_tracker.cpp index d2c881aa..edfde5c2 100644 --- a/driver/coverage_tracker.cpp +++ b/driver/coverage_tracker.cpp @@ -45,7 +45,6 @@ void AssertNoException(JNIEnv &env) { namespace jazzer { uint8_t *CoverageTracker::counters_ = nullptr; -uint32_t *CoverageTracker::fake_instructions_ = nullptr; PCTableEntry *CoverageTracker::pc_entries_ = nullptr; void CoverageTracker::Initialize(JNIEnv &env, jlong counters) { diff --git a/driver/coverage_tracker.h b/driver/coverage_tracker.h index 55bb5b05..d5b80a02 100644 --- a/driver/coverage_tracker.h +++ b/driver/coverage_tracker.h @@ -32,8 +32,6 @@ struct __attribute__((packed)) PCTableEntry { class CoverageTracker { private: static uint8_t *counters_; - - static uint32_t *fake_instructions_; static PCTableEntry *pc_entries_; public: diff --git a/driver/sanitizer_hooks_with_pc.cpp b/driver/sanitizer_hooks_with_pc.cpp index b69e2dd9..b50b2d83 100644 --- a/driver/sanitizer_hooks_with_pc.cpp +++ b/driver/sanitizer_hooks_with_pc.cpp @@ -146,7 +146,7 @@ void set_trampoline_offset() { // Computes the additive shift that needs to be applied to the caller PC by // caller_pc_to_fake_pc to make caller PC and resulting fake return address -// in their lowest 9 bite. This offset is constant for each binary, but may vary +// in their lowest 9 bits. This offset is constant for each binary, but may vary // based on code generation specifics. By calibrating the trampoline, the fuzzer // behavior is fully determined by the seed. __attribute__((constructor)) void CalibrateTrampoline() { -- cgit v1.2.3 From 23b08b6b64155621386967e089021fa75983f59c Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 21 Feb 2022 09:58:40 +0100 Subject: Add benchmarks for fuzzer callbacks The parts of the benchmark that require a JDK supporting Project Panama are commented out as there is no good way to enable them only if the current JDK supports them. --- .../java/com/code_intelligence/jazzer/BUILD.bazel | 6 + .../jazzer/instrumentor/BUILD.bazel | 20 +- .../jmh/java/com/code_intelligence/jazzer/jmh.bzl | 24 +++ .../code_intelligence/jazzer/runtime/BUILD.bazel | 50 +++++ .../jazzer/runtime/FuzzerCallbacks.java | 29 +++ .../jazzer/runtime/FuzzerCallbacksBenchmark.java | 219 +++++++++++++++++++++ .../runtime/FuzzerCallbacksOptimizedCritical.java | 46 +++++ .../FuzzerCallbacksOptimizedNonCritical.java | 46 +++++ .../jazzer/runtime/FuzzerCallbacksPanama.java | 59 ++++++ .../jazzer/runtime/FuzzerCallbacksWithPc.java | 31 +++ .../code_intelligence/jazzer/runtime/BUILD.bazel | 12 ++ .../jazzer/runtime/fuzzer_callbacks.cpp | 216 ++++++++++++++++++++ driver/BUILD.bazel | 3 + 13 files changed, 744 insertions(+), 17 deletions(-) create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/BUILD.bazel create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/jmh.bzl create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/runtime/BUILD.bazel create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacks.java create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksBenchmark.java create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedCritical.java create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedNonCritical.java create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksPanama.java create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksWithPc.java create mode 100644 agent/src/jmh/native/com/code_intelligence/jazzer/runtime/BUILD.bazel create mode 100644 agent/src/jmh/native/com/code_intelligence/jazzer/runtime/fuzzer_callbacks.cpp diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/BUILD.bazel b/agent/src/jmh/java/com/code_intelligence/jazzer/BUILD.bazel new file mode 100644 index 00000000..cf6acfbc --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/BUILD.bazel @@ -0,0 +1,6 @@ +java_plugin( + name = "JmhGeneratorAnnotationProcessor", + processor_class = "org.openjdk.jmh.generators.BenchmarkProcessor", + visibility = ["//agent/src/jmh/java:__subpackages__"], + deps = ["@maven//:org_openjdk_jmh_jmh_generator_annprocess"], +) diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel index 8862c435..f38cbc6a 100644 --- a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -1,4 +1,5 @@ load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") +load("//agent/src/jmh/java/com/code_intelligence/jazzer:jmh.bzl", "JMH_TEST_ARGS") java_binary( name = "CoverageInstrumentationBenchmark", @@ -10,16 +11,7 @@ java_binary( java_test( name = "CoverageInstrumentationBenchmarkTest", - args = [ - # Fail fast on any exceptions produced by benchmarks. - "-foe true", - "-wf 1", - "-f 1", - "-wi 1", - "-i 1", - "-r 1s", - "-w 1s", - ], + args = JMH_TEST_ARGS, jvm_flags = [ "-XX:CompileCommand=print,*CoverageMap.recordCoverage", ], @@ -34,7 +26,7 @@ java_test( java_library( name = "coverage_instrumentation_benchmark", srcs = ["CoverageInstrumentationBenchmark.java"], - plugins = [":JmhGeneratorAnnotationProcessor"], + plugins = ["//agent/src/jmh/java/com/code_intelligence/jazzer:JmhGeneratorAnnotationProcessor"], runtime_deps = [ "@maven//:com_mikesamuel_json_sanitizer", ], @@ -72,9 +64,3 @@ kt_jvm_library( "@org_ow2_asm_asm//jar", ], ) - -java_plugin( - name = "JmhGeneratorAnnotationProcessor", - processor_class = "org.openjdk.jmh.generators.BenchmarkProcessor", - deps = ["@maven//:org_openjdk_jmh_jmh_generator_annprocess"], -) diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/jmh.bzl b/agent/src/jmh/java/com/code_intelligence/jazzer/jmh.bzl new file mode 100644 index 00000000..5391a46b --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/jmh.bzl @@ -0,0 +1,24 @@ +# Copyright 2022 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. + +JMH_TEST_ARGS = [ + # Fail fast on any exceptions produced by benchmarks. + "-foe true", + "-wf 0", + "-f 1", + "-wi 0", + "-i 1", + "-r 1s", + "-w 1s", +] diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/BUILD.bazel b/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/BUILD.bazel new file mode 100644 index 00000000..96fd8e1f --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/BUILD.bazel @@ -0,0 +1,50 @@ +load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library") +load("//agent/src/jmh/java/com/code_intelligence/jazzer:jmh.bzl", "JMH_TEST_ARGS") + +java_binary( + name = "FuzzerCallbacksBenchmark", + main_class = "org.openjdk.jmh.Main", + runtime_deps = [ + ":fuzzer_callbacks_benchmark", + ], +) + +java_test( + name = "FuzzerCallbacksBenchmarkTest", + args = JMH_TEST_ARGS, + main_class = "org.openjdk.jmh.Main", + # Directly invoke JMH's main without using a testrunner. + use_testrunner = False, + runtime_deps = [ + ":fuzzer_callbacks_benchmark", + ], +) + +java_library( + name = "fuzzer_callbacks_benchmark", + srcs = ["FuzzerCallbacksBenchmark.java"], + plugins = ["//agent/src/jmh/java/com/code_intelligence/jazzer:JmhGeneratorAnnotationProcessor"], + deps = [ + ":fuzzer_callbacks", + "@maven//:org_openjdk_jmh_jmh_core", + ], +) + +java_jni_library( + name = "fuzzer_callbacks", + srcs = [ + "FuzzerCallbacks.java", + "FuzzerCallbacksOptimizedCritical.java", + "FuzzerCallbacksOptimizedNonCritical.java", + # Uncomment to benchmark Project Panama-backed implementation (requires JDK 16+). + # "FuzzerCallbacksPanama.java", + "FuzzerCallbacksWithPc.java", + ], + javacopts = [ + # Uncomment to benchmark Project Panama-backed implementation (requires JDK 16+). + # "--add-modules", + # "jdk.incubator.foreign", + ], + native_libs = ["//agent/src/jmh/native/com/code_intelligence/jazzer/runtime:fuzzer_callbacks"], + visibility = ["//agent/src/jmh/native/com/code_intelligence/jazzer/runtime:__pkg__"], +) diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacks.java b/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacks.java new file mode 100644 index 00000000..6e8343ce --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacks.java @@ -0,0 +1,29 @@ +// Copyright 2022 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.runtime; + +import com.github.fmeum.rules_jni.RulesJni; + +public final class FuzzerCallbacks { + static { + RulesJni.loadLibrary("fuzzer_callbacks", FuzzerCallbacks.class); + } + + static native void traceCmpInt(int arg1, int arg2, int pc); + static native void traceSwitch(long val, long[] cases, int pc); + + static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc); + static native void traceStrstr(String s1, String s2, int pc); +} diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksBenchmark.java b/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksBenchmark.java new file mode 100644 index 00000000..b55a9936 --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksBenchmark.java @@ -0,0 +1,219 @@ +// Copyright 2022 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.runtime; + +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@Fork(value = 3) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +public class FuzzerCallbacksBenchmark { + @State(Scope.Benchmark) + public static class TraceCmpIntState { + int arg1 = 0xCAFECAFE; + int arg2 = 0xFEEDFEED; + int pc = 0x12345678; + } + + @Benchmark + public void traceCmpInt(TraceCmpIntState state) { + FuzzerCallbacks.traceCmpInt(state.arg1, state.arg2, state.pc); + } + + @Benchmark + public void traceCmpIntWithPc(TraceCmpIntState state) { + FuzzerCallbacksWithPc.traceCmpInt(state.arg1, state.arg2, state.pc); + } + + @Benchmark + @Fork(jvmArgsAppend = {"-XX:+CriticalJNINatives"}) + public void traceCmpIntOptimizedCritical(TraceCmpIntState state) { + FuzzerCallbacksOptimizedCritical.traceCmpInt(state.arg1, state.arg2, state.pc); + } + + // Uncomment to benchmark Project Panama-backed implementation (requires JDK 16+). + // @Benchmark + // @Fork(jvmArgsAppend = {"--enable-native-access=ALL-UNNAMED", "--add-modules", + // "jdk.incubator.foreign"}) + // public void + // traceCmpIntPanama(TraceCmpIntState state) throws Throwable { + // FuzzerCallbacksPanama.traceCmpInt(state.arg1, state.arg2, state.pc); + // } + + @State(Scope.Benchmark) + public static class TraceSwitchState { + @Param({"5", "10"}) int numCases; + + long val; + long[] cases; + int pc = 0x12345678; + + @Setup + public void setup() { + cases = new long[2 + numCases]; + Random random = ThreadLocalRandom.current(); + Arrays.setAll(cases, i -> { + switch (i) { + case 0: + return numCases; + case 1: + return 32; + default: + return random.nextInt(); + } + }); + Arrays.sort(cases, 2, cases.length); + val = random.nextInt(); + } + } + + @Benchmark + public void traceSwitch(TraceSwitchState state) { + FuzzerCallbacks.traceSwitch(state.val, state.cases, state.pc); + } + + @Benchmark + public void traceSwitchWithPc(TraceSwitchState state) { + FuzzerCallbacksWithPc.traceSwitch(state.val, state.cases, state.pc); + } + + @Benchmark + @Fork(jvmArgsAppend = {"-XX:+CriticalJNINatives"}) + public void traceSwitchOptimizedCritical(TraceSwitchState state) { + FuzzerCallbacksOptimizedCritical.traceSwitch(state.val, state.cases, state.pc); + } + + @Benchmark + public void traceSwitchOptimizedNonCritical(TraceSwitchState state) { + FuzzerCallbacksOptimizedNonCritical.traceSwitch(state.val, state.cases, state.pc); + } + + // Uncomment to benchmark Project Panama-backed implementation (requires JDK 16+). + // @Benchmark + // @Fork(jvmArgsAppend = {"--enable-native-access=ALL-UNNAMED", "--add-modules", + // "jdk.incubator.foreign"}) + // public void + // traceCmpSwitchPanama(TraceSwitchState state) throws Throwable { + // FuzzerCallbacksPanama.traceCmpSwitch(state.val, state.cases, state.pc); + // } + + @State(Scope.Benchmark) + public static class TraceMemcmpState { + @Param({"10", "100", "1000"}) int length; + + byte[] array1; + byte[] array2; + int pc = 0x12345678; + + @Setup + public void setup() { + array1 = new byte[length]; + array2 = new byte[length]; + + Random random = ThreadLocalRandom.current(); + random.nextBytes(array1); + random.nextBytes(array2); + // Make the arrays agree unil the midpoint to benchmark the "average" + // case of an interesting memcmp. + System.arraycopy(array1, 0, array2, 0, length / 2); + } + } + + @Benchmark + public void traceMemcmp(TraceMemcmpState state) { + FuzzerCallbacks.traceMemcmp(state.array1, state.array2, 1, state.pc); + } + + @Benchmark + @Fork(jvmArgsAppend = {"-XX:+CriticalJNINatives"}) + public void traceMemcmpOptimizedCritical(TraceMemcmpState state) { + FuzzerCallbacksOptimizedCritical.traceMemcmp(state.array1, state.array2, 1, state.pc); + } + + @Benchmark + public void traceMemcmpOptimizedNonCritical(TraceMemcmpState state) { + FuzzerCallbacksOptimizedNonCritical.traceMemcmp(state.array1, state.array2, 1, state.pc); + } + + @State(Scope.Benchmark) + public static class TraceStrstrState { + @Param({"10", "100", "1000"}) int length; + @Param({"true", "false"}) boolean asciiOnly; + + String haystack; + String needle; + int pc = 0x12345678; + + @Setup + public void setup() { + haystack = randomString(length, asciiOnly); + needle = randomString(length, asciiOnly); + } + + private String randomString(int length, boolean asciiOnly) { + String asciiString = + ThreadLocalRandom.current() + .ints('a', 'z' + 1) + .limit(length) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString(); + if (asciiOnly) { + return asciiString; + } + // Force String to be non-Latin-1 to preclude compact string optimization. + return "\uFFFD" + asciiString.substring(1); + } + } + + @Benchmark + public void traceStrstr(TraceStrstrState state) { + FuzzerCallbacks.traceStrstr(state.haystack, state.needle, state.pc); + } + + @Benchmark + public void traceStrstrOptimizedNonCritical(TraceStrstrState state) { + FuzzerCallbacksOptimizedNonCritical.traceStrstr(state.haystack, state.needle, state.pc); + } + + @Benchmark + @Fork(jvmArgsAppend = {"-XX:+CriticalJNINatives"}) + public void traceStrstrOptimizedJavaCritical(TraceStrstrState state) + throws UnsupportedEncodingException { + FuzzerCallbacksOptimizedCritical.traceStrstrJava(state.haystack, state.needle, state.pc); + } + + @Benchmark + public void traceStrstrOptimizedJavaNonCritical(TraceStrstrState state) + throws UnsupportedEncodingException { + FuzzerCallbacksOptimizedNonCritical.traceStrstrJava(state.haystack, state.needle, state.pc); + } +} diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedCritical.java b/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedCritical.java new file mode 100644 index 00000000..1c09e9ad --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedCritical.java @@ -0,0 +1,46 @@ +// Copyright 2022 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.runtime; + +import com.github.fmeum.rules_jni.RulesJni; +import java.io.UnsupportedEncodingException; + +/** + * Optimized implementations of the libFuzzer callbacks that do rely on the deprecated + * CriticalJNINatives feature. Methods with `Java` in their name implement some parts in Java. + */ +public final class FuzzerCallbacksOptimizedCritical { + static { + RulesJni.loadLibrary("fuzzer_callbacks", FuzzerCallbacksOptimizedCritical.class); + } + + static native void traceCmpInt(int arg1, int arg2, int pc); + + static native void traceSwitch(long val, long[] cases, int pc); + + static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc); + + static void traceStrstrJava(String haystack, String needle, int pc) + throws UnsupportedEncodingException { + // Note that we are not encoding as modified UTF-8 here: The FuzzedDataProvider transparently + // converts CESU8 into modified UTF-8 by coding null bytes on two bytes. Since the fuzzer is + // more likely to insert literal null bytes, having both the fuzzer input and the reported + // string comparisons be CESU8 should perform even better than the current implementation using + // modified UTF-8. + traceStrstrInternal(needle.substring(0, Math.min(needle.length(), 64)).getBytes("CESU8"), pc); + } + + private static native void traceStrstrInternal(byte[] needle, int pc); +} diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedNonCritical.java b/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedNonCritical.java new file mode 100644 index 00000000..25fad3bf --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksOptimizedNonCritical.java @@ -0,0 +1,46 @@ +// Copyright 2022 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.runtime; + +import com.github.fmeum.rules_jni.RulesJni; +import java.io.UnsupportedEncodingException; + +/** + * Optimized implementations of the libFuzzer callbacks that do not rely on the deprecated + * CriticalJNINatives feature. Methods with `Java` in their name implement some parts in Java. + */ +public final class FuzzerCallbacksOptimizedNonCritical { + static { + RulesJni.loadLibrary("fuzzer_callbacks", FuzzerCallbacksOptimizedNonCritical.class); + } + + static native void traceSwitch(long val, long[] cases, int pc); + + static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc); + + static native void traceStrstr(String s1, String s2, int pc); + + static void traceStrstrJava(String haystack, String needle, int pc) + throws UnsupportedEncodingException { + // Note that we are not encoding as modified UTF-8 here: The FuzzedDataProvider transparently + // converts CESU8 into modified UTF-8 by coding null bytes on two bytes. Since the fuzzer is + // more likely to insert literal null bytes, having both the fuzzer input and the reported + // string comparisons be CESU8 should perform even better than the current implementation using + // modified UTF-8. + traceStrstrInternal(needle.substring(0, Math.min(needle.length(), 64)).getBytes("CESU8"), pc); + } + + private static native void traceStrstrInternal(byte[] needle, int pc); +} diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksPanama.java b/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksPanama.java new file mode 100644 index 00000000..ce3d6290 --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksPanama.java @@ -0,0 +1,59 @@ +// Copyright 2022 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.runtime; + +import com.github.fmeum.rules_jni.RulesJni; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import jdk.incubator.foreign.CLinker; +import jdk.incubator.foreign.FunctionDescriptor; +import jdk.incubator.foreign.MemoryAddress; +import jdk.incubator.foreign.MemoryLayout; +import jdk.incubator.foreign.MemorySegment; +import jdk.incubator.foreign.ResourceScope; +import jdk.incubator.foreign.SymbolLookup; + +/** + * Pure-Java implementation of the fuzzer callbacks backed by Project Panama (requires JDK 16+). + * To include the implementation in the benchmark on a supported JDK, uncomment the relevant lines + * in BUILD.bazel. + */ +public class FuzzerCallbacksPanama { + static { + RulesJni.loadLibrary("fuzzer_callbacks", FuzzerCallbacks.class); + } + + private static final MethodHandle traceCmp4 = CLinker.getInstance().downcallHandle( + SymbolLookup.loaderLookup().lookup("__sanitizer_cov_trace_cmp4").get(), + MethodType.methodType(void.class, int.class, int.class), + FunctionDescriptor.ofVoid(CLinker.C_INT, CLinker.C_INT)); + private static final MethodHandle traceSwitch = CLinker.getInstance().downcallHandle( + SymbolLookup.loaderLookup().lookup("__sanitizer_cov_trace_switch").get(), + MethodType.methodType(void.class, long.class, MemoryAddress.class), + FunctionDescriptor.ofVoid(CLinker.C_LONG, CLinker.C_POINTER)); + + static void traceCmpInt(int arg1, int arg2, int pc) throws Throwable { + traceCmp4.invokeExact(arg1, arg2); + } + + static void traceCmpSwitch(long val, long[] cases, int pc) throws Throwable { + try (ResourceScope scope = ResourceScope.newConfinedScope()) { + MemorySegment nativeCopy = MemorySegment.allocateNative( + MemoryLayout.sequenceLayout(cases.length, CLinker.C_LONG), scope); + nativeCopy.copyFrom(MemorySegment.ofArray(cases)); + traceSwitch.invokeExact(val, nativeCopy.address()); + } + } +} diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksWithPc.java b/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksWithPc.java new file mode 100644 index 00000000..21f416cf --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/runtime/FuzzerCallbacksWithPc.java @@ -0,0 +1,31 @@ +// Copyright 2022 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.runtime; + +import com.github.fmeum.rules_jni.RulesJni; + +/** + * Unoptimized implementation of the libFuzzer callbacks that use the trampoline construction to + * inject fake PCs. + */ +public final class FuzzerCallbacksWithPc { + static { + RulesJni.loadLibrary("fuzzer_callbacks", FuzzerCallbacksWithPc.class); + } + + static native void traceCmpInt(int arg1, int arg2, int pc); + + static native void traceSwitch(long val, long[] cases, int pc); +} diff --git a/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/BUILD.bazel b/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/BUILD.bazel new file mode 100644 index 00000000..555d5c7d --- /dev/null +++ b/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/BUILD.bazel @@ -0,0 +1,12 @@ +load("@fmeum_rules_jni//jni:defs.bzl", "cc_jni_library") + +cc_jni_library( + name = "fuzzer_callbacks", + srcs = ["fuzzer_callbacks.cpp"], + visibility = ["//agent/src/jmh/java/com/code_intelligence/jazzer/runtime:__pkg__"], + deps = [ + "//agent/src/jmh/java/com/code_intelligence/jazzer/runtime:fuzzer_callbacks.hdrs", + "//driver:sanitizer_hooks_with_pc", + "@jazzer_libfuzzer//:libFuzzer", + ], +) diff --git a/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/fuzzer_callbacks.cpp b/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/fuzzer_callbacks.cpp new file mode 100644 index 00000000..689a53d6 --- /dev/null +++ b/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/fuzzer_callbacks.cpp @@ -0,0 +1,216 @@ +// Copyright 2022 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 + +#include +#include + +#include "com_code_intelligence_jazzer_runtime_FuzzerCallbacks.h" +#include "com_code_intelligence_jazzer_runtime_FuzzerCallbacksOptimizedCritical.h" +#include "com_code_intelligence_jazzer_runtime_FuzzerCallbacksOptimizedNonCritical.h" +#include "com_code_intelligence_jazzer_runtime_FuzzerCallbacksWithPc.h" +#include "driver/sanitizer_hooks_with_pc.h" + +extern "C" { +void __sanitizer_weak_hook_compare_bytes(void *caller_pc, const void *s1, + const void *s2, std::size_t n1, + std::size_t n2, int result); +void __sanitizer_weak_hook_strstr(void *caller_pc, const char *s1, + const char *s2, const char *result); +void __sanitizer_weak_hook_memmem(void *caller_pc, const void *b1, + std::size_t n1, const void *s2, + std::size_t n2, void *result); +void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2); +void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2); + +void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases); + +void __sanitizer_cov_trace_div4(uint32_t val); +void __sanitizer_cov_trace_div8(uint64_t val); + +void __sanitizer_cov_trace_gep(uintptr_t idx); + +// Not called but required to link against libFuzzer. +int LLVMFuzzerTestOneInput(const uint8_t *data, std::size_t size) { return 0; } +} + +inline __attribute__((always_inline)) void *idToPc(jint id) { + return reinterpret_cast(static_cast(id)); +} + +void Java_com_code_1intelligence_jazzer_runtime_FuzzerCallbacks_traceCmpInt( + JNIEnv *env, jclass cls, jint value1, jint value2, jint id) { + __sanitizer_cov_trace_cmp4(value1, value2); +} + +void Java_com_code_1intelligence_jazzer_runtime_FuzzerCallbacksWithPc_traceCmpInt( + JNIEnv *env, jclass cls, jint value1, jint value2, jint id) { + __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); +} + +void Java_com_code_1intelligence_jazzer_runtime_FuzzerCallbacksOptimizedCritical_traceCmpInt( + JNIEnv *env, jclass cls, jint value1, jint value2, jint id) { + __sanitizer_cov_trace_cmp4(value1, value2); +} + +extern "C" JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_FuzzerCallbacksOptimizedCritical_traceCmpInt( + jint value1, jint value2, jint id) { + __sanitizer_cov_trace_cmp4(value1, value2); +} + +void Java_com_code_1intelligence_jazzer_runtime_FuzzerCallbacks_traceSwitch( + JNIEnv *env, jclass cls, jlong switch_value, + jlongArray libfuzzer_case_values, jint id) { + jlong *case_values = + env->GetLongArrayElements(libfuzzer_case_values, nullptr); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + __sanitizer_cov_trace_switch(switch_value, + reinterpret_cast(case_values)); + env->ReleaseLongArrayElements(libfuzzer_case_values, case_values, JNI_ABORT); + if (env->ExceptionCheck()) env->ExceptionDescribe(); +} + +void Java_com_code_1intelligence_jazzer_runtime_FuzzerCallbacksOptimizedNonCritical_traceSwitch( + JNIEnv *env, jclass cls, jlong switch_value, + jlongArray libfuzzer_case_values, jint id) { + auto *case_values = static_cast( + env->GetPrimitiveArrayCritical(libfuzzer_case_values, nullptr)); + __sanitizer_cov_trace_switch(switch_value, + reinterpret_cast(case_values)); + env->ReleasePrimitiveArrayCritical(libfuzzer_case_values, case_values, + JNI_ABORT); +} + +void Java_com_code_1intelligence_jazzer_runtime_FuzzerCallbacksWithPc_traceSwitch( + JNIEnv *env, jclass cls, jlong switch_value, + jlongArray libfuzzer_case_values, jint id) { + jlong *case_values = + env->GetLongArrayElements(libfuzzer_case_values, nullptr); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + __sanitizer_cov_trace_switch_with_pc( + idToPc(id), switch_value, reinterpret_cast(case_values)); + env->ReleaseLongArrayElements(libfuzzer_case_values, case_values, JNI_ABORT); + if (env->ExceptionCheck()) env->ExceptionDescribe(); +} + +void Java_com_code_1intelligence_jazzer_runtime_FuzzerCallbacksOptimizedCritical_traceSwitch( + JNIEnv *env, jclass cls, jlong switch_value, + jlongArray libfuzzer_case_values, jint id) { + Java_com_code_1intelligence_jazzer_runtime_FuzzerCallbacksOptimizedNonCritical_traceSwitch( + env, cls, switch_value, libfuzzer_case_values, id); +} + +extern "C" JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_FuzzerCallbacksOptimizedCritical_traceSwitch( + jlong switch_value, jint case_values_length, jlong *case_values, jint id) { + __sanitizer_cov_trace_switch(switch_value, + reinterpret_cast(case_values)); +} + +void Java_com_code_1intelligence_jazzer_runtime_FuzzerCallbacks_traceMemcmp( + JNIEnv *env, jclass cls, jbyteArray b1, jbyteArray b2, jint result, + jint id) { + jbyte *b1_native = env->GetByteArrayElements(b1, nullptr); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + jbyte *b2_native = env->GetByteArrayElements(b2, nullptr); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + jint b1_length = env->GetArrayLength(b1); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + jint b2_length = env->GetArrayLength(b2); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + __sanitizer_weak_hook_compare_bytes(idToPc(id), b1_native, b2_native, + b1_length, b2_length, result); + env->ReleaseByteArrayElements(b1, b1_native, JNI_ABORT); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + env->ReleaseByteArrayElements(b2, b2_native, JNI_ABORT); + if (env->ExceptionCheck()) env->ExceptionDescribe(); +} + +void Java_com_code_1intelligence_jazzer_runtime_FuzzerCallbacksOptimizedNonCritical_traceMemcmp( + JNIEnv *env, jclass cls, jbyteArray b1, jbyteArray b2, jint result, + jint id) { + auto *b1_native = + static_cast(env->GetPrimitiveArrayCritical(b1, nullptr)); + auto *b2_native = + static_cast(env->GetPrimitiveArrayCritical(b2, nullptr)); + jint b1_length = env->GetArrayLength(b1); + jint b2_length = env->GetArrayLength(b2); + __sanitizer_weak_hook_compare_bytes(idToPc(id), b1_native, b2_native, + b1_length, b2_length, result); + env->ReleasePrimitiveArrayCritical(b1, b1_native, JNI_ABORT); + env->ReleasePrimitiveArrayCritical(b2, b2_native, JNI_ABORT); +} + +void Java_com_code_1intelligence_jazzer_runtime_FuzzerCallbacksOptimizedCritical_traceMemcmp( + JNIEnv *env, jclass cls, jbyteArray b1, jbyteArray b2, jint result, + jint id) { + Java_com_code_1intelligence_jazzer_runtime_FuzzerCallbacksOptimizedNonCritical_traceMemcmp( + env, cls, b1, b2, result, id); +} + +extern "C" JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_FuzzerCallbacksOptimizedCritical_traceMemcmp( + jint b1_length, jbyte *b1, jint b2_length, jbyte *b2, jint result, + jint id) { + __sanitizer_weak_hook_compare_bytes(idToPc(id), b1, b2, b1_length, b2_length, + result); +} + +void Java_com_code_1intelligence_jazzer_runtime_FuzzerCallbacks_traceStrstr( + JNIEnv *env, jclass cls, jstring s1, jstring s2, jint id) { + const char *s1_native = env->GetStringUTFChars(s1, nullptr); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + const char *s2_native = env->GetStringUTFChars(s2, nullptr); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + // libFuzzer currently ignores the result, which allows us to simply pass a + // valid but arbitrary pointer here instead of performing an actual strstr + // operation. + __sanitizer_weak_hook_strstr(idToPc(id), s1_native, s2_native, s1_native); + env->ReleaseStringUTFChars(s1, s1_native); + if (env->ExceptionCheck()) env->ExceptionDescribe(); + env->ReleaseStringUTFChars(s2, s2_native); + if (env->ExceptionCheck()) env->ExceptionDescribe(); +} + +void Java_com_code_1intelligence_jazzer_runtime_FuzzerCallbacksOptimizedNonCritical_traceStrstr( + JNIEnv *env, jclass cls, jstring s1, jstring s2, jint id) { + const char *s2_native = env->GetStringUTFChars(s2, nullptr); + __sanitizer_weak_hook_strstr(idToPc(id), nullptr, s2_native, s2_native); + env->ReleaseStringUTFChars(s2, s2_native); +} + +void Java_com_code_1intelligence_jazzer_runtime_FuzzerCallbacksOptimizedNonCritical_traceStrstrInternal( + JNIEnv *env, jclass cls, jbyteArray needle, jint id) { + auto *needle_native = + static_cast(env->GetPrimitiveArrayCritical(needle, nullptr)); + jint needle_length = env->GetArrayLength(needle); + __sanitizer_weak_hook_memmem(idToPc(id), nullptr, 0, needle_native, + needle_length, nullptr); + env->ReleasePrimitiveArrayCritical(needle, needle_native, JNI_ABORT); +} + +void Java_com_code_1intelligence_jazzer_runtime_FuzzerCallbacksOptimizedCritical_traceStrstrInternal( + JNIEnv *env, jclass cls, jbyteArray needle, jint id) { + Java_com_code_1intelligence_jazzer_runtime_FuzzerCallbacksOptimizedNonCritical_traceStrstrInternal( + env, cls, needle, id); +} + +extern "C" JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_FuzzerCallbacksOptimizedCritical_traceStrstrInternal( + jint needle_length, jbyte *needle, jint id) { + __sanitizer_weak_hook_memmem(idToPc(id), nullptr, 0, needle, needle_length, + nullptr); +} diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index 24abeb01..63398fb7 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -5,6 +5,9 @@ cc_library( srcs = ["sanitizer_hooks_with_pc.cpp"], hdrs = ["sanitizer_hooks_with_pc.h"], linkstatic = True, + visibility = [ + "//agent/src/jmh/native/com/code_intelligence/jazzer/runtime:__pkg__", + ], ) cc_test( -- cgit v1.2.3 From 633a94526af7bcd4dd7af7f2d71b78b4fef1d9b1 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 1 Mar 2022 15:09:58 +0100 Subject: Optimize compare hooks if not needed based on return value --- .../jazzer/runtime/TraceCmpHooks.java | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java index 5af6d5c3..c7d4947e 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java @@ -205,9 +205,9 @@ final public class TraceCmpHooks { replace( MethodHandle method, Object thisObject, Object[] arguments, int hookId, String returnValue) { String original = (String) thisObject; - String target = arguments[0].toString(); // Report only if the replacement was not successful. if (original.equals(returnValue)) { + String target = arguments[0].toString(); TraceDataFlowNativeCallbacks.traceStrstr(original, target, hookId); } } @@ -217,11 +217,11 @@ final public class TraceCmpHooks { public static void arraysEquals( MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) { + if (returnValue) + return; byte[] first = (byte[]) arguments[0]; byte[] second = (byte[]) arguments[1]; - if (!returnValue) { - TraceDataFlowNativeCallbacks.traceMemcmp(first, second, 1, hookId); - } + TraceDataFlowNativeCallbacks.traceMemcmp(first, second, 1, hookId); } @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", targetMethod = "equals", @@ -229,13 +229,13 @@ final public class TraceCmpHooks { public static void arraysEqualsRange( MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) { + if (returnValue) + return; byte[] first = Arrays.copyOfRange((byte[]) arguments[0], (int) arguments[1], (int) arguments[2]); byte[] second = Arrays.copyOfRange((byte[]) arguments[3], (int) arguments[4], (int) arguments[5]); - if (!returnValue) { - TraceDataFlowNativeCallbacks.traceMemcmp(first, second, 1, hookId); - } + TraceDataFlowNativeCallbacks.traceMemcmp(first, second, 1, hookId); } @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", targetMethod = "compare", @@ -245,11 +245,11 @@ final public class TraceCmpHooks { public static void arraysCompare( MethodHandle method, Object thisObject, Object[] arguments, int hookId, Integer returnValue) { + if (returnValue == 0) + return; byte[] first = (byte[]) arguments[0]; byte[] second = (byte[]) arguments[1]; - if (returnValue != 0) { - TraceDataFlowNativeCallbacks.traceMemcmp(first, second, returnValue, hookId); - } + TraceDataFlowNativeCallbacks.traceMemcmp(first, second, returnValue, hookId); } @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", targetMethod = "compare", @@ -259,13 +259,13 @@ final public class TraceCmpHooks { public static void arraysCompareRange( MethodHandle method, Object thisObject, Object[] arguments, int hookId, Integer returnValue) { + if (returnValue == 0) + return; byte[] first = Arrays.copyOfRange((byte[]) arguments[0], (int) arguments[1], (int) arguments[2]); byte[] second = Arrays.copyOfRange((byte[]) arguments[3], (int) arguments[4], (int) arguments[5]); - if (returnValue != 0) { - TraceDataFlowNativeCallbacks.traceMemcmp(first, second, returnValue, hookId); - } + TraceDataFlowNativeCallbacks.traceMemcmp(first, second, returnValue, hookId); } // The maximal number of elements of a non-TreeMap Map that will be sorted and searched for the -- cgit v1.2.3 From c6fcbfd070728e68b79caf17a672f52e7d40ae3e Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Wed, 2 Mar 2022 16:23:38 +0100 Subject: Extend description on how to autofuzz constructors --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1330cf1b..90d2baa5 100644 --- a/README.md +++ b/README.md @@ -260,17 +260,18 @@ engineered to minimize copying and generate both valid and invalid ASCII-only an The Autofuzz mode enables fuzzing arbitrary methods without having to manually create fuzz targets. Instead, Jazzer will attempt to generate suitable and varied inputs to a specified methods using only public API functions available on the classpath. -To use Autofuzz, specify the `--autofuzz` flag and provide a fully qualified method reference, e.g.: +To use Autofuzz, specify the `--autofuzz` flag and provide a fully [qualified method reference](https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13), e.g.: ``` --autofuzz=org.apache.commons.imaging.Imaging::getBufferedImage ``` -If there are multiple overloads and you want Jazzer to only fuzz one, you can optionally specify the signature of the method to fuzz: +To autofuzz a constructor the `ClassType::new` format can be used. +If there are multiple overloads, and you want Jazzer to only fuzz one, you can optionally specify the signature of the method to fuzz: ``` --autofuzz=org.apache.commons.imaging.Imaging::getBufferedImage(java.io.InputStream,java.util.Map) ``` The format of the signature agrees with that obtained from the part after the `#` of the link to the Javadocs for the particular method. -Under the hood, jazzer tries various ways of creating objects from the fuzzer input. For example, if a parameter is an +Under the hood, Jazzer tries various ways of creating objects from the fuzzer input. For example, if a parameter is an interface or an abstract class, it will look for all concrete implementing classes on the classpath. Jazzer can also create objects from classes that follow the [builder design pattern](https://www.baeldung.com/creational-design-patterns#builder) or have a default constructor and use setters to set the fields. -- cgit v1.2.3 From ad9450e78f0df1af67e193dadd658f5127b4e84c Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 24 Feb 2022 10:17:30 +0100 Subject: Make Jazzer API calls noops in reproducers Previously, these would produce NullPointerExceptions. --- .../main/java/com/code_intelligence/jazzer/api/Jazzer.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java index afc18287..75c1ae62 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java @@ -467,6 +467,9 @@ final public class Jazzer { * @param id a (probabilistically) unique identifier for this particular compare hint */ public static void guideTowardsEquality(String current, String target, int id) { + if (TRACE_STRCMP == null) { + return; + } try { TRACE_STRCMP.invokeExact(current, target, 1, id); } catch (Throwable e) { @@ -487,6 +490,9 @@ final public class Jazzer { * @param id a (probabilistically) unique identifier for this particular compare hint */ public static void guideTowardsEquality(byte[] current, byte[] target, int id) { + if (TRACE_MEMCMP == null) { + return; + } try { TRACE_MEMCMP.invokeExact(current, target, 1, id); } catch (Throwable e) { @@ -508,6 +514,9 @@ final public class Jazzer { * @param id a (probabilistically) unique identifier for this particular compare hint */ public static void guideTowardsContainment(String haystack, String needle, int id) { + if (TRACE_STRSTR == null) { + return; + } try { TRACE_STRSTR.invokeExact(haystack, needle, id); } catch (Throwable e) { @@ -531,6 +540,9 @@ final public class Jazzer { * @param id a (probabilistically) unique identifier for this particular state hint */ public static void exploreState(byte state, int id) { + if (TRACE_PC_INDIR == null) { + return; + } // We only use the lower 7 bits of state, which allows for 128 different state values tracked // per id. The particular amount of 7 bits of state is also used in libFuzzer's // TracePC::HandleCmp: -- cgit v1.2.3 From 1e81d0a12cf9a57f655e14b87ae6aab83ff99aed Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 1 Mar 2022 10:43:48 +0100 Subject: Fix Java reproducers if fuzzerTestOneInput declares checked exceptions --- driver/java_reproducer_templates.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/java_reproducer_templates.h b/driver/java_reproducer_templates.h index a6d8a4d7..452e6312 100644 --- a/driver/java_reproducer_templates.h +++ b/driver/java_reproducer_templates.h @@ -25,7 +25,7 @@ import java.lang.reflect.Method; public class Crash_$0 { static final String base64Bytes = String.join("", "$1"); - public static void main(String[] args) { + public static void main(String[] args) throws Throwable { ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true); try { Method fuzzerInitialize = $2.class.getMethod("fuzzerInitialize"); -- cgit v1.2.3 From 0dbda45a4f6d25ceaf5b168da5d96d5abf410725 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 1 Mar 2022 10:45:35 +0100 Subject: Verify the precise exception reproduced by a reproducer New and existing verifications performed by the FuzzTargetTestWrapper are now only done if JAZZER_CI is set. This keeps the tests that do double duty as examples clean when executed by users, but still maximizes their test coverage. Developers can specify --config=ci to benefit from these extended checks. --- .bazelrc | 1 + bazel/fuzz_target.bzl | 13 ++++-- .../jazzer/tools/FuzzTargetTestWrapper.java | 41 +++++++++++------- examples/BUILD.bazel | 34 ++++++++++----- .../java/com/example/JpegImageParserFuzzer.java | 22 ---------- sanitizers/src/test/java/com/example/BUILD.bazel | 2 + tests/BUILD.bazel | 30 +++++++++++++- .../src/test/java/com/example/ForkModeFuzzer.java | 48 ++++++++++++++++++++++ 8 files changed, 139 insertions(+), 52 deletions(-) create mode 100644 tests/src/test/java/com/example/ForkModeFuzzer.java diff --git a/.bazelrc b/.bazelrc index 6c60e6c2..ea127918 100644 --- a/.bazelrc +++ b/.bazelrc @@ -34,6 +34,7 @@ run:windows --noincompatible_strict_action_env build:toolchain --//third_party:toolchain # CI tests (not using the toolchain to test OSS-Fuzz & local compatibility) +test:ci --test_env=JAZZER_CI=1 build:ci --bes_results_url=https://app.buildbuddy.io/invocation/ build:ci --bes_backend=grpcs://cloud.buildbuddy.io build:ci --remote_cache=grpcs://cloud.buildbuddy.io diff --git a/bazel/fuzz_target.bzl b/bazel/fuzz_target.bzl index f9ebb7ae..78ee3dc9 100644 --- a/bazel/fuzz_target.bzl +++ b/bazel/fuzz_target.bzl @@ -28,7 +28,8 @@ def java_fuzz_target_test( env = None, verify_crash_input = True, verify_crash_reproducer = True, - execute_crash_reproducer = False, + # Default is that the reproducer does not throw any exception. + expected_finding = "none", **kwargs): target_name = name + "_target" deploy_manifest_lines = [] @@ -62,6 +63,8 @@ def java_fuzz_target_test( else: fail("Invalid sanitizer: " + sanitizer) + if type(expected_finding) == type(""): + expected_finding = [expected_finding] native.java_test( name = name, runtime_deps = [ @@ -69,6 +72,11 @@ def java_fuzz_target_test( "//agent:jazzer_api_deploy.jar", ":%s_deploy.jar" % target_name, ], + jvm_flags = [ + # Use the same memory settings for reproducers as those suggested by Jazzer when + # encountering an OutOfMemoryError. + "-Xmx1620m", + ], size = size or "enormous", timeout = timeout or "moderate", args = [ @@ -77,8 +85,7 @@ def java_fuzz_target_test( "$(rootpath :%s_deploy.jar)" % target_name, str(verify_crash_input), str(verify_crash_reproducer), - str(execute_crash_reproducer), - ] + additional_args + fuzzer_args, + ] + expected_finding + additional_args + fuzzer_args, data = [ ":%s_deploy.jar" % target_name, "//agent:jazzer_agent_deploy.jar", diff --git a/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java b/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java index c78464ea..44a58e17 100644 --- a/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java +++ b/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java @@ -35,6 +35,8 @@ import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; public class FuzzTargetTestWrapper { + private static final boolean JAZZER_CI = "1".equals(System.getenv("JAZZER_CI")); + public static void main(String[] args) { Runfiles runfiles; String driverActualPath; @@ -42,7 +44,7 @@ public class FuzzTargetTestWrapper { String jarActualPath; boolean verifyCrashInput; boolean verifyCrashReproducer; - boolean executeCrashReproducer; + String expectedFinding; try { runfiles = Runfiles.create(); driverActualPath = lookUpRunfile(runfiles, args[0]); @@ -50,7 +52,7 @@ public class FuzzTargetTestWrapper { jarActualPath = lookUpRunfile(runfiles, args[2]); verifyCrashInput = Boolean.parseBoolean(args[3]); verifyCrashReproducer = Boolean.parseBoolean(args[4]); - executeCrashReproducer = Boolean.parseBoolean(args[5]); + expectedFinding = args[5]; } catch (IOException | ArrayIndexOutOfBoundsException e) { e.printStackTrace(); System.exit(1); @@ -92,13 +94,13 @@ public class FuzzTargetTestWrapper { System.exit(4); } // Verify that libFuzzer dumped a crashing input. - if (verifyCrashInput + if (JAZZER_CI && verifyCrashInput && Arrays.stream(outputFiles).noneMatch(name -> name.startsWith("crash-"))) { System.out.printf("No crashing input found in %s%n", outputDir); System.exit(5); } // Verify that libFuzzer dumped a crash reproducer. - if (verifyCrashReproducer + if (JAZZER_CI && verifyCrashReproducer && Arrays.stream(outputFiles).noneMatch(name -> name.startsWith("Crash_"))) { System.out.printf("No crash reproducer found in %s%n", outputDir); System.exit(6); @@ -108,9 +110,10 @@ public class FuzzTargetTestWrapper { System.exit(2); } - if (executeCrashReproducer) { + if (JAZZER_CI && verifyCrashReproducer) { try { - verifyCrashReproducer(outputDir, driverActualPath, apiActualPath, jarActualPath); + verifyCrashReproducer( + outputDir, driverActualPath, apiActualPath, jarActualPath, expectedFinding); } catch (Exception e) { e.printStackTrace(); System.exit(6); @@ -151,8 +154,8 @@ public class FuzzTargetTestWrapper { } } - private static void verifyCrashReproducer(String outputDir, String driver, String api, String jar) - throws Exception { + private static void verifyCrashReproducer(String outputDir, String driver, String api, String jar, + String expectedFinding) throws Exception { File source = Files.list(Paths.get(outputDir)) .filter(f -> f.toFile().getName().endsWith(".java")) @@ -161,7 +164,7 @@ public class FuzzTargetTestWrapper { .orElseThrow( () -> new IllegalStateException("Could not find crash reproducer in " + outputDir)); String crashReproducer = compile(source, driver, api, jar); - execute(crashReproducer, outputDir); + execute(crashReproducer, outputDir, expectedFinding); } private static String compile(File source, String driver, String api, String jar) @@ -182,7 +185,7 @@ public class FuzzTargetTestWrapper { } } - private static void execute(String classFile, String outputDir) + private static void execute(String classFile, String outputDir, String expectedFinding) throws IOException, ReflectiveOperationException { try { System.out.printf("Execute crash reproducer %s%n", classFile); @@ -190,12 +193,22 @@ public class FuzzTargetTestWrapper { new URLClassLoader(new URL[] {new URL("file://" + outputDir + "/")}); Class crashReproducerClass = classLoader.loadClass(classFile); Method main = crashReproducerClass.getMethod("main", String[].class); + System.setProperty("jazzer.is_reproducer", "true"); main.invoke(null, new Object[] {new String[] {}}); - throw new IllegalStateException("Crash not reproduced by " + classFile); + if (!expectedFinding.equals("none")) { + throw new IllegalStateException("Crash not reproduced by " + classFile); + } } catch (InvocationTargetException e) { - // expect the invocation to fail with the crash - // other reflection exceptions indicate a real problem - System.out.printf("Reproduced exception \"%s\"%n", e.getCause().getMessage()); + // expect the invocation to fail with the prescribed finding + Throwable finding = e.getCause(); + if (expectedFinding.equals("none")) { + throw new IllegalStateException("Did not expect " + classFile + " to crash", finding); + } else if (finding.getClass().getName().equals(expectedFinding)) { + System.out.printf("Reproduced exception \"%s\"%n", finding.getMessage()); + } else { + throw new IllegalStateException( + classFile + " did not crash with " + expectedFinding, finding); + } } } } diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index 085498b3..8ba01042 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -5,7 +5,7 @@ load("//bazel:fuzz_target.bzl", "java_fuzz_target_test") java_fuzz_target_test( name = "Autofuzz", - execute_crash_reproducer = True, + expected_finding = "java.lang.ArrayIndexOutOfBoundsException", fuzzer_args = [ "--autofuzz=com.google.json.JsonSanitizer::sanitize", # Exit after the first finding for testing purposes. @@ -81,6 +81,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/ExampleValueProfileFuzzer.java", ], + expected_finding = "com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow", # Comment out the next line to keep the fuzzer running indefinitely. fuzzer_args = ["-use_value_profile=1"], target_class = "com.example.ExampleValueProfileFuzzer", @@ -91,6 +92,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/MazeFuzzer.java", ], + expected_finding = "com.example.MazeFuzzer$$TreasureFoundException", fuzzer_args = ["-use_value_profile=1"], target_class = "com.example.MazeFuzzer", ) @@ -100,6 +102,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/ExampleOutOfMemoryFuzzer.java", ], + expected_finding = "java.lang.OutOfMemoryError", fuzzer_args = ["--jvm_args=-Xmx512m"], target_class = "com.example.ExampleOutOfMemoryFuzzer", ) @@ -109,6 +112,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/ExampleStackOverflowFuzzer.java", ], + expected_finding = "java.lang.StackOverflowError", target_class = "com.example.ExampleStackOverflowFuzzer", # Crashes with a segfault before any stack trace printing is reached. target_compatible_with = SKIP_ON_MACOS, @@ -138,21 +142,14 @@ java_fuzz_target_test( java_fuzz_target_test( name = "JpegImageParserFuzzer", + size = "enormous", srcs = [ "src/main/java/com/example/JpegImageParserFuzzer.java", ], - env = { - "JAVA_OPTS": "-Dfoo=not_foo -Djava_opts=1", - }, + expected_finding = "java.lang.NegativeArraySizeException", fuzzer_args = [ "-fork=3", - "--additional_jvm_args=-Dbaz=baz", - ] + select({ - # \\\\ becomes \\ when evaluated as a Starlark string literal, then \ in - # java_fuzz_target_test. - "@platforms//os:windows": ["--jvm_args=-Dfoo=foo;-Dbar=b\\\\;ar"], - "//conditions:default": ["--jvm_args=-Dfoo=foo:-Dbar=b\\\\:ar"], - }), + ], target_class = "com.example.JpegImageParserFuzzer", # The exit codes of the forked libFuzzer processes are not picked up correctly. target_compatible_with = SKIP_ON_MACOS, @@ -166,6 +163,11 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/GifImageParserFuzzer.java", ], + expected_finding = select({ + "@platforms//os:macos": ["java.lang.IllegalArgumentException"], + "@platforms//os:windows": ["java.lang.ArrayIndexOutOfBoundsException"], + "//conditions:default": ["java.lang.OutOfMemoryError"], + }), target_class = "com.example.GifImageParserFuzzer", deps = [ "@maven//:org_apache_commons_commons_imaging", @@ -189,6 +191,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/JsonSanitizerCrashFuzzer.java", ], + expected_finding = "java.lang.IndexOutOfBoundsException", target_class = "com.example.JsonSanitizerCrashFuzzer", deps = [ "@maven//:com_mikesamuel_json_sanitizer", @@ -200,6 +203,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/JsonSanitizerDenylistFuzzer.java", ], + expected_finding = "java.lang.AssertionError", target_class = "com.example.JsonSanitizerDenylistFuzzer", deps = [ "@maven//:com_mikesamuel_json_sanitizer", @@ -240,6 +244,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/JsonSanitizerIdempotenceFuzzer.java", ], + expected_finding = "java.lang.AssertionError", target_class = "com.example.JsonSanitizerIdempotenceFuzzer", deps = [ "@maven//:com_mikesamuel_json_sanitizer", @@ -251,6 +256,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/JsonSanitizerValidJsonFuzzer.java", ], + expected_finding = "com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow", target_class = "com.example.JsonSanitizerValidJsonFuzzer", deps = [ "@maven//:com_google_code_gson_gson", @@ -263,6 +269,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/JacksonCborFuzzer.java", ], + expected_finding = "java.lang.NullPointerException", target_class = "com.example.JacksonCborFuzzer", deps = [ "@maven//:com_fasterxml_jackson_core_jackson_core", @@ -276,6 +283,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/FastJsonFuzzer.java", ], + expected_finding = "java.lang.NumberFormatException", target_class = "com.example.FastJsonFuzzer", deps = [ "@maven//:com_alibaba_fastjson", @@ -295,6 +303,10 @@ kt_jvm_library( java_fuzz_target_test( name = "KlaxonFuzzer", + expected_finding = select({ + "@platforms//os:windows": ["java.lang.ClassCastException"], + "//conditions:default": ["java.lang.IllegalStateException"], + }), fuzzer_args = [ "--keep_going=7", ], diff --git a/examples/src/main/java/com/example/JpegImageParserFuzzer.java b/examples/src/main/java/com/example/JpegImageParserFuzzer.java index d6ef64f8..ba3e7c81 100644 --- a/examples/src/main/java/com/example/JpegImageParserFuzzer.java +++ b/examples/src/main/java/com/example/JpegImageParserFuzzer.java @@ -22,28 +22,6 @@ import org.apache.commons.imaging.formats.jpeg.JpegImageParser; // Found https://issues.apache.org/jira/browse/IMAGING-275. public class JpegImageParserFuzzer { - public static void fuzzerInitialize() { - String foo = System.getProperty("foo"); - String bar = System.getProperty("bar"); - String baz = System.getProperty("baz"); - // Only used to verify that arguments are correctly passed down to child processes. - if (foo == null || bar == null || baz == null || !foo.equals("foo") - || !(bar.equals("b;ar") || bar.equals("b:ar")) || !baz.equals("baz")) { - // Exit the process with an exit code different from that for a finding. - System.err.println("ERROR: Did not correctly pass all jvm_args to child process."); - System.err.printf("foo: %s%nbar: %s%nbaz: %s%n", foo, bar, baz); - System.exit(3); - } - // Only used to verify that Jazzer honors the JAVA_OPTS env var. - String javaOpts = System.getProperty("java_opts"); - if (javaOpts == null || !javaOpts.equals("1")) { - // Exit the process with an exit code different from that for a finding. - System.err.println("ERROR: Did not honor JAVA_OPTS."); - System.err.printf("java_opts: %s%n", javaOpts); - System.exit(4); - } - } - public static void fuzzerTestOneInput(byte[] input) { try { new JpegImageParser().getBufferedImage(new ByteSourceArray(input), new HashMap<>()); diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index c38bebc1..5abece30 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -54,6 +54,7 @@ java_fuzz_target_test( "ldap/MockInitialContextFactory.java", "ldap/MockLdapContext.java", ], + expected_finding = "javax.naming.directory.InvalidSearchFilterException", target_class = "com.example.LdapSearchInjection", deps = [ "@maven//:com_unboundid_unboundid_ldapsdk", @@ -67,6 +68,7 @@ java_fuzz_target_test( "ldap/MockInitialContextFactory.java", "ldap/MockLdapContext.java", ], + expected_finding = "javax.naming.NamingException", target_class = "com.example.LdapDnInjection", deps = [ "@maven//:com_unboundid_unboundid_ldapsdk", diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index e3f6cb6b..15348bc7 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -1,3 +1,4 @@ +load("//bazel:compat.bzl", "SKIP_ON_MACOS") load("//bazel:fuzz_target.bzl", "java_fuzz_target_test") java_fuzz_target_test( @@ -6,7 +7,7 @@ java_fuzz_target_test( "src/test/java/com/example/LongStringFuzzer.java", ], data = ["src/test/java/com/example/LongStringFuzzerInput"], - execute_crash_reproducer = True, + expected_finding = "com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow", fuzzer_args = [ "$(rootpath src/test/java/com/example/LongStringFuzzerInput)", ], @@ -16,7 +17,7 @@ java_fuzz_target_test( java_fuzz_target_test( name = "JpegImageParserAutofuzz", - execute_crash_reproducer = True, + expected_finding = "java.lang.NegativeArraySizeException", fuzzer_args = [ "--autofuzz=org.apache.commons.imaging.formats.jpeg.JpegImageParser::getBufferedImage", # Exit after the first finding for testing purposes. @@ -37,6 +38,7 @@ java_fuzz_target_test( java_fuzz_target_test( name = "AutofuzzWithoutCoverage", + expected_finding = "java.lang.NullPointerException", fuzzer_args = [ # Autofuzz a method that triggers no coverage instrumentation (the Java standard library is # excluded by default). @@ -44,3 +46,27 @@ java_fuzz_target_test( "--keep_going=1", ], ) + +java_fuzz_target_test( + name = "ForkModeFuzzer", + size = "enormous", + srcs = [ + "src/test/java/com/example/ForkModeFuzzer.java", + ], + env = { + "JAVA_OPTS": "-Dfoo=not_foo -Djava_opts=1", + }, + expected_finding = "com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow", + fuzzer_args = [ + "-fork=3", + "--additional_jvm_args=-Dbaz=baz", + ] + select({ + # \\\\ becomes \\ when evaluated as a Starlark string literal, then \ in + # java_fuzz_target_test. + "@platforms//os:windows": ["--jvm_args=-Dfoo=foo;-Dbar=b\\\\;ar"], + "//conditions:default": ["--jvm_args=-Dfoo=foo:-Dbar=b\\\\:ar"], + }), + target_class = "com.example.ForkModeFuzzer", + # The exit codes of the forked libFuzzer processes are not picked up correctly. + target_compatible_with = SKIP_ON_MACOS, +) diff --git a/tests/src/test/java/com/example/ForkModeFuzzer.java b/tests/src/test/java/com/example/ForkModeFuzzer.java new file mode 100644 index 00000000..9f005124 --- /dev/null +++ b/tests/src/test/java/com/example/ForkModeFuzzer.java @@ -0,0 +1,48 @@ +// Copyright 2022 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.example; + +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow; + +public final class ForkModeFuzzer { + public static void fuzzerInitialize() { + // When running through a Java reproducer, do not check the Java opts. + if (System.getProperty("jazzer.is_reproducer") != null) + return; + String foo = System.getProperty("foo"); + String bar = System.getProperty("bar"); + String baz = System.getProperty("baz"); + // Only used to verify that arguments are correctly passed down to child processes. + if (foo == null || bar == null || baz == null || !foo.equals("foo") + || !(bar.equals("b;ar") || bar.equals("b:ar")) || !baz.equals("baz")) { + // Exit the process with an exit code different from that for a finding. + System.err.println("ERROR: Did not correctly pass all jvm_args to child process."); + System.err.printf("foo: %s%nbar: %s%nbaz: %s%n", foo, bar, baz); + System.exit(3); + } + // Only used to verify that Jazzer honors the JAVA_OPTS env var. + String javaOpts = System.getProperty("java_opts"); + if (javaOpts == null || !javaOpts.equals("1")) { + // Exit the process with an exit code different from that for a finding. + System.err.println("ERROR: Did not honor JAVA_OPTS."); + System.err.printf("java_opts: %s%n", javaOpts); + System.exit(4); + } + } + + public static void fuzzerTestOneInput(byte[] data) { + throw new FuzzerSecurityIssueLow("Passed fuzzerInitialize"); + } +} -- cgit v1.2.3 From e55abd5d6221cd7e3493937f240ba559c0a7b852 Mon Sep 17 00:00:00 2001 From: Simon Resch Date: Wed, 2 Mar 2022 16:40:58 +0100 Subject: Add missing Expression Language test dependency --- maven.bzl | 21 +++++++++++---------- maven_install.json | 15 +++++++++++++-- sanitizers/src/test/java/com/example/BUILD.bazel | 1 + 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/maven.bzl b/maven.bzl index 4440bd39..1c104839 100644 --- a/maven.bzl +++ b/maven.bzl @@ -20,22 +20,23 @@ JAZZER_API_COORDINATES = "com.code-intelligence:jazzer-api:%s" % JAZZER_API_VERS # **WARNING**: These Maven dependencies have known vulnerabilities and are only used to test that # Jazzer finds these issues. DO NOT USE. MAVEN_ARTIFACTS = [ - "junit:junit:4.12", - "org.openjdk.jmh:jmh-core:1.34", - "org.openjdk.jmh:jmh-generator-annprocess:1.34", - "org.apache.commons:commons-imaging:1.0-alpha2", - "com.mikesamuel:json-sanitizer:1.2.1", - "com.google.code.gson:gson:2.8.6", + "com.alibaba:fastjson:1.2.75", + "com.beust:klaxon:5.5", "com.fasterxml.jackson.core:jackson-core:2.12.1", "com.fasterxml.jackson.core:jackson-databind:2.12.1", "com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.12.1", - "com.alibaba:fastjson:1.2.75", - "com.beust:klaxon:5.5", + "com.google.code.gson:gson:2.8.6", + "com.mikesamuel:json-sanitizer:1.2.1", + "com.unboundid:unboundid-ldapsdk:6.0.3", + "javax.el:javax.el-api:3.0.1-b06", "javax.validation:validation-api:2.0.1.Final", "javax.xml.bind:jaxb-api:2.3.1", - "javax.el:javax.el-api:3.0.1-b06", + "junit:junit:4.12", + "org.apache.commons:commons-imaging:1.0-alpha2", + "org.glassfish:javax.el:3.0.1-b06", "org.hibernate:hibernate-validator:5.2.4.Final", - "com.unboundid:unboundid-ldapsdk:6.0.3", + "org.openjdk.jmh:jmh-core:1.34", + "org.openjdk.jmh:jmh-generator-annprocess:1.34", maven.artifact("org.apache.logging.log4j", "log4j-api", "2.14.1", testonly = True), maven.artifact("org.apache.logging.log4j", "log4j-core", "2.14.1", testonly = True), ] diff --git a/maven_install.json b/maven_install.json index 12f6c092..ef0243ae 100644 --- a/maven_install.json +++ b/maven_install.json @@ -1,8 +1,8 @@ { "dependency_tree": { "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL", - "__INPUT_ARTIFACTS_HASH": 1013648735, - "__RESOLVED_ARTIFACTS_HASH": 304464594, + "__INPUT_ARTIFACTS_HASH": -1602439973, + "__RESOLVED_ARTIFACTS_HASH": -214698824, "conflict_resolution": {}, "dependencies": [ { @@ -258,6 +258,17 @@ "sha256": "ade7402a70667a727635d5c4c29495f4ff96f061f12539763f6f123973b465b0", "url": "https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-core/2.14.1/log4j-core-2.14.1.jar" }, + { + "coord": "org.glassfish:javax.el:3.0.1-b06", + "dependencies": [], + "directDependencies": [], + "file": "v1/https/repo1.maven.org/maven2/org/glassfish/javax.el/3.0.1-b06/javax.el-3.0.1-b06.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/org/glassfish/javax.el/3.0.1-b06/javax.el-3.0.1-b06.jar" + ], + "sha256": "c255fe3ff4d7e491caf92c10c497f3c77d19acc4832d9bd2e80180d168fcedd2", + "url": "https://repo1.maven.org/maven2/org/glassfish/javax.el/3.0.1-b06/javax.el-3.0.1-b06.jar" + }, { "coord": "org.hamcrest:hamcrest-core:1.3", "dependencies": [], diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index 5abece30..7a6064fc 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -27,6 +27,7 @@ java_fuzz_target_test( "@maven//:javax_el_javax_el_api", "@maven//:javax_validation_validation_api", "@maven//:javax_xml_bind_jaxb_api", + "@maven//:org_glassfish_javax_el", "@maven//:org_hibernate_hibernate_validator", ], ) -- cgit v1.2.3 From e966b316e9c44817cad19a5bd59f42b790e33f86 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Thu, 3 Mar 2022 10:53:07 +0100 Subject: Ignore specific windows exception in RegexCanonEqInjection --- .../src/test/java/com/example/RegexCanonEqInjection.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sanitizers/src/test/java/com/example/RegexCanonEqInjection.java b/sanitizers/src/test/java/com/example/RegexCanonEqInjection.java index f7b906e7..e2d0b722 100644 --- a/sanitizers/src/test/java/com/example/RegexCanonEqInjection.java +++ b/sanitizers/src/test/java/com/example/RegexCanonEqInjection.java @@ -24,6 +24,18 @@ public class RegexCanonEqInjection { try { Pattern.compile(Pattern.quote(input), Pattern.CANON_EQ); } catch (PatternSyntaxException ignored) { + } catch (IllegalArgumentException ignored) { + // "[媼" generates an IllegalArgumentException but only on Windows using + // Java 8. We ignore this for now. + // + // java.lang.IllegalArgumentException + // at java.lang.AbstractStringBuilder.appendCodePoint(AbstractStringBuilder.java:800) + // at java.lang.StringBuilder.appendCodePoint(StringBuilder.java:240) + // at java.util.regex.Pattern.normalizeCharClass(Pattern.java:1430) + // at java.util.regex.Pattern.normalize(Pattern.java:1396) + // at java.util.regex.Pattern.compile(Pattern.java:1665) + // at java.util.regex.Pattern.(Pattern.java:1352) + // at java.util.regex.Pattern.compile(Pattern.java:1054) } } } -- cgit v1.2.3 From b2e8a9dca69227f2952ec80020f436789efae887 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Tue, 1 Mar 2022 12:46:52 +0100 Subject: Hook ClassLoader methods Hook all Class.forName and ClassLoader.loadClass methods. Some are protected or even final but could be used by subclasses. --- .../jazzer/sanitizers/ReflectiveCall.kt | 24 ++++++++++++++--- sanitizers/src/test/java/com/example/BUILD.bazel | 12 ++++++++- .../java/com/example/ClassLoaderLoadClass.java | 30 ++++++++++++++++++++++ .../src/test/java/com/example/ReflectiveCall.java | 6 ++--- 4 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 sanitizers/src/test/java/com/example/ClassLoaderLoadClass.java diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt index 3dfd3ca1..7072cc7d 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt @@ -17,18 +17,36 @@ package com.code_intelligence.jazzer.sanitizers import com.code_intelligence.jazzer.api.HookType import com.code_intelligence.jazzer.api.Jazzer import com.code_intelligence.jazzer.api.MethodHook +import com.code_intelligence.jazzer.api.MethodHooks import java.lang.invoke.MethodHandle /** - * Detects unsafe reflective calls that lead to attacker-controlled method calls. + * Detects unsafe calls that lead to attacker-controlled class loading. + * + * Guide the fuzzer to load honeypot class via [Class.forName] or [ClassLoader.loadClass]. */ @Suppress("unused_parameter", "unused") object ReflectiveCall { - @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Class", targetMethod = "forName") + @MethodHooks( + MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Class", targetMethod = "forName", targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Class;"), + MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Class", targetMethod = "forName", targetMethodDescriptor = "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"), + MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.ClassLoader", targetMethod = "loadClass", targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Class;"), + MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.ClassLoader", targetMethod = "loadClass", targetMethodDescriptor = "(Ljava/lang/String;Z)Ljava/lang/Class;"), + ) @JvmStatic - fun classForNameHook(method: MethodHandle?, alwaysNull: Any?, args: Array, hookId: Int) { + fun loadClassHook(method: MethodHandle?, alwaysNull: Any?, args: Array, hookId: Int) { val className = args[0] as? String ?: return Jazzer.guideTowardsEquality(className, HONEYPOT_CLASS_NAME, hookId) } + + @MethodHooks( + MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Class", targetMethod = "forName", targetMethodDescriptor = "(Ljava/lang/Module;Ljava/lang/String;)Ljava/lang/Class;"), + MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.ClassLoader", targetMethod = "loadClass", targetMethodDescriptor = "(Ljava/lang/Module;Ljava/lang/String;)Ljava/lang/Class;"), + ) + @JvmStatic + fun loadClassWithModuleHook(method: MethodHandle?, alwaysNull: Any?, args: Array, hookId: Int) { + val className = args[1] as? String ?: return + Jazzer.guideTowardsEquality(className, HONEYPOT_CLASS_NAME, hookId) + } } diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index 7a6064fc..5279fe2e 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -78,6 +78,16 @@ java_fuzz_target_test( java_fuzz_target_test( name = "RegexCanonEqInjection", - srcs = ["RegexCanonEqInjection.java"], + srcs = [ + "RegexCanonEqInjection.java", + ], target_class = "com.example.RegexCanonEqInjection", ) + +java_fuzz_target_test( + name = "ClassLoaderLoadClass", + srcs = [ + "ClassLoaderLoadClass.java", + ], + target_class = "com.example.ClassLoaderLoadClass", +) diff --git a/sanitizers/src/test/java/com/example/ClassLoaderLoadClass.java b/sanitizers/src/test/java/com/example/ClassLoaderLoadClass.java new file mode 100644 index 00000000..c3fa47ac --- /dev/null +++ b/sanitizers/src/test/java/com/example/ClassLoaderLoadClass.java @@ -0,0 +1,30 @@ +// 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.example; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import java.lang.reflect.InvocationTargetException; + +public class ClassLoaderLoadClass { + public static void fuzzerTestOneInput(FuzzedDataProvider data) throws InterruptedException { + String input = data.consumeRemainingAsAsciiString(); + try { + // create an instance to trigger class initialization + ClassLoaderLoadClass.class.getClassLoader().loadClass(input).getConstructor().newInstance(); + } catch (ClassNotFoundException | InvocationTargetException | InstantiationException + | IllegalAccessException | NoSuchMethodException ignored) { + } + } +} diff --git a/sanitizers/src/test/java/com/example/ReflectiveCall.java b/sanitizers/src/test/java/com/example/ReflectiveCall.java index 7f85e486..e6b62b45 100644 --- a/sanitizers/src/test/java/com/example/ReflectiveCall.java +++ b/sanitizers/src/test/java/com/example/ReflectiveCall.java @@ -15,7 +15,6 @@ package com.example; import com.code_intelligence.jazzer.api.FuzzedDataProvider; -import java.lang.reflect.InvocationTargetException; public class ReflectiveCall { public static void fuzzerTestOneInput(FuzzedDataProvider data) { @@ -23,9 +22,8 @@ public class ReflectiveCall { if (input.startsWith("@")) { String className = input.substring(1); try { - Class.forName(className).getConstructor().newInstance(); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException - | NoSuchMethodException | ClassNotFoundException ignored) { + Class.forName(className); + } catch (ClassNotFoundException ignored) { } } } -- cgit v1.2.3 From 60776697833d84d968a8b6c403034fb1aa0a472b Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Tue, 1 Mar 2022 15:58:39 +0100 Subject: Report a finding whenever honeypot class is loaded Whenever external input leads to unrestricted class loading a RCE could be possible. Report a finding every time and not only in specific use-cases. --- sanitizers/src/main/java/jaz/Zer.java | 231 ++++++++++++++++++++++++++-------- 1 file changed, 179 insertions(+), 52 deletions(-) diff --git a/sanitizers/src/main/java/jaz/Zer.java b/sanitizers/src/main/java/jaz/Zer.java index 0b27609c..08ca3d2e 100644 --- a/sanitizers/src/main/java/jaz/Zer.java +++ b/sanitizers/src/main/java/jaz/Zer.java @@ -15,93 +15,220 @@ package jaz; import com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh; -import com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium; import com.code_intelligence.jazzer.api.Jazzer; -import java.io.IOException; -import java.io.ObjectInputStream; +import java.io.Closeable; +import java.io.Flushable; +import java.io.Serializable; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.function.Function; /** - * A honeypot class that reports an appropriate finding on any interaction with one of its methods - * or initializers. + * A honeypot class that reports a finding on initialization. * - * Note: This class must not be referenced in any way by the rest of the code, not even statically. - * When referring to it, always use its hardcoded class name "jaz.Zer". + * Class loading based on externally controlled data could lead to RCE + * depending on available classes on the classpath. Even if no applicable + * gadget class is available, allowing input to control class loading is a bad + * idea and should be prevented. A finding is generated whenever the class + * is loaded and initialized, regardless of its further use. + *

    + * This class needs to implement {@link Serializable} to be considered in + * deserialization scenarios. It also implements common constructors, getter + * and setter and common interfaces to increase chances of passing + * deserialization checks. + *

    + * Note: Jackson provides a nice list of "nasty classes" at + * SubTypeValidator. + *

    + * Note: This class must not be referenced in any way by the rest of the code, not even + * statically. When referring to it, always use its hardcoded class name {@code jaz.Zer}. */ -@SuppressWarnings("unused") -public class Zer implements java.io.Serializable { +@SuppressWarnings({"rawtypes", "unused"}) +public class Zer + implements Serializable, Cloneable, Comparable, Comparator, Closeable, Flushable, Iterable, + Iterator, Runnable, Callable, Function, Collection, List { static final long serialVersionUID = 42L; - private static final Throwable staticInitializerCause; - static { - staticInitializerCause = new FuzzerSecurityIssueMedium("finalize call on arbitrary object"); + Jazzer.reportFindingFromHook(new FuzzerSecurityIssueHigh("Remote Code Execution\n" + + "Unrestricted class loading based on externally controlled data may allow\n" + + "remote code execution depending on available classes on the classpath.")); } - public Zer() { - Jazzer.reportFindingFromHook( - new FuzzerSecurityIssueMedium("default constructor call on arbitrary object")); + // Common constructors + + public Zer() {} + + public Zer(String arg1) {} + + public Zer(String arg1, Throwable arg2) {} + + // Getter/Setter + + public Object getJaz() { + return this; } - public Zer(String arg1) { - Jazzer.reportFindingFromHook( - new FuzzerSecurityIssueMedium("String constructor call on arbitrary object")); + public void setJaz(String jaz) {} + + // Common interface stubs + + @Override + public void close() {} + + @Override + public void flush() {} + + @Override + public int compareTo(Zer o) { + return 0; } - public Zer(String arg1, Throwable arg2) { - Jazzer.reportFindingFromHook( - new FuzzerSecurityIssueMedium("(String, Throwable) constructor call on arbitrary object")); + @Override + public int compare(Object o1, Object o2) { + return 0; } - private String jaz; + @Override + public int size() { + return 0; + } - public String getJaz() { - Jazzer.reportFindingFromHook(new FuzzerSecurityIssueMedium("getter call on arbitrary object")); - return jaz; + @Override + public boolean isEmpty() { + return false; } - public void setJaz(String jaz) { - Jazzer.reportFindingFromHook(new FuzzerSecurityIssueMedium("setter call on arbitrary object")); - this.jaz = jaz; + @Override + public boolean contains(Object o) { + return false; } @Override - public int hashCode() { - Jazzer.reportFindingFromHook( - new FuzzerSecurityIssueMedium("hashCode call on arbitrary object")); - return super.hashCode(); + public Object[] toArray() { + return new Object[0]; } @Override - public boolean equals(Object obj) { - Jazzer.reportFindingFromHook(new FuzzerSecurityIssueMedium("equals call on arbitrary object")); - return super.equals(obj); + public boolean add(Object o) { + return false; } @Override - protected Object clone() throws CloneNotSupportedException { - Jazzer.reportFindingFromHook(new FuzzerSecurityIssueMedium("clone call on arbitrary object")); - return super.clone(); + public boolean remove(Object o) { + return false; } @Override - public String toString() { - Jazzer.reportFindingFromHook( - new FuzzerSecurityIssueMedium("toString call on arbitrary object")); - return super.toString(); + public boolean addAll(Collection c) { + return false; } @Override - protected void finalize() throws Throwable { - // finalize is invoked automatically by the GC with an uninformative stack trace. We use the - // stack trace prerecorded in the static initializer. - Jazzer.reportFindingFromHook(staticInitializerCause); - super.finalize(); + public boolean addAll(int index, Collection c) { + return false; } - private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { - Jazzer.reportFindingFromHook(new FuzzerSecurityIssueHigh("Remote Code Execution\n" - + " Deserialization of arbitrary classes with custom readObject may allow remote\n" - + " code execution depending on the classpath.")); - in.defaultReadObject(); + @Override + public void clear() {} + + @Override + public Object get(int index) { + return this; + } + + @Override + public Object set(int index, Object element) { + return this; + } + + @Override + public void add(int index, Object element) {} + + @Override + public Object remove(int index) { + return this; + } + + @Override + public int indexOf(Object o) { + return 0; + } + + @Override + public int lastIndexOf(Object o) { + return 0; + } + + @Override + @SuppressWarnings("ConstantConditions") + public ListIterator listIterator() { + return null; + } + + @Override + @SuppressWarnings("ConstantConditions") + public ListIterator listIterator(int index) { + return null; + } + + @Override + public List subList(int fromIndex, int toIndex) { + return this; + } + + @Override + public boolean retainAll(Collection c) { + return false; + } + + @Override + public boolean removeAll(Collection c) { + return false; + } + + @Override + public boolean containsAll(Collection c) { + return false; + } + + @Override + public Object[] toArray(Object[] a) { + return new Object[0]; + } + + @Override + public Iterator iterator() { + return this; + } + + @Override + public void run() {} + + @Override + public boolean hasNext() { + return false; + } + + @Override + public Object next() { + return this; + } + + @Override + public Object call() throws Exception { + return this; + } + + @Override + public Object apply(Object o) { + return this; + } + + @Override + @SuppressWarnings("MethodDoesntCallSuperMethod") + public Object clone() { + return this; } } -- cgit v1.2.3 From 7e6aa6e6da669d6653b8dd188c2845762899bc16 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Fri, 4 Mar 2022 09:41:23 +0100 Subject: Don't suppress LDAP exceptions in hooks --- .../java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt index 7aee6873..1afd614e 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/LdapInjection.kt @@ -101,7 +101,7 @@ object LdapInjection { } } } catch (e: Exception) { - return when (e) { + when (e) { is InvalidSearchFilterException -> Jazzer.reportFindingFromHook( FuzzerSecurityIssueCritical( @@ -116,8 +116,8 @@ Search filters based on untrusted data must be escape as specified in RFC 4515." Distinguished Names based on untrusted data must be escaped as specified in RFC 2253.""" ) ) - else -> throw e } + throw e } } } -- cgit v1.2.3 From c60cb26e69307874ae61c70bcf9f0c0971fb07e6 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 3 Mar 2022 17:16:49 +0100 Subject: Do not apply replace hooks to Java 6 class files Replace hooks usually can't function with a null MethodHandle parameter, but instrumented Java 6 class files cannot pass in any other value. --- .../jazzer/instrumentor/HookMethodVisitor.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt index b2e2cd04..da9e7150 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt @@ -20,6 +20,7 @@ import org.objectweb.asm.MethodVisitor import org.objectweb.asm.Opcodes import org.objectweb.asm.Type import org.objectweb.asm.commons.LocalVariablesSorter +import java.util.concurrent.atomic.AtomicBoolean internal fun makeHookMethodVisitor( access: Int, @@ -41,6 +42,10 @@ private class HookMethodVisitor( private val random: DeterministicRandom, ) : MethodVisitor(Instrumentor.ASM_API_VERSION, methodVisitor) { + companion object { + private val showUnsupportedHookWarning = AtomicBoolean(true) + } + val lvs = object : LocalVariablesSorter(Instrumentor.ASM_API_VERSION, access, descriptor, this) { override fun updateNewLocals(newLocals: Array) { // The local variables involved in calling hooks do not need to outlive the current @@ -123,6 +128,19 @@ private class HookMethodVisitor( return } + if (java6Mode && hookType == HookType.REPLACE) { + if (showUnsupportedHookWarning.getAndSet(false)) { + println( + """WARN: Some hooks could not be applied to class files built for Java 7 or lower. + |WARN: Ensure that the fuzz target and its dependencies are compiled with + |WARN: -target 8 or higher to identify as many bugs as possible. + """.trimMargin() + ) + } + visitNextHookTypeOrCall(hookType, false, opcode, owner, methodName, methodDescriptor, isInterface) + return + } + // The hookId is used to identify a call site. val hookId = random.nextInt() -- cgit v1.2.3 From 7afdcf5f932f20f67835c5493efe03273ea7a558 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 3 Mar 2022 08:59:43 +0100 Subject: Simplify existing regex injection hook Hooks are able to reference classes to hook without restrictions. --- .../java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt index 34a2d7e8..c2c60ab9 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt @@ -19,12 +19,10 @@ import com.code_intelligence.jazzer.api.HookType import com.code_intelligence.jazzer.api.Jazzer import com.code_intelligence.jazzer.api.MethodHook import java.lang.invoke.MethodHandle +import java.util.regex.Pattern @Suppress("unused_parameter", "unused") object RegexInjection { - // Inlined value of java.util.regex.Pattern.CANON_EQ to prevent a runtime dependency on Pattern. - private const val PATTERN_CANON_EQ = 0x80 - /** * Part of an OOM "exploit" for [java.util.regex.Pattern.compile] with the * [java.util.regex.Pattern.CANON_EQ] flag, formed by three consecutive combining marks, in this @@ -49,7 +47,7 @@ object RegexInjection { fun patternCompileWithFlagsHook(method: MethodHandle?, alwaysNull: Any?, args: Array, hookId: Int) { val pattern = args[0] as? String ?: return val flags = args[1] as? Int ?: return - if (flags and PATTERN_CANON_EQ == 0) return + if (flags and Pattern.CANON_EQ == 0) return if (pattern.contains(CANON_EQ_ALMOST_EXPLOIT)) { Jazzer.reportFindingFromHook( FuzzerSecurityIssueLow( -- cgit v1.2.3 From 6a1beba0c8b4c5c1d4084abc04139cd33fff8e46 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 3 Mar 2022 14:15:35 +0100 Subject: Add a sanitizer for general regex injections --- .../jazzer/sanitizers/RegexInjection.kt | 119 +++++++++++++++++---- sanitizers/src/test/java/com/example/BUILD.bazel | 6 ++ .../com/example/RegexInsecureQuoteInjection.java | 29 +++++ 3 files changed, 136 insertions(+), 18 deletions(-) create mode 100644 sanitizers/src/test/java/com/example/RegexInsecureQuoteInjection.java diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt index c2c60ab9..8435d032 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt @@ -18,8 +18,10 @@ import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow import com.code_intelligence.jazzer.api.HookType import com.code_intelligence.jazzer.api.Jazzer import com.code_intelligence.jazzer.api.MethodHook +import com.code_intelligence.jazzer.api.MethodHooks import java.lang.invoke.MethodHandle import java.util.regex.Pattern +import java.util.regex.PatternSyntaxException @Suppress("unused_parameter", "unused") object RegexInjection { @@ -31,34 +33,115 @@ object RegexInjection { */ private const val CANON_EQ_ALMOST_EXPLOIT = "\u0300\u0300\u0300" - // With CANON_EQ enabled, Pattern.compile allocates an array with a size that is - // (super-)exponential in the number of consecutive Unicode combining marks. We use a mild case - // of this as a magic string based on which we trigger a finding. - // Note: The fuzzer might trigger an OutOfMemoryError or NegativeArraySizeException (if the size - // of the array overflows an int) by chance before it correctly emits this "exploit". In that - // case, we report the original exception instead. + /** + * When injected into a regex pattern, helps the fuzzer break out of quotes and character + * classes in order to cause a [PatternSyntaxException]. + */ + private const val FORCE_PATTERN_SYNTAX_EXCEPTION_PATTERN = "\\E]\\E]]]]]]" + @MethodHook( - type = HookType.BEFORE, + type = HookType.REPLACE, targetClassName = "java.util.regex.Pattern", targetMethod = "compile", targetMethodDescriptor = "(Ljava/lang/String;I)Ljava/util/regex/Pattern;" ) @JvmStatic - fun patternCompileWithFlagsHook(method: MethodHandle?, alwaysNull: Any?, args: Array, hookId: Int) { - val pattern = args[0] as? String ?: return - val flags = args[1] as? Int ?: return - if (flags and Pattern.CANON_EQ == 0) return - if (pattern.contains(CANON_EQ_ALMOST_EXPLOIT)) { - Jazzer.reportFindingFromHook( - FuzzerSecurityIssueLow( - """Regular Expression Injection with CANON_EQ + fun compileWithFlagsHook(method: MethodHandle, alwaysNull: Any?, args: Array, hookId: Int): Any? { + val pattern = args[0] as String? + val hasCanonEqFlag = ((args[1] as Int) and Pattern.CANON_EQ) != 0 + return hookInternal(method, pattern, hasCanonEqFlag, args, hookId) + } + + @MethodHooks( + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.util.regex.Pattern", + targetMethod = "compile", + targetMethodDescriptor = "(Ljava/lang/String;)Ljava/util/regex/Pattern;" + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.util.regex.Pattern", + targetMethod = "matches", + targetMethodDescriptor = "(Ljava/lang/String;Ljava/lang/CharSequence;)Z" + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.lang.String", + targetMethod = "matches", + targetMethodDescriptor = "(Ljava/lang/String;)Z", + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.lang.String", + targetMethod = "replaceAll", + targetMethodDescriptor = "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.lang.String", + targetMethod = "replaceFirst", + targetMethodDescriptor = "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.lang.String", + targetMethod = "split", + targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/String;", + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.lang.String", + targetMethod = "split", + targetMethodDescriptor = "(Ljava/lang/String;I)Ljava/lang/String;", + ), + ) + @JvmStatic + fun patternHook(method: MethodHandle, alwaysNull: Any?, args: Array, hookId: Int): Any? { + return hookInternal(method, args[0] as String?, false, args, hookId) + } + + private fun hookInternal(method: MethodHandle, pattern: String?, hasCanonEqFlag: Boolean, args: Array, hookId: Int): Any? { + if (hasCanonEqFlag && pattern != null) { + // With CANON_EQ enabled, Pattern.compile allocates an array with a size that is + // (super-)exponential in the number of consecutive Unicode combining marks. We use a mild case + // of this as a magic string based on which we trigger a finding. + // Note: The fuzzer might trigger an OutOfMemoryError or NegativeArraySizeException (if the size + // of the array overflows an int) by chance before it correctly emits this "exploit". In that + // case, we report the original exception instead. + if (pattern.contains(CANON_EQ_ALMOST_EXPLOIT)) { + Jazzer.reportFindingFromHook( + FuzzerSecurityIssueLow( + """Regular Expression Injection with CANON_EQ When java.util.regex.Pattern.compile is used with the Pattern.CANON_EQ flag, every injection into the regular expression pattern can cause arbitrarily large memory allocations, even when wrapped with Pattern.quote(...).""" + ) + ) + } else { + Jazzer.guideTowardsContainment(pattern, CANON_EQ_ALMOST_EXPLOIT, hookId) + } + } + try { + return method.invokeWithArguments(*args).also { + // Only submit a fuzzer hint if no exception has been thrown. + if (!hasCanonEqFlag && pattern != null) { + Jazzer.guideTowardsContainment(pattern, FORCE_PATTERN_SYNTAX_EXCEPTION_PATTERN, hookId) + } + } + } catch (e: Exception) { + if (e is PatternSyntaxException) { + Jazzer.reportFindingFromHook( + FuzzerSecurityIssueLow( + """Regular Expression Injection +Regular expression patterns that contain unescaped untrusted input can consume +arbitrary amounts of CPU time. To properly escape the input, wrap it with +Pattern.quote(...).""", + e + ) ) - ) - } else { - Jazzer.guideTowardsContainment(pattern, CANON_EQ_ALMOST_EXPLOIT, hookId) + } + throw e } } } diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index 5279fe2e..4c5f4d2b 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -76,6 +76,12 @@ java_fuzz_target_test( ], ) +java_fuzz_target_test( + name = "RegexInsecureQuoteInjection", + srcs = ["RegexInsecureQuoteInjection.java"], + target_class = "com.example.RegexInsecureQuoteInjection", +) + java_fuzz_target_test( name = "RegexCanonEqInjection", srcs = [ diff --git a/sanitizers/src/test/java/com/example/RegexInsecureQuoteInjection.java b/sanitizers/src/test/java/com/example/RegexInsecureQuoteInjection.java new file mode 100644 index 00000000..a548cfb2 --- /dev/null +++ b/sanitizers/src/test/java/com/example/RegexInsecureQuoteInjection.java @@ -0,0 +1,29 @@ +// Copyright 2022 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.example; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +public class RegexInsecureQuoteInjection { + public static void fuzzerTestOneInput(FuzzedDataProvider data) { + String input = data.consumeRemainingAsString(); + try { + Pattern.matches("\\Q" + input + "\\E", "foobar"); + } catch (PatternSyntaxException ignored) { + } + } +} -- cgit v1.2.3 From efa7ac3298d57c662f2f1fa1b932bc0ee301be21 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sun, 6 Mar 2022 13:34:26 +0100 Subject: Fix regex injection hook invocation for String functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the incorrect invocation of the MethodHandle in the case where the regex injection hook is applied to a String function, which do require passing in the this object: == Java Exception: java.lang.invoke.WrongMethodTypeException: cannot convert MethodHandle(String,String,String)String to (Object,Object)Object  at java.base/java.lang.invoke.MethodHandle.asTypeUncached(MethodHandle.java:881)  at java.base/java.lang.invoke.MethodHandle.asType(MethodHandle.java:866)  at java.base/java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:729)  at com.code_intelligence.jazzer.sanitizers.RegexInjection.hookInternal(RegexInjection.kt:126)  at com.code_intelligence.jazzer.sanitizers.RegexInjection.patternHook(RegexInjection.kt:101) ... Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=45246 --- .../jazzer/sanitizers/RegexInjection.kt | 23 +++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt index 8435d032..def5f6e3 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexInjection.kt @@ -29,7 +29,7 @@ object RegexInjection { * Part of an OOM "exploit" for [java.util.regex.Pattern.compile] with the * [java.util.regex.Pattern.CANON_EQ] flag, formed by three consecutive combining marks, in this * case grave accents: ◌̀. - * See [patternCompileWithFlagsHook] for details. + * See [compileWithFlagsHook] for details. */ private const val CANON_EQ_ALMOST_EXPLOIT = "\u0300\u0300\u0300" @@ -49,7 +49,7 @@ object RegexInjection { fun compileWithFlagsHook(method: MethodHandle, alwaysNull: Any?, args: Array, hookId: Int): Any? { val pattern = args[0] as String? val hasCanonEqFlag = ((args[1] as Int) and Pattern.CANON_EQ) != 0 - return hookInternal(method, pattern, hasCanonEqFlag, args, hookId) + return hookInternal(method, pattern, hasCanonEqFlag, hookId, *args) } @MethodHooks( @@ -65,6 +65,13 @@ object RegexInjection { targetMethod = "matches", targetMethodDescriptor = "(Ljava/lang/String;Ljava/lang/CharSequence;)Z" ), + ) + @JvmStatic + fun patternHook(method: MethodHandle, alwaysNull: Any?, args: Array, hookId: Int): Any? { + return hookInternal(method, args[0] as String?, false, hookId, *args) + } + + @MethodHooks( MethodHook( type = HookType.REPLACE, targetClassName = "java.lang.String", @@ -97,11 +104,17 @@ object RegexInjection { ), ) @JvmStatic - fun patternHook(method: MethodHandle, alwaysNull: Any?, args: Array, hookId: Int): Any? { - return hookInternal(method, args[0] as String?, false, args, hookId) + fun stringHook(method: MethodHandle, thisObject: Any?, args: Array, hookId: Int): Any? { + return hookInternal(method, args[0] as String?, false, hookId, thisObject, *args) } - private fun hookInternal(method: MethodHandle, pattern: String?, hasCanonEqFlag: Boolean, args: Array, hookId: Int): Any? { + private fun hookInternal( + method: MethodHandle, + pattern: String?, + hasCanonEqFlag: Boolean, + hookId: Int, + vararg args: Any? + ): Any? { if (hasCanonEqFlag && pattern != null) { // With CANON_EQ enabled, Pattern.compile allocates an array with a size that is // (super-)exponential in the number of consecutive Unicode combining marks. We use a mild case -- cgit v1.2.3 From 003422bb991a29ed44e962496d40e3a56c3918d4 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 1 Mar 2022 13:08:17 +0100 Subject: Optimize libFuzzer callbacks without critical natives Implements the benchmark winners without their corresponding critical natives implementations, which will be added in a follow-up PR. --- .../runtime/TraceDataFlowNativeCallbacks.java | 25 ++++++- driver/libfuzzer_callbacks.cpp | 83 +++++++--------------- driver/sanitizer_symbols_for_tests.cpp | 8 +-- 3 files changed, 49 insertions(+), 67 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java index e862d76a..efbc3411 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java @@ -16,6 +16,7 @@ package com.code_intelligence.jazzer.runtime; import com.code_intelligence.jazzer.utils.Utils; import java.lang.reflect.Executable; +import java.nio.charset.Charset; @SuppressWarnings("unused") final public class TraceDataFlowNativeCallbacks { @@ -23,6 +24,12 @@ final public class TraceDataFlowNativeCallbacks { // such as: // if (USE_FAKE_PCS) ... else ... private static final boolean USE_FAKE_PCS = useFakePcs(); + // Note that we are not encoding as modified UTF-8 here: The FuzzedDataProvider transparently + // converts CESU8 into modified UTF-8 by coding null bytes on two bytes. Since the fuzzer is more + // likely to insert literal null bytes, having both the fuzzer input and the reported string + // comparisons be CESU8 should perform even better than the current implementation using modified + // UTF-8. + private static final Charset FUZZED_DATA_CHARSET = Charset.forName("CESU8"); /* trace-cmp */ public static void traceCmpInt(int arg1, int arg2, int pc) { @@ -58,8 +65,14 @@ final public class TraceDataFlowNativeCallbacks { } public static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc); - public static native void traceStrcmp(String s1, String s2, int result, int pc); - public static native void traceStrstr(String s1, String s2, int pc); + + public static void traceStrcmp(String s1, String s2, int result, int pc) { + traceMemcmp(encodeForLibFuzzer(s1), encodeForLibFuzzer(s2), result, pc); + } + + public static void traceStrstr(String s1, String s2, int pc) { + traceStrstr0(encodeForLibFuzzer(s2), pc); + } /* trace-div */ public static void traceDivInt(int val, int pc) { @@ -139,6 +152,12 @@ final public class TraceDataFlowNativeCallbacks { public static native void handleLibraryLoad(); + private static byte[] encodeForLibFuzzer(String str) { + // libFuzzer string hooks only ever consume the first 64 bytes, so we can definitely cut the + // string off after 64 characters. + return str.substring(0, Math.min(str.length(), 64)).getBytes(FUZZED_DATA_CHARSET); + } + private static boolean useFakePcs() { String rawFakePcs = System.getProperty("jazzer.fake_pcs"); if (rawFakePcs == null) { @@ -147,6 +166,8 @@ final public class TraceDataFlowNativeCallbacks { return Boolean.parseBoolean(rawFakePcs); } + private static native void traceStrstr0(byte[] needle, int pc); + private static native void traceCmpInt(int arg1, int arg2); private static native void traceCmpIntWithPc(int arg1, int arg2, int pc); private static native void traceConstCmpInt(int arg1, int arg2); diff --git a/driver/libfuzzer_callbacks.cpp b/driver/libfuzzer_callbacks.cpp index cbf513a0..78a4d94e 100644 --- a/driver/libfuzzer_callbacks.cpp +++ b/driver/libfuzzer_callbacks.cpp @@ -36,15 +36,11 @@ DEFINE_bool( namespace { extern "C" { -void __sanitizer_weak_hook_memcmp(void *caller_pc, const void *s1, - const void *s2, std::size_t n, int result); void __sanitizer_weak_hook_compare_bytes(void *caller_pc, const void *s1, const void *s2, std::size_t n1, std::size_t n2, int result); -void __sanitizer_weak_hook_strcmp(void *caller_pc, const char *s1, - const char *s2, int result); -void __sanitizer_weak_hook_strstr(void *caller_pc, const char *s1, - const char *s2, const char *result); +void __sanitizer_weak_hook_memmem(void *called_pc, const void *s1, size_t len1, + const void *s2, size_t len2, void *result); void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2); void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2); @@ -62,59 +58,30 @@ inline __attribute__((always_inline)) void *idToPc(jint id) { } // namespace [[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceStrcmp( - JNIEnv *env, jclass cls, jstring s1, jstring s2, jint result, jint id) { - const char *s1_native = env->GetStringUTFChars(s1, nullptr); - if (env->ExceptionCheck()) env->ExceptionDescribe(); - std::size_t n1 = env->GetStringUTFLength(s1); - if (env->ExceptionCheck()) env->ExceptionDescribe(); - const char *s2_native = env->GetStringUTFChars(s2, nullptr); - if (env->ExceptionCheck()) env->ExceptionDescribe(); - std::size_t n2 = env->GetStringUTFLength(s2); - if (env->ExceptionCheck()) env->ExceptionDescribe(); - __sanitizer_weak_hook_compare_bytes(idToPc(id), s1_native, s2_native, n1, n2, - result); - env->ReleaseStringUTFChars(s1, s1_native); - if (env->ExceptionCheck()) env->ExceptionDescribe(); - env->ReleaseStringUTFChars(s2, s2_native); - if (env->ExceptionCheck()) env->ExceptionDescribe(); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceStrstr( - JNIEnv *env, jclass cls, jstring s1, jstring s2, jint id) { - const char *s1_native = env->GetStringUTFChars(s1, nullptr); - if (env->ExceptionCheck()) env->ExceptionDescribe(); - const char *s2_native = env->GetStringUTFChars(s2, nullptr); - if (env->ExceptionCheck()) env->ExceptionDescribe(); - // libFuzzer currently ignores the result, which allows us to simply pass a - // valid but arbitrary pointer here instead of performing an actual strstr - // operation. - __sanitizer_weak_hook_strstr(idToPc(id), s1_native, s2_native, s1_native); - env->ReleaseStringUTFChars(s1, s1_native); - if (env->ExceptionCheck()) env->ExceptionDescribe(); - env->ReleaseStringUTFChars(s2, s2_native); - if (env->ExceptionCheck()) env->ExceptionDescribe(); +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceStrstr0( + JNIEnv *env, jclass cls, jbyteArray needle, jint id) { + auto *needle_native = + static_cast(env->GetPrimitiveArrayCritical(needle, nullptr)); + jint needle_length = env->GetArrayLength(needle); + __sanitizer_weak_hook_memmem(idToPc(id), nullptr, 0, needle_native, + needle_length, nullptr); + env->ReleasePrimitiveArrayCritical(needle, needle_native, JNI_ABORT); } [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceMemcmp( JNIEnv *env, jclass cls, jbyteArray b1, jbyteArray b2, jint result, jint id) { - jbyte *b1_native = env->GetByteArrayElements(b1, nullptr); - if (env->ExceptionCheck()) env->ExceptionDescribe(); - jbyte *b2_native = env->GetByteArrayElements(b2, nullptr); - if (env->ExceptionCheck()) env->ExceptionDescribe(); + auto *b1_native = + static_cast(env->GetPrimitiveArrayCritical(b1, nullptr)); + auto *b2_native = + static_cast(env->GetPrimitiveArrayCritical(b2, nullptr)); jint b1_length = env->GetArrayLength(b1); - if (env->ExceptionCheck()) env->ExceptionDescribe(); jint b2_length = env->GetArrayLength(b2); - if (env->ExceptionCheck()) env->ExceptionDescribe(); __sanitizer_weak_hook_compare_bytes(idToPc(id), b1_native, b2_native, b1_length, b2_length, result); - env->ReleaseByteArrayElements(b1, b1_native, JNI_ABORT); - if (env->ExceptionCheck()) env->ExceptionDescribe(); - env->ReleaseByteArrayElements(b2, b2_native, JNI_ABORT); - if (env->ExceptionCheck()) env->ExceptionDescribe(); + env->ReleasePrimitiveArrayCritical(b1, b1_native, JNI_ABORT); + env->ReleasePrimitiveArrayCritical(b2, b2_native, JNI_ABORT); } [[maybe_unused]] void @@ -157,26 +124,24 @@ Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCon Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitch( JNIEnv *env, jclass cls, jlong switch_value, jlongArray libfuzzer_case_values) { - jlong *case_values = - env->GetLongArrayElements(libfuzzer_case_values, nullptr); - if (env->ExceptionCheck()) env->ExceptionDescribe(); + auto *case_values = static_cast( + env->GetPrimitiveArrayCritical(libfuzzer_case_values, nullptr)); __sanitizer_cov_trace_switch(switch_value, reinterpret_cast(case_values)); - env->ReleaseLongArrayElements(libfuzzer_case_values, case_values, JNI_ABORT); - if (env->ExceptionCheck()) env->ExceptionDescribe(); + env->ReleasePrimitiveArrayCritical(libfuzzer_case_values, case_values, + JNI_ABORT); } [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitchWithPc( JNIEnv *env, jclass cls, jlong switch_value, jlongArray libfuzzer_case_values, jint id) { - jlong *case_values = - env->GetLongArrayElements(libfuzzer_case_values, nullptr); - if (env->ExceptionCheck()) env->ExceptionDescribe(); + auto *case_values = static_cast( + env->GetPrimitiveArrayCritical(libfuzzer_case_values, nullptr)); __sanitizer_cov_trace_switch_with_pc( idToPc(id), switch_value, reinterpret_cast(case_values)); - env->ReleaseLongArrayElements(libfuzzer_case_values, case_values, JNI_ABORT); - if (env->ExceptionCheck()) env->ExceptionDescribe(); + env->ReleasePrimitiveArrayCritical(libfuzzer_case_values, case_values, + JNI_ABORT); } [[maybe_unused]] void diff --git a/driver/sanitizer_symbols_for_tests.cpp b/driver/sanitizer_symbols_for_tests.cpp index 7d84feac..62623f5f 100644 --- a/driver/sanitizer_symbols_for_tests.cpp +++ b/driver/sanitizer_symbols_for_tests.cpp @@ -25,15 +25,11 @@ size_t __sanitizer_cov_get_observed_pcs(uintptr_t **pc_entries) { *pc_entries = new uintptr_t[0]; return 0; } -void __sanitizer_weak_hook_memcmp(void *caller_pc, const void *s1, - const void *s2, std::size_t n, int result) {} -void __sanitizer_weak_hook_strcmp(void *caller_pc, const char *s1, - const char *s2, int result) {} void __sanitizer_weak_hook_compare_bytes(void *caller_pc, const void *s1, const void *s2, std::size_t n1, std::size_t n2, int result) {} -void __sanitizer_weak_hook_strstr(void *caller_pc, const char *s1, - const char *s2, int result) {} +void __sanitizer_weak_hook_memmem(void *called_pc, const void *s1, size_t len1, + const void *s2, size_t len2, void *result) {} void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2) {} void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2) {} void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases) {} -- cgit v1.2.3 From 20f7badf3770f79862e582a81ddb0276c80ee4aa Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 1 Mar 2022 16:13:08 +0100 Subject: Allow hooks to disable themselves until the fuzz target is ready This is needed to prevent hooks on Java standard library classes from executing during Jazzer startup (e.g., to prevent regex hooks from tracking compares for ClassGraph's use of regexes). --- .../com/code_intelligence/jazzer/api/Jazzer.java | 23 ++++++++++++++++++++ .../code_intelligence/jazzer/instrumentor/Hooks.kt | 6 ++++-- .../jazzer/runtime/JazzerInternal.java | 13 ++++++++++- driver/fuzz_target_runner.cpp | 25 ++++++++++++---------- 4 files changed, 53 insertions(+), 14 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java index 75c1ae62..8f9024fc 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java @@ -39,6 +39,8 @@ final public class Jazzer { private static final Class JAZZER_INTERNAL; + private static final MethodHandle ON_FUZZ_TARGET_READY; + private static final MethodHandle TRACE_STRCMP; private static final MethodHandle TRACE_STRSTR; private static final MethodHandle TRACE_MEMCMP; @@ -58,6 +60,7 @@ final public class Jazzer { static { Class jazzerInternal = null; + MethodHandle onFuzzTargetReady = null; MethodHandle traceStrcmp = null; MethodHandle traceStrstr = null; MethodHandle traceMemcmp = null; @@ -75,6 +78,9 @@ final public class Jazzer { MethodHandle autofuzzConsumer5 = null; try { jazzerInternal = Class.forName("com.code_intelligence.jazzer.runtime.JazzerInternal"); + MethodType onFuzzTargetReadyType = MethodType.methodType(void.class, Runnable.class); + onFuzzTargetReady = MethodHandles.publicLookup().findStatic( + jazzerInternal, "registerOnFuzzTargetReadyCallback", onFuzzTargetReadyType); Class traceDataFlowNativeCallbacks = Class.forName("com.code_intelligence.jazzer.runtime.TraceDataFlowNativeCallbacks"); @@ -131,6 +137,7 @@ final public class Jazzer { System.exit(1); } JAZZER_INTERNAL = jazzerInternal; + ON_FUZZ_TARGET_READY = onFuzzTargetReady; TRACE_STRCMP = traceStrcmp; TRACE_STRSTR = traceStrstr; TRACE_MEMCMP = traceMemcmp; @@ -601,6 +608,22 @@ final public class Jazzer { } } + /** + * Register a callback to be executed right before the fuzz target is executed for the first time. + * + * This can be used to disable hooks until after Jazzer has been fully initializing, e.g. to + * prevent Jazzer internals from triggering hooks on Java standard library classes. + * + * @param callback the callback to execute + */ + public static void onFuzzTargetReady(Runnable callback) { + try { + ON_FUZZ_TARGET_READY.invokeExact(callback); + } catch (Throwable e) { + e.printStackTrace(); + } + } + private static int getLibFuzzerSeed() { // The Jazzer driver sets this property based on the value of libFuzzer's -seed command-line // option, which allows for fully reproducible fuzzing runs if set. If not running in the diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt index 676ec5c7..b94f8e19 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt @@ -59,8 +59,10 @@ data class Hooks( // Custom hook classes outside the agent jar can not be found by bootstrap // class loader, so use the system class loader as that will be the main application // class loader and can access jars on the classpath. - // Also, don't initialize the hook class to defer further class loading. - val hookClass = Class.forName(hookClassName, false, ClassLoader.getSystemClassLoader()) + // We let the static initializers of hook classes execute so that hooks can run + // code before the fuzz target class has been loaded (e.g., register themselves + // for the onFuzzTargetReady callback). + val hookClass = Class.forName(hookClassName, true, ClassLoader.getSystemClassLoader()) loadHooks(hookClass).also { println("INFO: Loaded ${it.size} hooks from $hookClassName") }.map { diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java index d79bdf61..6802bd75 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java @@ -14,7 +14,11 @@ package com.code_intelligence.jazzer.runtime; +import java.util.ArrayList; + final public class JazzerInternal { + private static final ArrayList ON_FUZZ_TARGET_READY_CALLBACKS = new ArrayList<>(); + // Accessed from native code. private static Throwable lastFinding; @@ -27,6 +31,13 @@ final public class JazzerInternal { throw new HardToCatchError(); } + public static void registerOnFuzzTargetReadyCallback(Runnable callback) { + ON_FUZZ_TARGET_READY_CALLBACKS.add(callback); + } + // Accessed from native code. - public static void onFuzzTargetReady(String fuzzTargetClass) {} + public static void onFuzzTargetReady(String fuzzTargetClass) { + ON_FUZZ_TARGET_READY_CALLBACKS.forEach(Runnable::run); + ON_FUZZ_TARGET_READY_CALLBACKS.clear(); + } } diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index a03be0b6..10d90521 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -164,6 +164,20 @@ FuzzTargetRunner::FuzzTargetRunner( last_finding_ = env.GetStaticFieldID(jazzer_, "lastFinding", "Ljava/lang/Throwable;"); + // Inform the agent about the fuzz target class. + // Important note: This has to be done *before* + // jvm.FindClass(FLAGS_target_class) so that hooks can enable themselves in + // time for the fuzz target's static initializer. + auto on_fuzz_target_ready = jvm.GetStaticMethodID( + jazzer_, "onFuzzTargetReady", "(Ljava/lang/String;)V", true); + jstring fuzz_target_class = env.NewStringUTF(FLAGS_target_class.c_str()); + env.CallStaticObjectMethod(jazzer_, on_fuzz_target_ready, fuzz_target_class); + if (env.ExceptionCheck()) { + env.ExceptionDescribe(); + return; + } + env.DeleteLocalRef(fuzz_target_class); + jclass_ = jvm.FindClass(FLAGS_target_class); // one of the following functions is required: // public static void fuzzerTestOneInput(byte[] input) @@ -189,17 +203,6 @@ FuzzTargetRunner::FuzzTargetRunner( exit(1); } - // Inform the agent about the fuzz target class. - auto on_fuzz_target_ready = jvm.GetStaticMethodID( - jazzer_, "onFuzzTargetReady", "(Ljava/lang/String;)V", true); - jstring fuzz_target_class = env.NewStringUTF(FLAGS_target_class.c_str()); - env.CallStaticObjectMethod(jazzer_, on_fuzz_target_ready, fuzz_target_class); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - return; - } - env.DeleteLocalRef(fuzz_target_class); - // check existence of optional methods for initialization and destruction fuzzer_initialize_ = jvm.GetStaticMethodID(jclass_, "fuzzerInitialize", "()V", false); -- cgit v1.2.3 From 6a668db62f85e7092cc45c1bc71e18d7975e580e Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 1 Mar 2022 16:13:08 +0100 Subject: Report compares for regex literals and character ranges --- .../code_intelligence/jazzer/runtime/BUILD.bazel | 3 + sanitizers/sanitizers.bzl | 1 + .../jazzer/sanitizers/BUILD.bazel | 13 + .../jazzer/sanitizers/RegexRoadblocks.java | 322 +++++++++++++++++++++ .../jazzer/sanitizers/utils/BUILD.bazel | 7 + .../jazzer/sanitizers/utils/ReflectionUtils.java | 62 ++++ sanitizers/src/test/java/com/example/BUILD.bazel | 11 + .../src/test/java/com/example/RegexRoadblocks.java | 89 ++++++ 8 files changed, 508 insertions(+) create mode 100644 sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexRoadblocks.java create mode 100644 sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils/ReflectionUtils.java create mode 100644 sanitizers/src/test/java/com/example/RegexRoadblocks.java 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 c3ed9856..e75692df 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 @@ -59,6 +59,9 @@ jni_headers( java_library( name = "unsafe_provider", srcs = ["UnsafeProvider.java"], + visibility = [ + "//sanitizers/src/main/java:__subpackages__", + ], ) kt_jvm_library( diff --git a/sanitizers/sanitizers.bzl b/sanitizers/sanitizers.bzl index 8812e3ee..17d6b272 100644 --- a/sanitizers/sanitizers.bzl +++ b/sanitizers/sanitizers.bzl @@ -22,6 +22,7 @@ _sanitizer_class_names = [ "OsCommandInjection", "ReflectiveCall", "RegexInjection", + "RegexRoadblocks", ] SANITIZER_CLASSES = [_sanitizer_package_prefix + class_name for class_name in _sanitizer_class_names] diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel index ec324526..b585ecb8 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel @@ -1,5 +1,15 @@ load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") +java_library( + name = "regex_roadblocks", + srcs = ["RegexRoadblocks.java"], + deps = [ + "//agent:jazzer_api_compile_only", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:unsafe_provider", + "//sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils:reflection_utils", + ], +) + kt_jvm_library( name = "sanitizers", srcs = [ @@ -13,6 +23,9 @@ kt_jvm_library( "Utils.kt", ], visibility = ["//sanitizers:__pkg__"], + runtime_deps = [ + ":regex_roadblocks", + ], deps = [ "//agent:jazzer_api_compile_only", "//sanitizers/src/main/java/jaz", diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexRoadblocks.java b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexRoadblocks.java new file mode 100644 index 00000000..f28c9991 --- /dev/null +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexRoadblocks.java @@ -0,0 +1,322 @@ +// Copyright 2022 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.sanitizers; + +import static com.code_intelligence.jazzer.sanitizers.utils.ReflectionUtils.INVALID_OFFSET; +import static com.code_intelligence.jazzer.sanitizers.utils.ReflectionUtils.field; +import static com.code_intelligence.jazzer.sanitizers.utils.ReflectionUtils.nestedClass; +import static com.code_intelligence.jazzer.sanitizers.utils.ReflectionUtils.offset; + +import com.code_intelligence.jazzer.api.HookType; +import com.code_intelligence.jazzer.api.Jazzer; +import com.code_intelligence.jazzer.api.MethodHook; +import com.code_intelligence.jazzer.runtime.UnsafeProvider; +import java.lang.invoke.MethodHandle; +import java.util.WeakHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import sun.misc.Unsafe; + +/** + * The hooks in this class extend the reach of Jazzer's string compare instrumentation to literals + * (both strings and characters) that are part of regular expression patterns. + * + * Internally, the Java standard library represents a compiled regular expression as a graph of + * instances of Pattern$Node instances, each of which represents a single unit of the full + * expression and provides a `match` function that takes a {@link Matcher}, a {@link CharSequence} + * to match against and an index into the sequence. With a hook on this method for every subclass of + * Pattern$Node, the contents of the node can be inspected and an appropriate string comparison + * between the relevant part of the input string and the literal string can be reported. + */ +public final class RegexRoadblocks { + // The number of characters preceding one that failed a character predicate to include in the + // reported string comparison. + private static final int CHARACTER_COMPARE_CONTEXT_LENGTH = 10; + + private static final Unsafe UNSAFE = UnsafeProvider.getUnsafe(); + private static final Class SLICE_NODE = nestedClass(Pattern.class, "SliceNode"); + private static final long SLICE_NODE_BUFFER_OFFSET = + offset(UNSAFE, field(SLICE_NODE, "buffer", int[].class)); + private static final Class CHAR_PREDICATE = nestedClass(Pattern.class, "CharPredicate"); + private static final Class CHAR_PROPERTY = nestedClass(Pattern.class, "CharProperty"); + private static final long CHAR_PROPERTY_PREDICATE_OFFSET = offset( + UNSAFE, field(CHAR_PROPERTY, "predicate", nestedClass(Pattern.class, "CharPredicate"))); + private static final Class BIT_CLASS = nestedClass(Pattern.class, "BitClass"); + private static final long BIT_CLASS_BITS_OFFSET = + offset(UNSAFE, field(BIT_CLASS, "bits", boolean[].class)); + + // Weakly map CharPredicate instances to characters that satisfy the predicate. Since + // CharPredicate instances are usually lambdas, we collect their solutions by hooking the + // functions constructing them rather than extracting the solutions via reflection. + // Note: Java 8 uses anonymous subclasses of CharProperty instead of lambdas implementing + // CharPredicate, hence CharProperty instances are used as keys instead in that case. + private static final ThreadLocal> PREDICATE_SOLUTIONS = + ThreadLocal.withInitial(WeakHashMap::new); + + // Do not act on instrumented regexes used by Jazzer internally, e.g. by ClassGraph. + private static boolean HOOK_DISABLED = true; + + static { + Jazzer.onFuzzTargetReady(() -> HOOK_DISABLED = UNSAFE == null); + } + + @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern$Node", + targetMethod = "match", + targetMethodDescriptor = "(Ljava/util/regex/Matcher;ILjava/lang/CharSequence;)Z", + additionalClassesToHook = + { + "java.util.regex.Matcher", + "java.util.regex.Pattern$BackRef", + "java.util.regex.Pattern$Behind", + "java.util.regex.Pattern$BehindS", + "java.util.regex.Pattern$BmpCharProperty", + "java.util.regex.Pattern$BmpCharPropertyGreedy", + "java.util.regex.Pattern$BnM", + "java.util.regex.Pattern$BnMS", + "java.util.regex.Pattern$Bound", + "java.util.regex.Pattern$Branch", + "java.util.regex.Pattern$BranchConn", + "java.util.regex.Pattern$CharProperty", + "java.util.regex.Pattern$CharPropertyGreedy", + "java.util.regex.Pattern$CIBackRef", + "java.util.regex.Pattern$Caret", + "java.util.regex.Pattern$Curly", + "java.util.regex.Pattern$Conditional", + "java.util.regex.Pattern$First", + "java.util.regex.Pattern$GraphemeBound", + "java.util.regex.Pattern$GroupCurly", + "java.util.regex.Pattern$GroupHead", + "java.util.regex.Pattern$GroupRef", + "java.util.regex.Pattern$LastMatch", + "java.util.regex.Pattern$LazyLoop", + "java.util.regex.Pattern$LineEnding", + "java.util.regex.Pattern$Loop", + "java.util.regex.Pattern$Neg", + "java.util.regex.Pattern$NFCCharProperty", + "java.util.regex.Pattern$NotBehind", + "java.util.regex.Pattern$NotBehindS", + "java.util.regex.Pattern$Pos", + "java.util.regex.Pattern$Ques", + "java.util.regex.Pattern$Slice", + "java.util.regex.Pattern$SliceI", + "java.util.regex.Pattern$SliceIS", + "java.util.regex.Pattern$SliceS", + "java.util.regex.Pattern$SliceU", + "java.util.regex.Pattern$Start", + "java.util.regex.Pattern$StartS", + "java.util.regex.Pattern$UnixCaret", + "java.util.regex.Pattern$UnixDollar", + "java.util.regex.Pattern$XGrapheme", + }) + public static void + nodeMatchHook(MethodHandle method, Object node, Object[] args, int hookId, Boolean matched) { + if (HOOK_DISABLED || matched || node == null) + return; + Matcher matcher = (Matcher) args[0]; + if (matcher == null) + return; + int i = (int) args[1]; + CharSequence seq = (CharSequence) args[2]; + if (seq == null) + return; + + if (SLICE_NODE != null && SLICE_NODE.isInstance(node)) { + // The node encodes a match against a fixed string literal. Extract the literal and report a + // comparison between it and the subsequence of seq starting at i. + if (SLICE_NODE_BUFFER_OFFSET == INVALID_OFFSET) + return; + int currentLength = limitedLength(matcher.regionEnd() - i); + String current = seq.subSequence(i, i + currentLength).toString(); + + // All the subclasses of SliceNode store the literal in an int[], which we have to truncate to + // a char[]. + int[] buffer = (int[]) UNSAFE.getObject(node, SLICE_NODE_BUFFER_OFFSET); + char[] charBuffer = new char[limitedLength(buffer.length)]; + for (int j = 0; j < charBuffer.length; j++) { + charBuffer[j] = (char) buffer[j]; + } + String target = new String(charBuffer); + + Jazzer.guideTowardsEquality(current, target, perRegexId(hookId, matcher)); + } else if (CHAR_PROPERTY != null && CHAR_PROPERTY.isInstance(node)) { + // The node encodes a match against a class of characters, which may be hard to guess unicode + // characters. We rely on further hooks to track the relation between these nodes and + // characters satisfying their match function since the nodes themselves encode this + // information in lambdas, which are difficult to dissect via reflection. If we know a + // matching character, report a one-character (plus context) string comparison. + Object solutionKey; + if (CHAR_PROPERTY_PREDICATE_OFFSET == INVALID_OFFSET) { + if (CHAR_PREDICATE == null) { + // We are likely running against JDK 8, which directly construct subclasses of + // CharProperty rather than using lambdas implementing CharPredicate. + solutionKey = node; + } else { + return; + } + } else { + solutionKey = UNSAFE.getObject(node, CHAR_PROPERTY_PREDICATE_OFFSET); + } + if (solutionKey == null) + return; + Character solution = predicateSolution(solutionKey); + if (solution == null) + return; + // We report a string comparison rather than an integer comparison for two reasons: + // 1. If the characters are four byte codepoints, they will be coded on six bytes (a surrogate + // pair) in CESU-8, which is the encoding assumed for the fuzzer input, whereas ASCII + // characters will be coded on a single byte. By using the string compare hook, we do not + // have to worry about the encoding at this point. + // 2. The same character can appear multiple times in both the pattern and the matched string, + // which makes it harder for the fuzzer to determine the correct position to mutate the + // current character into the matching character. By providing a short section of the + // input string preceding the incorrect character, we increase the chance of a hit. + String context = + seq.subSequence(Math.max(0, i - CHARACTER_COMPARE_CONTEXT_LENGTH), i).toString(); + String current = seq.subSequence(i, Math.min(i + 1, matcher.regionEnd())).toString(); + String target = Character.toString(solution); + Jazzer.guideTowardsEquality(context + current, context + target, perRegexId(hookId, matcher)); + } + } + + // This and all following hooks track the relation between a CharPredicate or CharProperty + // instance and a character that matches it. We use an after hook on the factory methods so that + // we have access to the parameters and the created instance at the same time. + @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern", + targetMethod = "Single", + targetMethodDescriptor = "(I)Ljava/util/regex/Pattern$BmpCharPredicate;", + additionalClassesToHook = {"java.util.regex.Pattern"}) + @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern", + targetMethod = "SingleI", + targetMethodDescriptor = "(II)Ljava/util/regex/Pattern$CharPredicate;", + additionalClassesToHook = {"java.util.regex.Pattern"}) + @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern", + targetMethod = "SingleS", + targetMethodDescriptor = "(I)Ljava/util/regex/Pattern$CharPredicate;", + additionalClassesToHook = {"java.util.regex.Pattern"}) + @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern", + targetMethod = "SingleU", + targetMethodDescriptor = "(I)Ljava/util/regex/Pattern$CharPredicate;", + additionalClassesToHook = {"java.util.regex.Pattern"}) + public static void + singleHook(MethodHandle method, Object node, Object[] args, int hookId, Object predicate) { + if (HOOK_DISABLED || predicate == null) + return; + PREDICATE_SOLUTIONS.get().put(predicate, (char) (int) args[0]); + } + + // Java 8 uses classes extending CharProperty instead of lambdas implementing CharPredicate to + // match single characters, so also hook those. + @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern$Single", + targetMethod = "", additionalClassesToHook = {"java.util.regex.Pattern"}) + @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern$SingleI", + targetMethod = "", additionalClassesToHook = {"java.util.regex.Pattern"}) + @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern$SingleS", + targetMethod = "", additionalClassesToHook = {"java.util.regex.Pattern"}) + @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern$SingleU", + targetMethod = "", additionalClassesToHook = {"java.util.regex.Pattern"}) + public static void + java8SingleHook( + MethodHandle method, Object property, Object[] args, int hookId, Object alwaysNull) { + if (HOOK_DISABLED || property == null) + return; + PREDICATE_SOLUTIONS.get().put(property, (char) (int) args[0]); + } + + @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern", + targetMethod = "Range", + targetMethodDescriptor = "(II)Ljava/util/regex/Pattern$CharPredicate;", + additionalClassesToHook = {"java.util.regex.Pattern"}) + @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern", + targetMethod = "CIRange", + targetMethodDescriptor = "(II)Ljava/util/regex/Pattern$CharPredicate;", + additionalClassesToHook = {"java.util.regex.Pattern"}) + @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern", + targetMethod = "CIRangeU", + targetMethodDescriptor = "(II)Ljava/util/regex/Pattern$CharPredicate;", + additionalClassesToHook = {"java.util.regex.Pattern"}) + // Java 8 uses anonymous classes extending CharProperty instead of lambdas implementing + // CharPredicate to match single characters, so also hook those. + @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern", + targetMethod = "rangeFor", + targetMethodDescriptor = "(II)Ljava/util/regex/Pattern$CharProperty;", + additionalClassesToHook = {"java.util.regex.Pattern"}) + @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern", + targetMethod = "caseInsensitiveRangeFor", + targetMethodDescriptor = "(II)Ljava/util/regex/Pattern$CharProperty;", + additionalClassesToHook = {"java.util.regex.Pattern"}) + public static void + rangeHook(MethodHandle method, Object node, Object[] args, int hookId, Object predicate) { + if (HOOK_DISABLED || predicate == null) + return; + PREDICATE_SOLUTIONS.get().put(predicate, (char) (int) args[0]); + } + + @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern$CharPredicate", + targetMethod = "union", + targetMethodDescriptor = + "(Ljava/util/regex/Pattern$CharPredicate;)Ljava/util/regex/Pattern$CharPredicate;", + additionalClassesToHook = {"java.util.regex.Pattern"}) + // Java 8 uses anonymous classes extending CharProperty instead of lambdas implementing + // CharPredicate to match single characters, so also hook union for those. Even though the classes + // of the parameters will be different, the actual implementation of the hook is the same in this + // case. + @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Pattern", + targetMethod = "union", + targetMethodDescriptor = + "(Ljava/util/regex/Pattern$CharProperty;Ljava/util/regex/Pattern$CharProperty;)Ljava/util/regex/Pattern$CharProperty;", + additionalClassesToHook = {"java.util.regex.Pattern"}) + public static void + unionHook( + MethodHandle method, Object thisObject, Object[] args, int hookId, Object unionPredicate) { + if (HOOK_DISABLED || unionPredicate == null) + return; + Character solution = predicateSolution(thisObject); + if (solution == null) + solution = predicateSolution(args[0]); + if (solution == null) + return; + PREDICATE_SOLUTIONS.get().put(unionPredicate, solution); + } + + private static Character predicateSolution(Object charPredicate) { + return PREDICATE_SOLUTIONS.get().computeIfAbsent(charPredicate, unused -> { + if (BIT_CLASS != null && BIT_CLASS.isInstance(charPredicate)) { + // BitClass instances have an empty bits array at construction time, so we scan their + // constants lazily when needed. + boolean[] bits = (boolean[]) UNSAFE.getObject(charPredicate, BIT_CLASS_BITS_OFFSET); + for (int i = 0; i < bits.length; i++) { + if (bits[i]) { + PREDICATE_SOLUTIONS.get().put(charPredicate, (char) i); + return (char) i; + } + } + } + return null; + }); + } + + // Limits a length to the maximum length libFuzzer will read up to in a callback. + private static int limitedLength(int length) { + return Math.min(length, 64); + } + + // hookId only takes one distinct value per Node subclass. In order to get different regex matches + // to be tracked similar to different instances of string compares, we mix in the hash of the + // underlying pattern. We expect patterns to be static almost always, so that this should not fill + // up the value profile map too quickly. + private static int perRegexId(int hookId, Matcher matcher) { + return hookId ^ matcher.pattern().toString().hashCode(); + } +} diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils/BUILD.bazel b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils/BUILD.bazel index e69de29b..c7258447 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils/BUILD.bazel +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils/BUILD.bazel @@ -0,0 +1,7 @@ +java_library( + name = "reflection_utils", + srcs = ["ReflectionUtils.java"], + visibility = [ + "//sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers:__pkg__", + ], +) diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils/ReflectionUtils.java b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils/ReflectionUtils.java new file mode 100644 index 00000000..fd6ac72f --- /dev/null +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/utils/ReflectionUtils.java @@ -0,0 +1,62 @@ +// Copyright 2022 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.sanitizers.utils; + +import java.lang.reflect.Field; +import sun.misc.Unsafe; + +public final class ReflectionUtils { + public static final long INVALID_OFFSET = Long.MIN_VALUE; + + private static final boolean JAZZER_REFLECTION_DEBUG = + "1".equals(System.getenv("JAZZER_REFLECTION_DEBUG")); + + public static Class clazz(String className) { + try { + return Class.forName(className); + } catch (ClassNotFoundException e) { + if (JAZZER_REFLECTION_DEBUG) + e.printStackTrace(); + return null; + } + } + + public static Class nestedClass(Class parentClass, String nestedClassName) { + return clazz(parentClass.getName() + "$" + nestedClassName); + } + + public static Field field(Class clazz, String name, Class type) { + if (clazz == null) + return null; + try { + Field field = clazz.getDeclaredField(name); + if (!field.getType().equals(type)) { + throw new NoSuchFieldException( + "Expected " + name + " to be of type " + type + " (is: " + field.getType() + ")"); + } + return field; + } catch (NoSuchFieldException e) { + if (JAZZER_REFLECTION_DEBUG) + e.printStackTrace(); + return null; + } + } + + public static long offset(Unsafe unsafe, Field field) { + if (unsafe == null || field == null) + return INVALID_OFFSET; + return unsafe.objectFieldOffset(field); + } +} diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index 4c5f4d2b..e94898a4 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -97,3 +97,14 @@ java_fuzz_target_test( ], target_class = "com.example.ClassLoaderLoadClass", ) + +java_fuzz_target_test( + name = "RegexRoadblocks", + srcs = ["RegexRoadblocks.java"], + fuzzer_args = [ + # Limit the number of runs to verify that the regex roadblocks are + # cleared quickly. + "-runs=15000", + ], + target_class = "com.example.RegexRoadblocks", +) diff --git a/sanitizers/src/test/java/com/example/RegexRoadblocks.java b/sanitizers/src/test/java/com/example/RegexRoadblocks.java new file mode 100644 index 00000000..21986e3d --- /dev/null +++ b/sanitizers/src/test/java/com/example/RegexRoadblocks.java @@ -0,0 +1,89 @@ +// Copyright 2022 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.example; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow; +import java.util.regex.Pattern; + +public class RegexRoadblocks { + // We accept arbitrary suffixes but not prefixes for the following reasons: + // 1. The fuzzer will take much longer to match the exact length of the input than to satisfy the + // compare checks, which is what we really want to test. + // 2. Accepting arbitrary prefixes could lead to tests passing purely due to ToC entries being + // emitted in arbitrary positions, but we want to ensure that compares are correctly reported + // including position hints. + private static final Pattern LITERAL = Pattern.compile("foobarbaz.*"); + private static final Pattern QUOTED_LITERAL = Pattern.compile(Pattern.quote("jazzer_is_cool.*")); + private static final Pattern CASE_INSENSITIVE_LITERAL = + Pattern.compile("JaZzER!.*", Pattern.CASE_INSENSITIVE); + private static final Pattern GROUP = Pattern.compile("(always).*"); + private static final Pattern ALTERNATIVE = Pattern.compile("(to_be|not_to_be).*"); + private static final Pattern SINGLE_LATIN1_CHAR_PROPERTY = Pattern.compile("[€].*"); + private static final Pattern MULTIPLE_LATIN1_CHAR_PROPERTY = Pattern.compile("[ẞÄ].*"); + private static final Pattern RANGE_LATIN1_CHAR_PROPERTY = Pattern.compile("[¢-¥].*"); + + private static int run = 0; + + private static boolean matchedLiteral = false; + private static boolean matchedQuotedLiteral = false; + private static boolean matchedCaseInsensitiveLiteral = false; + private static boolean matchedGroup = false; + private static boolean matchedAlternative = false; + private static boolean matchedSingleLatin1CharProperty = false; + private static boolean matchedMultipleLatin1CharProperty = false; + private static boolean matchedRangeLatin1CharProperty = false; + + public static void fuzzerTestOneInput(FuzzedDataProvider data) { + run++; + String input = data.consumeRemainingAsString(); + + if (!matchedLiteral && LITERAL.matcher(input).matches()) { + System.out.println("Cleared LITERAL"); + matchedLiteral = true; + } else if (!matchedQuotedLiteral && QUOTED_LITERAL.matcher(input).matches()) { + System.out.println("Cleared QUOTED_LITERAL"); + matchedQuotedLiteral = true; + } else if (!matchedCaseInsensitiveLiteral + && CASE_INSENSITIVE_LITERAL.matcher(input).matches()) { + System.out.println("Cleared CASE_INSENSITIVE_LITERAL"); + matchedCaseInsensitiveLiteral = true; + } else if (!matchedGroup && GROUP.matcher(input).matches()) { + System.out.println("Cleared GROUP"); + matchedGroup = true; + } else if (!matchedAlternative && ALTERNATIVE.matcher(input).matches()) { + System.out.println("Cleared ALTERNATIVE"); + matchedAlternative = true; + } else if (!matchedSingleLatin1CharProperty + && SINGLE_LATIN1_CHAR_PROPERTY.matcher(input).matches()) { + System.out.println("Cleared SINGLE_LATIN1_CHAR_PROPERTY"); + matchedSingleLatin1CharProperty = true; + } else if (!matchedMultipleLatin1CharProperty + && MULTIPLE_LATIN1_CHAR_PROPERTY.matcher(input).matches()) { + System.out.println("Cleared MULTIPLE_LATIN1_CHAR_PROPERTY"); + matchedMultipleLatin1CharProperty = true; + } else if (!matchedRangeLatin1CharProperty + && RANGE_LATIN1_CHAR_PROPERTY.matcher(input).matches()) { + System.out.println("Cleared RANGE_LATIN1_CHAR_PROPERTY"); + matchedRangeLatin1CharProperty = true; + } + + if (matchedLiteral && matchedQuotedLiteral && matchedCaseInsensitiveLiteral && matchedGroup + && matchedAlternative && matchedSingleLatin1CharProperty + && matchedMultipleLatin1CharProperty && matchedRangeLatin1CharProperty) { + throw new FuzzerSecurityIssueLow("Fuzzer matched all regexes in " + run + " runs"); + } + } +} -- cgit v1.2.3 From bbe6c8f95df5a7f621e02e491ffb750ed1ed643c Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 4 Mar 2022 07:46:29 +0100 Subject: Inherit Jazzer debug env variables in tests --- .bazelrc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.bazelrc b/.bazelrc index ea127918..62b1b956 100644 --- a/.bazelrc +++ b/.bazelrc @@ -33,6 +33,10 @@ run:windows --noincompatible_strict_action_env # Since the toolchain is conditional on OS and architecture, set it on the particular GitHub Action. build:toolchain --//third_party:toolchain +# Forward debug variables to tests +test --test_env=JAZZER_AUTOFUZZ_DEBUG +test --test_env=JAZZER_REFLECTION_DEBUG + # CI tests (not using the toolchain to test OSS-Fuzz & local compatibility) test:ci --test_env=JAZZER_CI=1 build:ci --bes_results_url=https://app.buildbuddy.io/invocation/ -- cgit v1.2.3 From 1d9cc3f76a1478372c2af40d1f46e9099355b678 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 4 Mar 2022 18:47:36 +0100 Subject: Sort hooks deterministically --- .../src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt index b94f8e19..66a21ee7 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hooks.kt @@ -17,6 +17,7 @@ package com.code_intelligence.jazzer.instrumentor import com.code_intelligence.jazzer.api.MethodHook import com.code_intelligence.jazzer.api.MethodHooks import com.code_intelligence.jazzer.utils.ClassNameGlobber +import com.code_intelligence.jazzer.utils.descriptor import io.github.classgraph.ClassGraph import io.github.classgraph.ScanResult import java.lang.reflect.Method @@ -76,7 +77,7 @@ data class Hooks( private fun loadHooks(hookClass: Class<*>): List { val hooks = mutableListOf() - for (method in hookClass.methods) { + for (method in hookClass.methods.sortedBy { it.descriptor }) { method.getAnnotation(MethodHook::class.java)?.let { hooks.addAll(verifyAndGetHooks(method, it)) } @@ -106,7 +107,7 @@ data class Hooks( targetClassInfo.isAbstract -> scanResult.getSubclasses(targetClassName) else -> emptyList() } - return listOf(targetClassName) + additionalTargetClasses.map { it.name } + return (listOf(targetClassName) + additionalTargetClasses.map { it.name }).sorted() } } } -- cgit v1.2.3 From bd4e75f7892478affad0c298dd20731b4483b855 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 7 Mar 2022 18:43:57 +0100 Subject: Make crash reproducer verification more reproducible * Sort crash reproducers by last modification time and only verify the most recent one, which corresponds to the last finding when using --keep_going. * Switch to a list of expected findings as the exact finding type can differ not only between different platforms, but also between different JDK versions on a single platform. --- bazel/fuzz_target.bzl | 9 +++-- .../jazzer/tools/FuzzTargetTestWrapper.java | 31 ++++++++++------ examples/BUILD.bazel | 43 +++++++++++----------- sanitizers/src/test/java/com/example/BUILD.bazel | 4 +- tests/BUILD.bazel | 8 ++-- 5 files changed, 52 insertions(+), 43 deletions(-) diff --git a/bazel/fuzz_target.bzl b/bazel/fuzz_target.bzl index 78ee3dc9..0ae67b53 100644 --- a/bazel/fuzz_target.bzl +++ b/bazel/fuzz_target.bzl @@ -29,7 +29,7 @@ def java_fuzz_target_test( verify_crash_input = True, verify_crash_reproducer = True, # Default is that the reproducer does not throw any exception. - expected_finding = "none", + expected_findings = [], **kwargs): target_name = name + "_target" deploy_manifest_lines = [] @@ -63,8 +63,6 @@ def java_fuzz_target_test( else: fail("Invalid sanitizer: " + sanitizer) - if type(expected_finding) == type(""): - expected_finding = [expected_finding] native.java_test( name = name, runtime_deps = [ @@ -85,7 +83,10 @@ def java_fuzz_target_test( "$(rootpath :%s_deploy.jar)" % target_name, str(verify_crash_input), str(verify_crash_reproducer), - ] + expected_finding + additional_args + fuzzer_args, + # args are shell tokenized and thus quotes are required in the case where + # expected_findings is empty. + "'" + ",".join(expected_findings) + "'", + ] + additional_args + fuzzer_args, data = [ ":%s_deploy.jar" % target_name, "//agent:jazzer_agent_deploy.jar", diff --git a/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java b/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java index 44a58e17..ecbd6e19 100644 --- a/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java +++ b/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java @@ -24,8 +24,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; +import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.tools.JavaCompiler; @@ -44,7 +46,7 @@ public class FuzzTargetTestWrapper { String jarActualPath; boolean verifyCrashInput; boolean verifyCrashReproducer; - String expectedFinding; + Set expectedFindings; try { runfiles = Runfiles.create(); driverActualPath = lookUpRunfile(runfiles, args[0]); @@ -52,7 +54,8 @@ public class FuzzTargetTestWrapper { jarActualPath = lookUpRunfile(runfiles, args[2]); verifyCrashInput = Boolean.parseBoolean(args[3]); verifyCrashReproducer = Boolean.parseBoolean(args[4]); - expectedFinding = args[5]; + expectedFindings = + Arrays.stream(args[5].split(",")).filter(s -> !s.isEmpty()).collect(Collectors.toSet()); } catch (IOException | ArrayIndexOutOfBoundsException e) { e.printStackTrace(); System.exit(1); @@ -113,7 +116,7 @@ public class FuzzTargetTestWrapper { if (JAZZER_CI && verifyCrashReproducer) { try { verifyCrashReproducer( - outputDir, driverActualPath, apiActualPath, jarActualPath, expectedFinding); + outputDir, driverActualPath, apiActualPath, jarActualPath, expectedFindings); } catch (Exception e) { e.printStackTrace(); System.exit(6); @@ -155,16 +158,18 @@ public class FuzzTargetTestWrapper { } private static void verifyCrashReproducer(String outputDir, String driver, String api, String jar, - String expectedFinding) throws Exception { + Set expectedFindings) throws Exception { File source = Files.list(Paths.get(outputDir)) .filter(f -> f.toFile().getName().endsWith(".java")) - .findFirst() + // Verify the crash reproducer that was created last in order to reproduce the last + // crash when using --keep_going. + .max(Comparator.comparingLong(p -> p.toFile().lastModified())) .map(Path::toFile) .orElseThrow( () -> new IllegalStateException("Could not find crash reproducer in " + outputDir)); String crashReproducer = compile(source, driver, api, jar); - execute(crashReproducer, outputDir, expectedFinding); + execute(crashReproducer, outputDir, expectedFindings); } private static String compile(File source, String driver, String api, String jar) @@ -185,7 +190,7 @@ public class FuzzTargetTestWrapper { } } - private static void execute(String classFile, String outputDir, String expectedFinding) + private static void execute(String classFile, String outputDir, Set expectedFindings) throws IOException, ReflectiveOperationException { try { System.out.printf("Execute crash reproducer %s%n", classFile); @@ -195,19 +200,21 @@ public class FuzzTargetTestWrapper { Method main = crashReproducerClass.getMethod("main", String[].class); System.setProperty("jazzer.is_reproducer", "true"); main.invoke(null, new Object[] {new String[] {}}); - if (!expectedFinding.equals("none")) { - throw new IllegalStateException("Crash not reproduced by " + classFile); + if (!expectedFindings.isEmpty()) { + throw new IllegalStateException("Expected crash with any of " + + String.join(", ", expectedFindings) + " not reproduced by " + classFile); } } catch (InvocationTargetException e) { // expect the invocation to fail with the prescribed finding Throwable finding = e.getCause(); - if (expectedFinding.equals("none")) { + if (expectedFindings.isEmpty()) { throw new IllegalStateException("Did not expect " + classFile + " to crash", finding); - } else if (finding.getClass().getName().equals(expectedFinding)) { + } else if (expectedFindings.contains(finding.getClass().getName())) { System.out.printf("Reproduced exception \"%s\"%n", finding.getMessage()); } else { throw new IllegalStateException( - classFile + " did not crash with " + expectedFinding, finding); + classFile + " did not crash with any of " + String.join(", ", expectedFindings), + finding); } } } diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index 8ba01042..a9cd04e4 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -5,7 +5,7 @@ load("//bazel:fuzz_target.bzl", "java_fuzz_target_test") java_fuzz_target_test( name = "Autofuzz", - expected_finding = "java.lang.ArrayIndexOutOfBoundsException", + expected_findings = ["java.lang.ArrayIndexOutOfBoundsException"], fuzzer_args = [ "--autofuzz=com.google.json.JsonSanitizer::sanitize", # Exit after the first finding for testing purposes. @@ -81,7 +81,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/ExampleValueProfileFuzzer.java", ], - expected_finding = "com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow", + expected_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow"], # Comment out the next line to keep the fuzzer running indefinitely. fuzzer_args = ["-use_value_profile=1"], target_class = "com.example.ExampleValueProfileFuzzer", @@ -92,7 +92,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/MazeFuzzer.java", ], - expected_finding = "com.example.MazeFuzzer$$TreasureFoundException", + expected_findings = ["com.example.MazeFuzzer$$TreasureFoundException"], fuzzer_args = ["-use_value_profile=1"], target_class = "com.example.MazeFuzzer", ) @@ -102,7 +102,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/ExampleOutOfMemoryFuzzer.java", ], - expected_finding = "java.lang.OutOfMemoryError", + expected_findings = ["java.lang.OutOfMemoryError"], fuzzer_args = ["--jvm_args=-Xmx512m"], target_class = "com.example.ExampleOutOfMemoryFuzzer", ) @@ -112,7 +112,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/ExampleStackOverflowFuzzer.java", ], - expected_finding = "java.lang.StackOverflowError", + expected_findings = ["java.lang.StackOverflowError"], target_class = "com.example.ExampleStackOverflowFuzzer", # Crashes with a segfault before any stack trace printing is reached. target_compatible_with = SKIP_ON_MACOS, @@ -146,7 +146,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/JpegImageParserFuzzer.java", ], - expected_finding = "java.lang.NegativeArraySizeException", + expected_findings = ["java.lang.NegativeArraySizeException"], fuzzer_args = [ "-fork=3", ], @@ -163,11 +163,11 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/GifImageParserFuzzer.java", ], - expected_finding = select({ - "@platforms//os:macos": ["java.lang.IllegalArgumentException"], - "@platforms//os:windows": ["java.lang.ArrayIndexOutOfBoundsException"], - "//conditions:default": ["java.lang.OutOfMemoryError"], - }), + expected_findings = [ + "java.lang.ArrayIndexOutOfBoundsException", + "java.lang.IllegalArgumentException", + "java.lang.OutOfMemoryError", + ], target_class = "com.example.GifImageParserFuzzer", deps = [ "@maven//:org_apache_commons_commons_imaging", @@ -191,7 +191,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/JsonSanitizerCrashFuzzer.java", ], - expected_finding = "java.lang.IndexOutOfBoundsException", + expected_findings = ["java.lang.IndexOutOfBoundsException"], target_class = "com.example.JsonSanitizerCrashFuzzer", deps = [ "@maven//:com_mikesamuel_json_sanitizer", @@ -203,7 +203,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/JsonSanitizerDenylistFuzzer.java", ], - expected_finding = "java.lang.AssertionError", + expected_findings = ["java.lang.AssertionError"], target_class = "com.example.JsonSanitizerDenylistFuzzer", deps = [ "@maven//:com_mikesamuel_json_sanitizer", @@ -244,7 +244,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/JsonSanitizerIdempotenceFuzzer.java", ], - expected_finding = "java.lang.AssertionError", + expected_findings = ["java.lang.AssertionError"], target_class = "com.example.JsonSanitizerIdempotenceFuzzer", deps = [ "@maven//:com_mikesamuel_json_sanitizer", @@ -256,7 +256,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/JsonSanitizerValidJsonFuzzer.java", ], - expected_finding = "com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow", + expected_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow"], target_class = "com.example.JsonSanitizerValidJsonFuzzer", deps = [ "@maven//:com_google_code_gson_gson", @@ -269,7 +269,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/JacksonCborFuzzer.java", ], - expected_finding = "java.lang.NullPointerException", + expected_findings = ["java.lang.NullPointerException"], target_class = "com.example.JacksonCborFuzzer", deps = [ "@maven//:com_fasterxml_jackson_core_jackson_core", @@ -283,7 +283,7 @@ java_fuzz_target_test( srcs = [ "src/main/java/com/example/FastJsonFuzzer.java", ], - expected_finding = "java.lang.NumberFormatException", + expected_findings = ["java.lang.NumberFormatException"], target_class = "com.example.FastJsonFuzzer", deps = [ "@maven//:com_alibaba_fastjson", @@ -303,10 +303,11 @@ kt_jvm_library( java_fuzz_target_test( name = "KlaxonFuzzer", - expected_finding = select({ - "@platforms//os:windows": ["java.lang.ClassCastException"], - "//conditions:default": ["java.lang.IllegalStateException"], - }), + expected_findings = [ + "java.lang.ClassCastException", + "java.lang.IllegalStateException", + "java.lang.NumberFormatException", + ], fuzzer_args = [ "--keep_going=7", ], diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index e94898a4..e90faa2f 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -55,7 +55,7 @@ java_fuzz_target_test( "ldap/MockInitialContextFactory.java", "ldap/MockLdapContext.java", ], - expected_finding = "javax.naming.directory.InvalidSearchFilterException", + expected_findings = ["javax.naming.directory.InvalidSearchFilterException"], target_class = "com.example.LdapSearchInjection", deps = [ "@maven//:com_unboundid_unboundid_ldapsdk", @@ -69,7 +69,7 @@ java_fuzz_target_test( "ldap/MockInitialContextFactory.java", "ldap/MockLdapContext.java", ], - expected_finding = "javax.naming.NamingException", + expected_findings = ["javax.naming.NamingException"], target_class = "com.example.LdapDnInjection", deps = [ "@maven//:com_unboundid_unboundid_ldapsdk", diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 15348bc7..4c70e835 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -7,7 +7,7 @@ java_fuzz_target_test( "src/test/java/com/example/LongStringFuzzer.java", ], data = ["src/test/java/com/example/LongStringFuzzerInput"], - expected_finding = "com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow", + expected_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow"], fuzzer_args = [ "$(rootpath src/test/java/com/example/LongStringFuzzerInput)", ], @@ -17,7 +17,7 @@ java_fuzz_target_test( java_fuzz_target_test( name = "JpegImageParserAutofuzz", - expected_finding = "java.lang.NegativeArraySizeException", + expected_findings = ["java.lang.NegativeArraySizeException"], fuzzer_args = [ "--autofuzz=org.apache.commons.imaging.formats.jpeg.JpegImageParser::getBufferedImage", # Exit after the first finding for testing purposes. @@ -38,7 +38,7 @@ java_fuzz_target_test( java_fuzz_target_test( name = "AutofuzzWithoutCoverage", - expected_finding = "java.lang.NullPointerException", + expected_findings = ["java.lang.NullPointerException"], fuzzer_args = [ # Autofuzz a method that triggers no coverage instrumentation (the Java standard library is # excluded by default). @@ -56,7 +56,7 @@ java_fuzz_target_test( env = { "JAVA_OPTS": "-Dfoo=not_foo -Djava_opts=1", }, - expected_finding = "com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow", + expected_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow"], fuzzer_args = [ "-fork=3", "--additional_jvm_args=-Dbaz=baz", -- cgit v1.2.3 From 627a0433e6ad15c033e546c52fbf64a16f6487b8 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 8 Mar 2022 19:08:26 +0100 Subject: Do not fail without custom hooks --- agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index a9325702..dd9bccb7 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -87,7 +87,7 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { val manifestCustomHookNames = ManifestUtils.combineManifestValues(ManifestUtils.HOOK_CLASSES).flatMap { it.split(':') - } + }.filter { it.isNotBlank() } val customHookNames = manifestCustomHookNames + (argumentMap["custom_hooks"] ?: emptyList()) val classNameGlobber = ClassNameGlobber( argumentMap["instrumentation_includes"] ?: emptyList(), -- cgit v1.2.3 From 03693ea2d2eb48ea389ebb4da59d5cc9ee1b3e2f Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 8 Mar 2022 19:08:50 +0100 Subject: Dump both instrumented and original bytecode with --dump_classes_dir --- .../code_intelligence/jazzer/agent/RuntimeInstrumentor.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt index f29dd7bd..c069b9e8 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt @@ -69,15 +69,20 @@ internal class RuntimeInstrumentor( }.also { instrumentedByteCode -> // Only dump classes that were instrumented. if (instrumentedByteCode != null && dumpClassesDir != null) { - val relativePath = "$internalClassName.class" - val absolutePath = dumpClassesDir.resolve(relativePath) - val dumpFile = absolutePath.toFile() - dumpFile.parentFile.mkdirs() - dumpFile.writeBytes(instrumentedByteCode) + dumpToClassFile(internalClassName, instrumentedByteCode) + dumpToClassFile(internalClassName, classfileBuffer, basenameSuffix = ".original") } } } + private fun dumpToClassFile(internalClassName: String, bytecode: ByteArray, basenameSuffix: String = "") { + val relativePath = "$internalClassName$basenameSuffix.class" + val absolutePath = dumpClassesDir!!.resolve(relativePath) + val dumpFile = absolutePath.toFile() + dumpFile.parentFile.mkdirs() + dumpFile.writeBytes(bytecode) + } + override fun transform( module: Module?, loader: ClassLoader?, -- cgit v1.2.3 From e60b9966cbfb1ed32d9988e5d0365a8043c395a1 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 8 Mar 2022 19:09:14 +0100 Subject: Fix incorrect return value type in test hook --- tests/BUILD.bazel | 1 + tests/src/test/java/com/example/HookDependenciesFuzzer.java | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 4c70e835..d12f4986 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -32,6 +32,7 @@ java_fuzz_target_test( java_fuzz_target_test( name = "HookDependenciesFuzzer", srcs = ["src/test/java/com/example/HookDependenciesFuzzer.java"], + env = {"JAVA_OPTS": "-Xverify:all"}, hook_classes = ["com.example.HookDependenciesFuzzer"], target_class = "com.example.HookDependenciesFuzzer", ) diff --git a/tests/src/test/java/com/example/HookDependenciesFuzzer.java b/tests/src/test/java/com/example/HookDependenciesFuzzer.java index 9962045c..88627f4c 100644 --- a/tests/src/test/java/com/example/HookDependenciesFuzzer.java +++ b/tests/src/test/java/com/example/HookDependenciesFuzzer.java @@ -39,10 +39,11 @@ public class HookDependenciesFuzzer { } @MethodHook(type = HookType.AFTER, targetClassName = "java.util.regex.Matcher", - targetMethod = "matches", additionalClassesToHook = {"java.util.regex.Pattern"}) + targetMethod = "matches", targetMethodDescriptor = "()Z", + additionalClassesToHook = {"java.util.regex.Pattern"}) public static void matcherMatchesHook(MethodHandle method, Object alwaysNull, Object[] alwaysEmpty, int hookId, - boolean returnValue) { + Boolean returnValue) { if (PATTERN_ROOT != null) { throw new FuzzerSecurityIssueLow("Hook applied even though it depends on the class to hook"); } -- cgit v1.2.3 From 95520d2b1090934af983b2342770238084557d31 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 9 Mar 2022 11:16:39 +0100 Subject: Prevent primitive return types on AFTER hooks We could look into conditionally unwrapping boxed types in the future, but for now catching this early rather than having it produce an obscure VerifyError is good enough. --- .../code_intelligence/jazzer/instrumentor/Hook.kt | 21 ++++++++++++++------- .../jazzer/instrumentor/HookValidationTest.kt | 2 +- .../jazzer/instrumentor/InvalidHookMocks.java | 4 ++++ .../jazzer/instrumentor/ValidHookMocks.java | 4 ---- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt index 5a6aa0ad..60b20a1c 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt @@ -112,13 +112,20 @@ class Hook private constructor( } } - // AfterMethodHook only: Verify the type of the last parameter if known. - if (potentialHook.hookType == HookType.AFTER && potentialHook.targetReturnTypeDescriptor != null) { - require( - parameterTypes[4] == java.lang.Object::class.java || - parameterTypes[4].descriptor == potentialHook.targetWrappedReturnTypeDescriptor - ) { - "$potentialHook: fifth parameter must have type Object or match the descriptor ${potentialHook.targetWrappedReturnTypeDescriptor}" + // AfterMethodHook only: Verify the type of the last parameter if known. Even if not + // known, it must not be a primitive value. + if (potentialHook.hookType == HookType.AFTER) { + if (potentialHook.targetReturnTypeDescriptor != null) { + require( + parameterTypes[4] == java.lang.Object::class.java || + parameterTypes[4].descriptor == potentialHook.targetWrappedReturnTypeDescriptor + ) { + "$potentialHook: fifth parameter must have type Object or match the descriptor ${potentialHook.targetWrappedReturnTypeDescriptor}" + } + } else { + require(!parameterTypes[4].isPrimitive) { + "$potentialHook: fifth parameter must not be a primitive type, use a boxed type instead" + } } } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt index b03fdbad..ac263dc5 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt @@ -23,7 +23,7 @@ class HookValidationTest { @Test fun testValidHooks() { val hooks = Hooks.loadHooks(setOf(ValidHookMocks::class.java.name)).first().hooks - assertEquals(6, hooks.size) + assertEquals(5, hooks.size) } @Test diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java index 551e7781..38d79b55 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java @@ -73,4 +73,8 @@ class InvalidHookMocks { MethodHandle method, Object thisObject, Object[] arguments, int hookId) throws Throwable { return new Object(); } + + @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "equals") + public static void primitiveReturnType(MethodHandle method, String thisObject, Object[] arguments, + int hookId, boolean returnValue) {} } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.java index 06bed141..a919242b 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.java @@ -27,10 +27,6 @@ class ValidHookMocks { public static void validAfterHook(MethodHandle method, String thisObject, Object[] arguments, int hookId, Boolean returnValue) {} - @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "equals") - public static void validAfterHook2(MethodHandle method, String thisObject, Object[] arguments, - int hookId, boolean returnValue) {} - @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.String", targetMethod = "equals", targetMethodDescriptor = "(Ljava/lang/Object;)Z") public static Boolean -- cgit v1.2.3 From 59d16517cd5ae8d743d570ee24fed2403fdd4682 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 8 Mar 2022 12:54:20 +0100 Subject: Fix ClassCastException in mapGet hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the argument to Map#get cannot be compared to the keys in the map due to an incompatible type, the hook threw a ClassCastException. To address this, we: * Catch and ignore ClassCastException where it could be thrown. * Reverse the compareTo calls so that the likely more lenient compareTo function of the valid map key is used. The original OSS-Fuzz finding that uncovered this bug: == Java Exception: java.lang.ClassCastException: class com.alibaba.fastjson.JSONObject cannot be cast to class java.lang.Integer (com.alibaba.fastjson.JSONObject is in unnamed module of loader 'app'; java.lang.Integer is in module java.base of loader 'bootstrap')  at java.base/java.lang.Integer.compareTo(Integer.java:64)  at com.code_intelligence.jazzer.runtime.TraceCmpHooks.mapGet(TraceCmpHooks.java:308)  at com.alibaba.fastjson.JSONObject.get(JSONObject.java:110)  at com.alibaba.fastjson.JSONPath.getArrayItem(JSONPath.java:3577)  at com.alibaba.fastjson.JSONPath$ArrayAccessSegment.eval(JSONPath.java:2736)  at com.alibaba.fastjson.JSONPath.eval(JSONPath.java:121)  at com.alibaba.fastjson.parser.DefaultJSONParser.handleResovleTask(DefaultJSONParser.java:1599)  at com.alibaba.fastjson.JSON.parse(JSON.java:183)  at com.alibaba.fastjson.JSON.parse(JSON.java:191)  at com.alibaba.fastjson.JSON.parse(JSON.java:147)  at JsonFuzzer.fuzzerTestOneInput(JsonFuzzer.java:24) Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=42365 --- .../jazzer/runtime/TraceCmpHooks.java | 33 ++++++++++++++-------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java index c7d4947e..e6d74187 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java @@ -293,25 +293,36 @@ final public class TraceCmpHooks { Object upperBoundKey = null; if (map instanceof TreeMap) { final TreeMap treeMap = (TreeMap) map; - lowerBoundKey = treeMap.floorKey(currentKey); - upperBoundKey = treeMap.ceilingKey(currentKey); + try { + lowerBoundKey = treeMap.floorKey(currentKey); + upperBoundKey = treeMap.ceilingKey(currentKey); + } catch (ClassCastException ignored) { + // Can be thrown by floorKey and ceilingKey if currentKey is of a type that can't be + // compared to the maps keys. + } } else if (currentKey instanceof Comparable) { - final Comparable comparableKey = (Comparable) currentKey; + final Comparable comparableCurrentKey = (Comparable) currentKey; // Find two keys that bracket currentKey. // Note: This is not deterministic if map.size() > MAX_NUM_KEYS_TO_ENUMERATE. int enumeratedKeys = 0; for (Object validKey : map.keySet()) { - if (validKey == null) + if (!(validKey instanceof Comparable)) continue; + final Comparable comparableValidKey = (Comparable) validKey; // If the key sorts lower than the non-existing key, but higher than the current lower // bound, update the lower bound and vice versa for the upper bound. - if (comparableKey.compareTo(validKey) > 0 - && (lowerBoundKey == null || ((Comparable) validKey).compareTo(lowerBoundKey) > 0)) { - lowerBoundKey = validKey; - } - if (comparableKey.compareTo(validKey) < 0 - && (upperBoundKey == null || ((Comparable) validKey).compareTo(upperBoundKey) < 0)) { - upperBoundKey = validKey; + try { + if (comparableValidKey.compareTo(comparableCurrentKey) < 0 + && (lowerBoundKey == null || comparableValidKey.compareTo(lowerBoundKey) > 0)) { + lowerBoundKey = validKey; + } + if (comparableValidKey.compareTo(comparableCurrentKey) > 0 + && (upperBoundKey == null || comparableValidKey.compareTo(upperBoundKey) < 0)) { + upperBoundKey = validKey; + } + } catch (ClassCastException ignored) { + // Can be thrown by floorKey and ceilingKey if currentKey is of a type that can't be + // compared to the maps keys. } if (enumeratedKeys++ > MAX_NUM_KEYS_TO_ENUMERATE) break; -- cgit v1.2.3 From bb95a0d3623c7ca28580301a25c3dd7703845b54 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 10 Mar 2022 13:56:10 +0100 Subject: Update rules_jni --- repositories.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/repositories.bzl b/repositories.bzl index 4276db0c..36e9aebd 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -91,9 +91,9 @@ def jazzer_dependencies(): maybe( http_archive, name = "fmeum_rules_jni", - sha256 = "9a387a066f683a8aac4d165917dc7fe15ec2a20931894a97e153a9caab6123ca", - strip_prefix = "rules_jni-0.4.0", - url = "https://github.com/fmeum/rules_jni/archive/refs/tags/v0.4.0.tar.gz", + sha256 = "39902411424856ce51c0137665171a4d08f2b767d30978dbded29dd099319890", + strip_prefix = "rules_jni-0.4.2", + url = "https://github.com/fmeum/rules_jni/archive/refs/tags/v0.4.2.tar.gz", ) maybe( -- cgit v1.2.3 From 8d5ca15e4c00a27403670f393020bda3380c830f Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 10 Mar 2022 13:52:32 +0100 Subject: Extract mock sanitizer symbols into a cc_library --- driver/BUILD.bazel | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index 63398fb7..b0af7897 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -232,13 +232,15 @@ cc_binary( deps = [":driver_lib"], ) +cc_library( + name = "sanitizer_symbols_for_tests", + srcs = ["sanitizer_symbols_for_tests.cpp"], +) + cc_test( name = "jvm_tooling_test", size = "small", - srcs = [ - "jvm_tooling_test.cpp", - "sanitizer_symbols_for_tests.cpp", - ], + srcs = ["jvm_tooling_test.cpp"], args = [ "--cp=jazzer/$(rootpath //driver/testdata:fuzz_target_mocks_deploy.jar)", ], @@ -258,6 +260,7 @@ cc_test( deps = [ ":coverage_tracker", ":jvm_tooling_lib", + ":sanitizer_symbols_for_tests", ":test_main", "@bazel_tools//tools/cpp/runfiles", "@googletest//:gtest", @@ -268,10 +271,7 @@ cc_test( cc_test( name = "fuzzed_data_provider_test", size = "medium", - srcs = [ - "fuzzed_data_provider_test.cpp", - "sanitizer_symbols_for_tests.cpp", - ], + srcs = ["fuzzed_data_provider_test.cpp"], args = [ "--cp=jazzer/$(rootpath //driver/testdata:fuzz_target_mocks_deploy.jar)", ], @@ -282,6 +282,7 @@ cc_test( includes = ["."], deps = [ ":jvm_tooling_lib", + ":sanitizer_symbols_for_tests", ":test_main", "@bazel_tools//tools/cpp/runfiles", "@googletest//:gtest", -- cgit v1.2.3 From 2fe2409518d9ef53212ce0e2c652118159f62a6b Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 10 Mar 2022 13:52:32 +0100 Subject: Extract native fuzzer callbacks into a shared library When looking up a JavaCritical_ function, the OpenJDK will first look up the library that contained the corresponding Java_ function and then try to dlopen that library. If the Java_ function is defined by the driver, this dlopen call will fail as it can't dynamically load executables. This seems to a bug as the usual Java_ lookup goes through more complicated logic that properly deals with statically linked native libraries. We work around this issue by loading the fuzzer callbacks from an honest shared library packaged into the agent JAR. Since dynamic linking is difficult on Windows and we don't even know whether CriticalJNINatives work there, we continue to statically link the callbacks there. --- agent/agent_shade_rules | 5 +- .../code_intelligence/jazzer/runtime/BUILD.bazel | 28 ++-- .../runtime/TraceDataFlowNativeCallbacks.java | 17 ++ .../code_intelligence/jazzer/runtime/BUILD.bazel | 24 +++ .../jazzer/runtime/jazzer_fuzzer_callbacks.cpp | 175 +++++++++++++++++++++ driver/BUILD.bazel | 22 ++- driver/libfuzzer_callbacks.cpp | 155 ------------------ 7 files changed, 258 insertions(+), 168 deletions(-) create mode 100644 agent/src/main/native/com/code_intelligence/jazzer/runtime/BUILD.bazel create mode 100644 agent/src/main/native/com/code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp diff --git a/agent/agent_shade_rules b/agent/agent_shade_rules index 2897ec37..84fb2a74 100644 --- a/agent/agent_shade_rules +++ b/agent/agent_shade_rules @@ -1,5 +1,6 @@ -rule kotlin.** com.code_intelligence.jazzer.third_party.kotlin.@1 +rule com.github.** com.code_intelligence.jazzer.third_party.com.github.@1 rule io.** com.code_intelligence.jazzer.third_party.io.@1 -rule nonapi.** com.code_intelligence.jazzer.third_party.nonapi.@1 +rule kotlin.** com.code_intelligence.jazzer.third_party.kotlin.@1 rule net.jodah.** com.code_intelligence.jazzer.third_party.net.jodah.@1 +rule nonapi.** com.code_intelligence.jazzer.third_party.nonapi.@1 rule org.** com.code_intelligence.jazzer.third_party.org.@1 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 e75692df..bd2dcf66 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,4 +1,4 @@ -load("@fmeum_rules_jni//jni:defs.bzl", "jni_headers") +load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library", "jni_headers") load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") java_library( @@ -44,16 +44,26 @@ jni_headers( visibility = ["//driver:__pkg__"], ) -java_library( +java_jni_library( name = "trace_data_flow_native_callbacks", srcs = ["TraceDataFlowNativeCallbacks.java"], - deps = ["//agent/src/main/java/com/code_intelligence/jazzer/utils"], -) - -jni_headers( - name = "trace_data_flow_native_callbacks.hdrs", - lib = ":trace_data_flow_native_callbacks", - visibility = ["//driver:__pkg__"], + native_libs = select({ + # On Windows, the fuzzer callbacks are statically linked into the driver instead (see + # //driver:jazzer_driver). + "@platforms//os:windows": [], + "//conditions:default": [ + "//agent/src/main/native/com/code_intelligence/jazzer/runtime:jazzer_fuzzer_callbacks", + ], + }), + visibility = [ + # For libFuzzer callbacks. + "//agent/src/main/native/com/code_intelligence/jazzer/runtime:__pkg__", + # For handleLibraryLoad. + "//driver:__pkg__", + ], + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/utils", + ], ) java_library( diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java index efbc3411..d759e282 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java @@ -15,11 +15,28 @@ package com.code_intelligence.jazzer.runtime; import com.code_intelligence.jazzer.utils.Utils; +import com.github.fmeum.rules_jni.RulesJni; import java.lang.reflect.Executable; import java.nio.charset.Charset; @SuppressWarnings("unused") final public class TraceDataFlowNativeCallbacks { + static { + try { + RulesJni.loadLibrary("jazzer_fuzzer_callbacks", TraceDataFlowNativeCallbacks.class); + } catch (UnsatisfiedLinkError e) { + // On Windows, we link the fuzzer callbacks statically instead and thus expect this library + // load to fail. + if (!System.getProperty("os.name").startsWith("Windows")) { + // In some scenarios (e.g. Java unit tests that do not go through the driver), this native + // library load will expectedly fail due to missing symbols. We make this case non-fatal as + // every actual usage of native methods in this class would result in another + // UnsatisfiedLinkError. + e.printStackTrace(); + } + } + } + // Making this static final ensures that the JIT will eliminate the dead branch of a construct // such as: // if (USE_FAKE_PCS) ... else ... diff --git a/agent/src/main/native/com/code_intelligence/jazzer/runtime/BUILD.bazel b/agent/src/main/native/com/code_intelligence/jazzer/runtime/BUILD.bazel new file mode 100644 index 00000000..f18afe15 --- /dev/null +++ b/agent/src/main/native/com/code_intelligence/jazzer/runtime/BUILD.bazel @@ -0,0 +1,24 @@ +load("@fmeum_rules_jni//jni:defs.bzl", "cc_jni_library") + +cc_jni_library( + name = "jazzer_fuzzer_callbacks", + # Not needed (and broken) on Windows. + tags = ["manual"], + visibility = ["//agent/src/main/java/com/code_intelligence/jazzer/runtime:__pkg__"], + deps = [ + ":jazzer_fuzzer_callbacks_for_static_linking", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:trace_data_flow_native_callbacks.hdrs", + "//driver:sanitizer_hooks_with_pc", + ], +) + +cc_library( + name = "jazzer_fuzzer_callbacks_for_static_linking", + srcs = ["jazzer_fuzzer_callbacks.cpp"], + visibility = ["//driver:__pkg__"], + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:trace_data_flow_native_callbacks.hdrs", + "//driver:sanitizer_hooks_with_pc", + ], + alwayslink = True, +) diff --git a/agent/src/main/native/com/code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp b/agent/src/main/native/com/code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp new file mode 100644 index 00000000..0a3dc9b7 --- /dev/null +++ b/agent/src/main/native/com/code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp @@ -0,0 +1,175 @@ +// Copyright 2022 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 + +#include +#include + +#include "com_code_intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks.h" +#include "driver/sanitizer_hooks_with_pc.h" + +namespace { + +extern "C" { +void __sanitizer_weak_hook_compare_bytes(void *caller_pc, const void *s1, + const void *s2, std::size_t n1, + std::size_t n2, int result); +void __sanitizer_weak_hook_memmem(void *called_pc, const void *s1, size_t len1, + const void *s2, size_t len2, void *result); +void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2); +void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2); + +void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases); + +void __sanitizer_cov_trace_div4(uint32_t val); +void __sanitizer_cov_trace_div8(uint64_t val); + +void __sanitizer_cov_trace_gep(uintptr_t idx); +} + +inline __attribute__((always_inline)) void *idToPc(jint id) { + return reinterpret_cast(static_cast(id)); +} +} // namespace + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceStrstr0( + JNIEnv *env, jclass cls, jbyteArray needle, jint id) { + auto *needle_native = + static_cast(env->GetPrimitiveArrayCritical(needle, nullptr)); + jint needle_length = env->GetArrayLength(needle); + __sanitizer_weak_hook_memmem(idToPc(id), nullptr, 0, needle_native, + needle_length, nullptr); + env->ReleasePrimitiveArrayCritical(needle, needle_native, JNI_ABORT); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceMemcmp( + JNIEnv *env, jclass cls, jbyteArray b1, jbyteArray b2, jint result, + jint id) { + auto *b1_native = + static_cast(env->GetPrimitiveArrayCritical(b1, nullptr)); + auto *b2_native = + static_cast(env->GetPrimitiveArrayCritical(b2, nullptr)); + jint b1_length = env->GetArrayLength(b1); + jint b2_length = env->GetArrayLength(b2); + __sanitizer_weak_hook_compare_bytes(idToPc(id), b1_native, b2_native, + b1_length, b2_length, result); + env->ReleasePrimitiveArrayCritical(b1, b1_native, JNI_ABORT); + env->ReleasePrimitiveArrayCritical(b2, b2_native, JNI_ABORT); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpLong( + JNIEnv *env, jclass cls, jlong value1, jlong value2) { + __sanitizer_cov_trace_cmp8(value1, value2); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpLongWithPc( + JNIEnv *env, jclass cls, jlong value1, jlong value2, jint id) { + __sanitizer_cov_trace_cmp8_with_pc(idToPc(id), value1, value2); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpInt( + JNIEnv *env, jclass cls, jint value1, jint value2) { + __sanitizer_cov_trace_cmp4(value1, value2); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpIntWithPc( + JNIEnv *env, jclass cls, jint value1, jint value2, jint id) { + __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceConstCmpInt( + JNIEnv *env, jclass cls, jint value1, jint value2) { + __sanitizer_cov_trace_cmp4(value1, value2); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceConstCmpIntWithPc( + JNIEnv *env, jclass cls, jint value1, jint value2, jint id) { + __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitch( + JNIEnv *env, jclass cls, jlong switch_value, + jlongArray libfuzzer_case_values) { + auto *case_values = static_cast( + env->GetPrimitiveArrayCritical(libfuzzer_case_values, nullptr)); + __sanitizer_cov_trace_switch(switch_value, + reinterpret_cast(case_values)); + env->ReleasePrimitiveArrayCritical(libfuzzer_case_values, case_values, + JNI_ABORT); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitchWithPc( + JNIEnv *env, jclass cls, jlong switch_value, + jlongArray libfuzzer_case_values, jint id) { + auto *case_values = static_cast( + env->GetPrimitiveArrayCritical(libfuzzer_case_values, nullptr)); + __sanitizer_cov_trace_switch_with_pc( + idToPc(id), switch_value, reinterpret_cast(case_values)); + env->ReleasePrimitiveArrayCritical(libfuzzer_case_values, case_values, + JNI_ABORT); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivLong( + JNIEnv *env, jclass cls, jlong value) { + __sanitizer_cov_trace_div8(value); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivLongWithPc( + JNIEnv *env, jclass cls, jlong value, jint id) { + __sanitizer_cov_trace_div8_with_pc(idToPc(id), value); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivInt( + JNIEnv *env, jclass cls, jint value) { + __sanitizer_cov_trace_div4(value); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivIntWithPc( + JNIEnv *env, jclass cls, jint value, jint id) { + __sanitizer_cov_trace_div4_with_pc(idToPc(id), value); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceGep( + JNIEnv *env, jclass cls, jlong idx) { + __sanitizer_cov_trace_gep(static_cast(idx)); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceGepWithPc( + JNIEnv *env, jclass cls, jlong idx, jint id) { + __sanitizer_cov_trace_gep_with_pc(idToPc(id), static_cast(idx)); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_tracePcIndir0( + JNIEnv *env, jclass cls, jint caller_id, jint callee_id) { + __sanitizer_cov_trace_pc_indir_with_pc(idToPc(caller_id), + static_cast(callee_id)); +} diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index b0af7897..f7f2fc64 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -7,6 +7,7 @@ cc_library( linkstatic = True, visibility = [ "//agent/src/jmh/native/com/code_intelligence/jazzer/runtime:__pkg__", + "//agent/src/main/native/com/code_intelligence/jazzer/runtime:__pkg__", ], ) @@ -68,13 +69,30 @@ cc_library( srcs = ["libfuzzer_callbacks.cpp"], linkstatic = True, deps = [ - ":sanitizer_hooks_with_pc", "//agent/src/main/java/com/code_intelligence/jazzer/runtime:trace_data_flow_native_callbacks.hdrs", "@com_google_absl//absl/strings", "@com_google_glog//:glog", "@fmeum_rules_jni//jni", "@jazzer_com_github_gflags_gflags//:gflags", - ], + ] + select({ + # We statically link the fuzzer callbacks on Windows since linking a shared library against + # symbols exported by a binary is difficult: We would need a .def file to export the symbols + # and create an interface library from the binary that is then linked into the shared + # library. This creates a conceptual (and real) cyclic dependency between the binary and the + # shared library since the binary data-depends on the agent and thus on the shared library. + # Since we only have to link the fuzzer callbacks dynamically to work around a JDK bug in an + # obsoleted feature (critical JNI natives) that is only meant to improve performance, we + # don't worry about this too much. + "@platforms//os:windows": [ + "//agent/src/main/native/com/code_intelligence/jazzer/runtime:jazzer_fuzzer_callbacks_for_static_linking", + ], + # On other platforms, dynamic linking is easy, so we load the fuzzer callbacks from a shared + # library at runtime. This is needed to let the JVM's JavaCritical_* lookup succeed, which + # does not correctly load statically linked symbols. + # See //agent/src/main/native/com/code_intelligence/jazzer/runtime:jazzer_fuzzer_callbacks + # for the place this is linked into the agent instead. + "//conditions:default": [], + }), # Symbols are only referenced dynamically via JNI. alwayslink = True, ) diff --git a/driver/libfuzzer_callbacks.cpp b/driver/libfuzzer_callbacks.cpp index 78a4d94e..97243a43 100644 --- a/driver/libfuzzer_callbacks.cpp +++ b/driver/libfuzzer_callbacks.cpp @@ -25,7 +25,6 @@ #include "com_code_intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks.h" #include "gflags/gflags.h" #include "glog/logging.h" -#include "sanitizer_hooks_with_pc.h" DEFINE_bool( fake_pcs, false, @@ -33,160 +32,6 @@ DEFINE_bool( "make value profiling more effective. Enabled by default if " "-use_value_profile=1 is specified."); -namespace { - -extern "C" { -void __sanitizer_weak_hook_compare_bytes(void *caller_pc, const void *s1, - const void *s2, std::size_t n1, - std::size_t n2, int result); -void __sanitizer_weak_hook_memmem(void *called_pc, const void *s1, size_t len1, - const void *s2, size_t len2, void *result); -void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2); -void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2); - -void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases); - -void __sanitizer_cov_trace_div4(uint32_t val); -void __sanitizer_cov_trace_div8(uint64_t val); - -void __sanitizer_cov_trace_gep(uintptr_t idx); -} - -inline __attribute__((always_inline)) void *idToPc(jint id) { - return reinterpret_cast(static_cast(id)); -} -} // namespace - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceStrstr0( - JNIEnv *env, jclass cls, jbyteArray needle, jint id) { - auto *needle_native = - static_cast(env->GetPrimitiveArrayCritical(needle, nullptr)); - jint needle_length = env->GetArrayLength(needle); - __sanitizer_weak_hook_memmem(idToPc(id), nullptr, 0, needle_native, - needle_length, nullptr); - env->ReleasePrimitiveArrayCritical(needle, needle_native, JNI_ABORT); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceMemcmp( - JNIEnv *env, jclass cls, jbyteArray b1, jbyteArray b2, jint result, - jint id) { - auto *b1_native = - static_cast(env->GetPrimitiveArrayCritical(b1, nullptr)); - auto *b2_native = - static_cast(env->GetPrimitiveArrayCritical(b2, nullptr)); - jint b1_length = env->GetArrayLength(b1); - jint b2_length = env->GetArrayLength(b2); - __sanitizer_weak_hook_compare_bytes(idToPc(id), b1_native, b2_native, - b1_length, b2_length, result); - env->ReleasePrimitiveArrayCritical(b1, b1_native, JNI_ABORT); - env->ReleasePrimitiveArrayCritical(b2, b2_native, JNI_ABORT); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpLong( - JNIEnv *env, jclass cls, jlong value1, jlong value2) { - __sanitizer_cov_trace_cmp8(value1, value2); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpLongWithPc( - JNIEnv *env, jclass cls, jlong value1, jlong value2, jint id) { - __sanitizer_cov_trace_cmp8_with_pc(idToPc(id), value1, value2); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpInt( - JNIEnv *env, jclass cls, jint value1, jint value2) { - __sanitizer_cov_trace_cmp4(value1, value2); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpIntWithPc( - JNIEnv *env, jclass cls, jint value1, jint value2, jint id) { - __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceConstCmpInt( - JNIEnv *env, jclass cls, jint value1, jint value2) { - __sanitizer_cov_trace_cmp4(value1, value2); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceConstCmpIntWithPc( - JNIEnv *env, jclass cls, jint value1, jint value2, jint id) { - __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitch( - JNIEnv *env, jclass cls, jlong switch_value, - jlongArray libfuzzer_case_values) { - auto *case_values = static_cast( - env->GetPrimitiveArrayCritical(libfuzzer_case_values, nullptr)); - __sanitizer_cov_trace_switch(switch_value, - reinterpret_cast(case_values)); - env->ReleasePrimitiveArrayCritical(libfuzzer_case_values, case_values, - JNI_ABORT); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitchWithPc( - JNIEnv *env, jclass cls, jlong switch_value, - jlongArray libfuzzer_case_values, jint id) { - auto *case_values = static_cast( - env->GetPrimitiveArrayCritical(libfuzzer_case_values, nullptr)); - __sanitizer_cov_trace_switch_with_pc( - idToPc(id), switch_value, reinterpret_cast(case_values)); - env->ReleasePrimitiveArrayCritical(libfuzzer_case_values, case_values, - JNI_ABORT); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivLong( - JNIEnv *env, jclass cls, jlong value) { - __sanitizer_cov_trace_div8(value); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivLongWithPc( - JNIEnv *env, jclass cls, jlong value, jint id) { - __sanitizer_cov_trace_div8_with_pc(idToPc(id), value); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivInt( - JNIEnv *env, jclass cls, jint value) { - __sanitizer_cov_trace_div4(value); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivIntWithPc( - JNIEnv *env, jclass cls, jint value, jint id) { - __sanitizer_cov_trace_div4_with_pc(idToPc(id), value); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceGep( - JNIEnv *env, jclass cls, jlong idx) { - __sanitizer_cov_trace_gep(static_cast(idx)); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceGepWithPc( - JNIEnv *env, jclass cls, jlong idx, jint id) { - __sanitizer_cov_trace_gep_with_pc(idToPc(id), static_cast(idx)); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_tracePcIndir0( - JNIEnv *env, jclass cls, jint caller_id, jint callee_id) { - __sanitizer_cov_trace_pc_indir_with_pc(idToPc(caller_id), - static_cast(callee_id)); -} - namespace { bool is_using_native_libraries = false; std::once_flag ignore_list_flag; -- cgit v1.2.3 From 297b9203d91c2543f4180386fd6c1d4200def0ba Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 10 Mar 2022 14:59:43 +0100 Subject: Add JavaCritical implementations for all fuzzer callbacks --- .../jazzer/runtime/jazzer_fuzzer_callbacks.cpp | 109 +++++++++++++++++++++ driver/jvm_tooling.cpp | 2 + 2 files changed, 111 insertions(+) diff --git a/agent/src/main/native/com/code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp b/agent/src/main/native/com/code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp index 0a3dc9b7..da85efbb 100644 --- a/agent/src/main/native/com/code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp +++ b/agent/src/main/native/com/code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp @@ -55,6 +55,13 @@ Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceStr env->ReleasePrimitiveArrayCritical(needle, needle_native, JNI_ABORT); } +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceStrstr0( + jint needle_length, jbyte *needle_native, jint id) { + __sanitizer_weak_hook_memmem(idToPc(id), nullptr, 0, needle_native, + needle_length, nullptr); +} + [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceMemcmp( JNIEnv *env, jclass cls, jbyteArray b1, jbyteArray b2, jint result, @@ -71,42 +78,86 @@ Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceMem env->ReleasePrimitiveArrayCritical(b2, b2_native, JNI_ABORT); } +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceMemcmp( + jint b1_length, jbyte *b1, jint b2_length, jbyte *b2, jint result, + jint id) { + __sanitizer_weak_hook_compare_bytes(idToPc(id), b1, b2, b1_length, b2_length, + result); +} + [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpLong( JNIEnv *env, jclass cls, jlong value1, jlong value2) { __sanitizer_cov_trace_cmp8(value1, value2); } +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpLong( + jlong value1, jlong value2) { + __sanitizer_cov_trace_cmp8(value1, value2); +} + [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpLongWithPc( JNIEnv *env, jclass cls, jlong value1, jlong value2, jint id) { __sanitizer_cov_trace_cmp8_with_pc(idToPc(id), value1, value2); } +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpLongWithPc( + jlong value1, jlong value2, jint id) { + __sanitizer_cov_trace_cmp8_with_pc(idToPc(id), value1, value2); +} + [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpInt( JNIEnv *env, jclass cls, jint value1, jint value2) { __sanitizer_cov_trace_cmp4(value1, value2); } +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpInt( + jint value1, jint value2) { + __sanitizer_cov_trace_cmp4(value1, value2); +} + [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpIntWithPc( JNIEnv *env, jclass cls, jint value1, jint value2, jint id) { __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); } +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpIntWithPc( + jint value1, jint value2, jint id) { + __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); +} + [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceConstCmpInt( JNIEnv *env, jclass cls, jint value1, jint value2) { __sanitizer_cov_trace_cmp4(value1, value2); } +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceConstCmpInt( + jint value1, jint value2) { + __sanitizer_cov_trace_cmp4(value1, value2); +} + [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceConstCmpIntWithPc( JNIEnv *env, jclass cls, jint value1, jint value2, jint id) { __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); } +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceConstCmpIntWithPc( + jint value1, jint value2, jint id) { + __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); +} + [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitch( JNIEnv *env, jclass cls, jlong switch_value, @@ -119,6 +170,13 @@ Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwi JNI_ABORT); } +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitch( + jlong switch_value, jint libfuzzer_case_values_length, jlong *case_values) { + __sanitizer_cov_trace_switch(switch_value, + reinterpret_cast(case_values)); +} + [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitchWithPc( JNIEnv *env, jclass cls, jlong switch_value, @@ -131,45 +189,96 @@ Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwi JNI_ABORT); } +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitchWithPc( + jlong switch_value, jint libfuzzer_case_values_length, jlong *case_values, + jint id) { + __sanitizer_cov_trace_switch_with_pc( + idToPc(id), switch_value, reinterpret_cast(case_values)); +} + [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivLong( JNIEnv *env, jclass cls, jlong value) { __sanitizer_cov_trace_div8(value); } +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivLong( + jlong value) { + __sanitizer_cov_trace_div8(value); +} + [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivLongWithPc( JNIEnv *env, jclass cls, jlong value, jint id) { __sanitizer_cov_trace_div8_with_pc(idToPc(id), value); } +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivLongWithPc( + jlong value, jint id) { + __sanitizer_cov_trace_div8_with_pc(idToPc(id), value); +} + [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivInt( JNIEnv *env, jclass cls, jint value) { __sanitizer_cov_trace_div4(value); } +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivInt( + jint value) { + __sanitizer_cov_trace_div4(value); +} + [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivIntWithPc( JNIEnv *env, jclass cls, jint value, jint id) { __sanitizer_cov_trace_div4_with_pc(idToPc(id), value); } +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivIntWithPc( + jint value, jint id) { + __sanitizer_cov_trace_div4_with_pc(idToPc(id), value); +} + [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceGep( JNIEnv *env, jclass cls, jlong idx) { __sanitizer_cov_trace_gep(static_cast(idx)); } +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceGep( + jlong idx) { + __sanitizer_cov_trace_gep(static_cast(idx)); +} + [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceGepWithPc( JNIEnv *env, jclass cls, jlong idx, jint id) { __sanitizer_cov_trace_gep_with_pc(idToPc(id), static_cast(idx)); } +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceGepWithPc( + jlong idx, jint id) { + __sanitizer_cov_trace_gep_with_pc(idToPc(id), static_cast(idx)); +} + [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_tracePcIndir0( JNIEnv *env, jclass cls, jint caller_id, jint callee_id) { __sanitizer_cov_trace_pc_indir_with_pc(idToPc(caller_id), static_cast(callee_id)); } + +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_tracePcIndir0( + jint caller_id, jint callee_id) { + __sanitizer_cov_trace_pc_indir_with_pc(idToPc(caller_id), + static_cast(callee_id)); +} diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index 9ae954d2..09bca90a 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -257,6 +257,8 @@ JVM::JVM(std::string_view executable_path, std::string_view seed) { JavaVMOption{.optionString = (char *)"-XX:-OmitStackTraceInFastThrow"}); // Optimize GC for high throughput rather than low latency. options.push_back(JavaVMOption{.optionString = (char *)"-XX:+UseParallelGC"}); + options.push_back( + JavaVMOption{.optionString = (char *)"-XX:+CriticalJNINatives"}); // Forward libFuzzer's random seed so that Jazzer hooks can base their // mutations on it. std::string seed_property = absl::StrFormat("-Djazzer.seed=%s", seed); -- cgit v1.2.3 From 8a30d4301e01f44756ad0d8456f4bd74e2b72b4c Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Fri, 18 Mar 2022 15:26:08 +0100 Subject: Extract native library loading to dedicated class Native library loading has a few pitfalls. This PR extracts the code out of RuntimeInstrumentor into its own class and describes the situation in context of the Java agent. --- .../java/com/code_intelligence/jazzer/agent/Agent.kt | 17 ++++++++++++++++- .../jazzer/agent/RuntimeInstrumentor.kt | 8 -------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index dd9bccb7..3ce50e03 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -35,7 +35,7 @@ import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.exists import kotlin.io.path.isDirectory -val KNOWN_ARGUMENTS = listOf( +private val KNOWN_ARGUMENTS = listOf( "instrumentation_includes", "instrumentation_excludes", "custom_hook_includes", @@ -46,6 +46,19 @@ val KNOWN_ARGUMENTS = listOf( "dump_classes_dir", ) +// To be accessible by the agent classes the native library has to be loaded by the same class loader. +// premain is executed in the context of the system class loader. At the beginning of premain the agent jar is added to +// the bootstrap class loader and all subsequently required agent classes are loaded by it. Hence, it's not possible to +// load the native library directly in premain by the system class loader, instead it's delegated to NativeLibraryLoader +// loaded by the bootstrap class loader. +internal object NativeLibraryLoader { + fun load() { + // Calls JNI_OnLoad_jazzer_initialize in the driver, which ensures that dynamically + // linked JNI methods are resolved against it. + System.loadLibrary("jazzer_initialize") + } +} + private object AgentJarFinder { val agentJarFile = jarUriForClass(AgentJarFinder::class.java)?.let { JarFile(File(it)) } } @@ -67,6 +80,8 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { } else { println("WARN: Failed to add agent JAR to bootstrap class loader search path") } + NativeLibraryLoader.load() + val argumentMap = (agentArgs ?: "") .split(',') .mapNotNull { diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt index c069b9e8..79c04c4a 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt @@ -178,12 +178,4 @@ internal class RuntimeInstrumentor( instrumentedBytecode } } - - companion object { - init { - // Calls JNI_OnLoad_jazzer_initialize in the driver, which ensures that dynamically - // linked JNI methods are resolved against it. - System.loadLibrary("jazzer_initialize") - } - } } -- cgit v1.2.3 From 4aac631795dd76f4cafdc2349343fdcca7065664 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Mon, 14 Mar 2022 11:46:39 +0100 Subject: Only pass number of nodes to CoverageRecorder CoverageRecorder internally adds firstId and actualNumEdgeIds, this should not be done two times. --- .../main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt index 79c04c4a..8490fb68 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt @@ -168,7 +168,7 @@ internal class RuntimeInstrumentor( internalClassName, bytecode, firstId, - firstId + actualNumEdgeIds + actualNumEdgeIds ) } } -- cgit v1.2.3 From 1c0e77a0e84818d1b3d8f1d314cea90d49277abc Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Mon, 14 Mar 2022 11:47:57 +0100 Subject: Add coverage generation tests This test verifies the old coverage output and will be used to check the new implementation. --- tests/BUILD.bazel | 16 +++ .../src/test/java/com/example/CoverageFuzzer.java | 130 +++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 tests/src/test/java/com/example/CoverageFuzzer.java diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index d12f4986..2870edef 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -71,3 +71,19 @@ java_fuzz_target_test( # The exit codes of the forked libFuzzer processes are not picked up correctly. target_compatible_with = SKIP_ON_MACOS, ) + +java_fuzz_target_test( + name = "CoverageFuzzer", + srcs = [ + "src/test/java/com/example/CoverageFuzzer.java", + ], + fuzzer_args = [ + "-use_value_profile=1", + "--coverage_report=coverage.exec", + ], + target_class = "com.example.CoverageFuzzer", + verify_crash_input = False, + deps = [ + "@jazzer_jacoco//:jacoco_internal", + ], +) diff --git a/tests/src/test/java/com/example/CoverageFuzzer.java b/tests/src/test/java/com/example/CoverageFuzzer.java new file mode 100644 index 00000000..e3a4880b --- /dev/null +++ b/tests/src/test/java/com/example/CoverageFuzzer.java @@ -0,0 +1,130 @@ +/* + * Copyright 2022 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.example; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +@SuppressWarnings({"unused", "UnusedReturnValue"}) +public final class CoverageFuzzer { + public static class ClassToCover { + private final int i; + + public ClassToCover(int i) { + if (i < 0 || i > 1000) { + throw new IllegalArgumentException(String.format("Invalid repeat number \"%d\"", i)); + } + this.i = i; + } + + public String repeat(String str) { + if (str != null && str.length() >= 3 && str.length() <= 10) { + return IntStream.range(0, i).mapToObj(i -> str).collect(Collectors.joining()); + } + throw new IllegalArgumentException(String.format("Invalid str \"%s\"", str)); + } + } + + public static void fuzzerTestOneInput(FuzzedDataProvider data) { + try { + ClassToCover classToCover = new ClassToCover(data.consumeInt()); + String repeated = classToCover.repeat(data.consumeRemainingAsAsciiString()); + if (repeated.equals("foofoofoo")) { + throw new FuzzerSecurityIssueLow("Finished coverage fuzzer test"); + } + } catch (IllegalArgumentException ignored) { + } + } + + public static void fuzzerTearDown() throws IOException { + List coverage = Files.readAllLines(Paths.get("./coverage.exec")); + assertEquals(871, coverage.size()); + + List> sections = new ArrayList<>(4); + sections.add(new ArrayList<>()); + coverage.forEach(l -> { + if (l.isEmpty()) { + sections.add(new ArrayList<>()); + } + sections.get(sections.size() - 1).add(l); + }); + + List branchCoverage = sections.get(0); + assertEquals(217, branchCoverage.size()); + List lineCoverage = sections.get(1); + assertEquals(218, lineCoverage.size()); + List incompleteCoverage = sections.get(2); + assertEquals(218, incompleteCoverage.size()); + List missedCoverage = sections.get(3); + assertEquals(218, missedCoverage.size()); + + String branch = + branchCoverage.stream() + .filter(l -> l.startsWith(CoverageFuzzer.class.getSimpleName())) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Could not find branch coverage")); + // assertEquals("CoverageFuzzer.java: 11/16 (68.75%)", branch); + + String line = lineCoverage.stream() + .filter(l -> l.startsWith(CoverageFuzzer.class.getSimpleName())) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Could not find line coverage")); + assertEquals("CoverageFuzzer.java: 15/61 (24.59%)", line); + + String incomplete = + incompleteCoverage.stream() + .filter(l -> l.startsWith(CoverageFuzzer.class.getSimpleName())) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Could not find incomplete coverage")); + assertEquals("CoverageFuzzer.java: []", incomplete); + + String missed = + missedCoverage.stream() + .filter(l -> l.startsWith(CoverageFuzzer.class.getSimpleName())) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Could not find missed coverage")); + if (IntStream.rangeClosed(15, 44).anyMatch(i -> missed.contains(String.valueOf(i)))) { + throw new IllegalStateException("No coverage collected for ClassToCover"); + } + + // TODO switch to JaCoCo coverage report format + // CoverageBuilder coverage = new CoverageBuilder(); + // ExecutionDataStore executionDataStore = new ExecutionDataStore(); + // SessionInfoStore sessionInfoStore = new SessionInfoStore(); + // try (FileInputStream bais = new FileInputStream("./coverage.exec")) { + // ExecutionDataReader reader = new ExecutionDataReader(bais); + // reader.setExecutionDataVisitor(executionDataStore); + // reader.setSessionInfoVisitor(sessionInfoStore); + // reader.read(); + // } + // System.out.println(coverage.getClasses()); + } + + private static void assertEquals(T expected, T actual) { + if (!expected.equals(actual)) { + throw new IllegalStateException( + String.format("Expected \"%s\", got \"%s\"", expected, actual)); + } + } +} -- cgit v1.2.3 From 5b470bb4ee41d925d100b16fcc9cb0009acb1a93 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Tue, 15 Mar 2022 17:13:17 +0100 Subject: Add JaCoCo coverage dump command JaCoCo coverage can be used to create reports helping to identify fuzz blockers. The --coverage_dump flag can now specify a dump file containing coverage information that should be created after the fuzz run. --- README.md | 27 +++++ .../jazzer/instrumentor/CoverageRecorder.kt | 72 +++++++---- driver/coverage_tracker.cpp | 39 ++++-- driver/coverage_tracker.h | 3 +- driver/fuzz_target_runner.cpp | 19 +-- driver/libfuzzer_driver.cpp | 8 ++ tests/BUILD.bazel | 9 +- .../src/test/java/com/example/CoverageFuzzer.java | 132 ++++++++++++++++----- 8 files changed, 234 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 90d2baa5..fdceec99 100644 --- a/README.md +++ b/README.md @@ -471,6 +471,33 @@ With the flag `--keep_going=N` Jazzer continues fuzzing until `N` unique stack t Particular stack traces can also be ignored based on their `DEDUP_TOKEN` by passing a comma-separated list of tokens via `--ignore=,`. +### Export coverage information + +The internally gathered JaCoCo coverage information can be exported in a human-readable and the JaCoCo dump format. +These can help identify code areas that can not be reached through fuzzing and perhaps need changes to make them more +accessible for the fuzzer. + +The human-readable report contains coverage information, like branch and line coverage, on file level. It's useful to +get a quick overview about the overall coverage. The flag `--coverage_report=` can be used to generate the report. + +Similar to the JaCoCo `dump` command the flag `--coverage_dump=` specifies the coverage dump file, often called +`coverage.exec`, that should be generated after the fuzzing run. It contains a binary representation of the gathered +coverage data in the JaCoCo format. + +The JaCoCo `report` command can be used to generate reports based on the coverage dump. For example the +following command generates an HTML report in the folder `./report/` containing all classes available in `classes.jar` +and their coverage as captured in the export `coverage.exec`. +```shell +java -jar jacococli.jar report coverage.exec \ + --classfiles classes.jar \ + --sourcefiles some/path/to/sources \ + --html ./report/ \ + --name FuzzCoverageReport +``` + +More information about coverage report generation is available on the JaCoCo +[CLI documentation](https://www.eclemma.org/jacoco/trunk/doc/cli.html) page. + ## Advanced fuzz targets ### Fuzzing with Native Libraries diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt index b873d118..7ad59086 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt @@ -19,14 +19,13 @@ import com.code_intelligence.jazzer.utils.ClassNameGlobber import io.github.classgraph.ClassGraph import org.jacoco.core.analysis.CoverageBuilder import org.jacoco.core.data.ExecutionData -import org.jacoco.core.data.ExecutionDataReader import org.jacoco.core.data.ExecutionDataStore import org.jacoco.core.data.ExecutionDataWriter import org.jacoco.core.data.SessionInfo -import org.jacoco.core.data.SessionInfoStore import org.jacoco.core.internal.data.CRC64 -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream +import java.io.File +import java.io.FileOutputStream +import java.io.OutputStream import java.time.Instant import java.util.UUID @@ -65,8 +64,18 @@ object CoverageRecorder { CoverageMap.replayCoveredIds(additionalCoverage) } + /** + * [dumpCoverageReport] dumps a human-readable coverage report of files using any [coveredIds] to [dumpFileName]. + */ @JvmStatic - fun computeFileCoverage(coveredIds: IntArray): String { + fun dumpCoverageReport(coveredIds: IntArray, dumpFileName: String) { + File(dumpFileName).bufferedWriter().use { writer -> + writer.write(computeFileCoverage(coveredIds)) + } + } + + private fun computeFileCoverage(coveredIds: IntArray): String { + fun Double.format(digits: Int) = "%.${digits}f".format(this) val coverage = analyzeCoverage(coveredIds.toSet()) ?: return "No classes were instrumented" return coverage.sourceFiles.joinToString( "\n", @@ -104,21 +113,42 @@ object CoverageRecorder { } } - private fun Double.format(digits: Int) = "%.${digits}f".format(this) + /** + * [dumpJacocoCoverage] dumps the JaCoCo coverage of files using any [coveredIds] to [dumpFileName]. + * JaCoCo only exports coverage for files containing at least one coverage data point. The dump + * can be used by the JaCoCo report command to create reports also including not covered files. + */ + @JvmStatic + fun dumpJacocoCoverage(coveredIds: IntArray, dumpFileName: String) { + FileOutputStream(dumpFileName).use { outStream -> + dumpJacocoCoverage(coveredIds, outStream) + } + } + + /** + * [dumpJacocoCoverage] dumps the JaCoCo coverage of files using any [coveredIds] to [outStream]. + */ + @JvmStatic + fun dumpJacocoCoverage(coveredIds: IntArray, outStream: OutputStream) { + // Return if no class has been instrumented. + val startTimestamp = startTimestamp ?: return - fun dumpJacocoCoverage(coveredIds: Set): ByteArray? { // Update the list of covered IDs with the coverage information for the current run. updateCoveredIdsWithCoverageMap() val dumpTimestamp = Instant.now() - val outStream = ByteArrayOutputStream() val outWriter = ExecutionDataWriter(outStream) - // Return null if no class has been instrumented. - val startTimestamp = startTimestamp ?: return null outWriter.visitSessionInfo( SessionInfo(UUID.randomUUID().toString(), startTimestamp.epochSecond, dumpTimestamp.epochSecond) ) + analyzeJacocoCoverage(coveredIds.toSet()).accept(outWriter) + } + /** + * Build up a JaCoCo [ExecutionDataStore] based on [coveredIds] containing the internally gathered coverage information. + */ + private fun analyzeJacocoCoverage(coveredIds: Set): ExecutionDataStore { + val executionDataStore = ExecutionDataStore() val sortedCoveredIds = (additionalCoverage + coveredIds).sorted().toIntArray() for ((internalClassName, info) in instrumentedClassInfo) { // Determine the subarray of coverage IDs in sortedCoveredIds that contains the IDs generated while @@ -148,25 +178,19 @@ object CoverageRecorder { .forEach { classLocalEdgeId -> probes[classLocalEdgeId] = true } - outWriter.visitClassExecution(ExecutionData(info.classId, internalClassName, probes)) + executionDataStore.visitClassExecution(ExecutionData(info.classId, internalClassName, probes)) } - return outStream.toByteArray() + return executionDataStore } + /** + * Create a [CoverageBuilder] containing all classes matching the include/exclude pattern and their coverage statistics. + */ fun analyzeCoverage(coveredIds: Set): CoverageBuilder? { return try { val coverage = CoverageBuilder() analyzeAllUncoveredClasses(coverage) - val rawExecutionData = dumpJacocoCoverage(coveredIds) ?: return null - val executionDataStore = ExecutionDataStore() - val sessionInfoStore = SessionInfoStore() - ByteArrayInputStream(rawExecutionData).use { stream -> - ExecutionDataReader(stream).run { - setExecutionDataVisitor(executionDataStore) - setSessionInfoVisitor(sessionInfoStore) - read() - } - } + val executionDataStore = analyzeJacocoCoverage(coveredIds) for ((internalClassName, info) in instrumentedClassInfo) { EdgeCoverageInstrumentor(ClassInstrumentor.defaultEdgeCoverageStrategy, ClassInstrumentor.defaultCoverageMap, 0) .analyze( @@ -194,7 +218,6 @@ object CoverageRecorder { .asSequence() .map { it.replace('/', '.') } .toSet() - val emptyExecutionDataStore = ExecutionDataStore() ClassGraph() .enableClassInfo() .ignoreClassVisibility() @@ -205,6 +228,9 @@ object CoverageRecorder { "jaz", ) .scan().use { result -> + // ExecutionDataStore is used to look up existing coverage during analysis of the class files, + // no entries are added during that. Passing in an empty store is fine for uncovered files. + val emptyExecutionDataStore = ExecutionDataStore() result.allClasses .asSequence() .filter { classInfo -> classNameGlobber.includes(classInfo.name) } diff --git a/driver/coverage_tracker.cpp b/driver/coverage_tracker.cpp index edfde5c2..4be0db16 100644 --- a/driver/coverage_tracker.cpp +++ b/driver/coverage_tracker.cpp @@ -114,7 +114,7 @@ void CoverageTracker::ReplayInitialCoverage(JNIEnv &env) { AssertNoException(env); } -std::string CoverageTracker::ComputeCoverage(JNIEnv &env) { +void CoverageTracker::ReportCoverage(JNIEnv &env, std::string report_file) { uintptr_t *covered_pcs; size_t num_covered_pcs = __sanitizer_cov_get_observed_pcs(&covered_pcs); std::vector covered_edge_ids(covered_pcs, @@ -123,24 +123,43 @@ std::string CoverageTracker::ComputeCoverage(JNIEnv &env) { jclass coverage_recorder = env.FindClass(kCoverageRecorderClass); AssertNoException(env); - jmethodID coverage_recorder_compute_file_coverage = env.GetStaticMethodID( - coverage_recorder, "computeFileCoverage", "([I)Ljava/lang/String;"); + jmethodID coverage_recorder_dump_coverage_report = env.GetStaticMethodID( + coverage_recorder, "dumpCoverageReport", "([ILjava/lang/String;)V"); AssertNoException(env); jintArray covered_edge_ids_jni = env.NewIntArray(num_covered_pcs); AssertNoException(env); env.SetIntArrayRegion(covered_edge_ids_jni, 0, num_covered_pcs, covered_edge_ids.data()); AssertNoException(env); - auto file_coverage_jni = (jstring)(env.CallStaticObjectMethod( - coverage_recorder, coverage_recorder_compute_file_coverage, - covered_edge_ids_jni)); + jstring report_file_str = env.NewStringUTF(report_file.c_str()); + env.CallStaticObjectMethod(coverage_recorder, + coverage_recorder_dump_coverage_report, + covered_edge_ids_jni, report_file_str); AssertNoException(env); - auto file_coverage_cstr = env.GetStringUTFChars(file_coverage_jni, nullptr); +} + +void CoverageTracker::DumpCoverage(JNIEnv &env, std::string dump_file) { + uintptr_t *covered_pcs; + size_t num_covered_pcs = __sanitizer_cov_get_observed_pcs(&covered_pcs); + std::vector covered_edge_ids(covered_pcs, + covered_pcs + num_covered_pcs); + delete[] covered_pcs; + + jclass coverage_recorder = env.FindClass(kCoverageRecorderClass); + AssertNoException(env); + jmethodID coverage_recorder_dump_jacoco_coverage = env.GetStaticMethodID( + coverage_recorder, "dumpJacocoCoverage", "([ILjava/lang/String;)V"); + AssertNoException(env); + jintArray covered_edge_ids_jni = env.NewIntArray(num_covered_pcs); + AssertNoException(env); + env.SetIntArrayRegion(covered_edge_ids_jni, 0, num_covered_pcs, + covered_edge_ids.data()); AssertNoException(env); - std::string file_coverage(file_coverage_cstr); - env.ReleaseStringUTFChars(file_coverage_jni, file_coverage_cstr); + jstring dump_file_str = env.NewStringUTF(dump_file.c_str()); + env.CallStaticObjectMethod(coverage_recorder, + coverage_recorder_dump_jacoco_coverage, + covered_edge_ids_jni, dump_file_str); AssertNoException(env); - return file_coverage; } } // namespace jazzer diff --git a/driver/coverage_tracker.h b/driver/coverage_tracker.h index d5b80a02..455fe15e 100644 --- a/driver/coverage_tracker.h +++ b/driver/coverage_tracker.h @@ -44,6 +44,7 @@ class CoverageTracker { static void RecordInitialCoverage(JNIEnv &env); static void ReplayInitialCoverage(JNIEnv &env); - static std::string ComputeCoverage(JNIEnv &env); + static void ReportCoverage(JNIEnv &env, std::string); + static void DumpCoverage(JNIEnv &env, std::string); }; } // namespace jazzer diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index 10d90521..d7e2d7ce 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -62,6 +62,9 @@ DEFINE_string(reproducer_path, ".", DEFINE_string(coverage_report, "", "Path at which a coverage report is stored when the fuzzer " "exits. If left empty, no report is generated (default)"); +DEFINE_string(coverage_dump, "", + "Path at which a coverage dump is stored when the fuzzer " + "exits. If left empty, no dump is generated (default)"); DEFINE_string(autofuzz, "", "Fully qualified reference to a method on the classpath that " @@ -262,20 +265,18 @@ FuzzTargetRunner::FuzzTargetRunner( FuzzTargetRunner::~FuzzTargetRunner() { if (FLAGS_hooks && !FLAGS_coverage_report.empty()) { - std::string report = CoverageTracker::ComputeCoverage(jvm_.GetEnv()); - std::ofstream report_file(FLAGS_coverage_report); - if (report_file) { - report_file << report << std::flush; - } else { - LOG(ERROR) << "Failed to write coverage report to " - << FLAGS_coverage_report; - } + CoverageTracker::ReportCoverage(jvm_.GetEnv(), FLAGS_coverage_report); + } + if (FLAGS_hooks && !FLAGS_coverage_dump.empty()) { + CoverageTracker::DumpCoverage(jvm_.GetEnv(), FLAGS_coverage_dump); } if (fuzzer_tear_down_ != nullptr) { std::cerr << "calling fuzzer teardown function" << std::endl; jvm_.GetEnv().CallStaticVoidMethod(jclass_, fuzzer_tear_down_); - if (jthrowable exception = jvm_.GetEnv().ExceptionOccurred()) + if (jthrowable exception = jvm_.GetEnv().ExceptionOccurred()) { std::cerr << getStackTrace(exception) << std::endl; + _Exit(1); + } } } diff --git a/driver/libfuzzer_driver.cpp b/driver/libfuzzer_driver.cpp index d4f8803b..c3deec38 100644 --- a/driver/libfuzzer_driver.cpp +++ b/driver/libfuzzer_driver.cpp @@ -45,6 +45,9 @@ DECLARE_string(id_sync_file); // Defined in fuzz_target_runner.cpp DECLARE_string(coverage_report); +// Defined in fuzz_target_runner.cpp +DECLARE_string(coverage_dump); + // This symbol is defined by sanitizers if linked into Jazzer or in // sanitizer_symbols.cpp if no sanitizer is used. extern "C" void __sanitizer_set_death_callback(void (*)()); @@ -129,6 +132,11 @@ AbstractLibfuzzerDriver::AbstractLibfuzzerDriver( "fuzzing and has been disabled"; FLAGS_coverage_report = ""; } + if (!FLAGS_coverage_dump.empty()) { + LOG(WARNING) << "WARN: --coverage_dump does not support parallel " + "fuzzing and has been disabled"; + FLAGS_coverage_dump = ""; + } if (FLAGS_id_sync_file.empty()) { // Create an empty temporary file used for coverage ID synchronization and // pass its path to the agent in every child process. This requires adding diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 2870edef..19ccac22 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -77,12 +77,19 @@ java_fuzz_target_test( srcs = [ "src/test/java/com/example/CoverageFuzzer.java", ], + env = { + "COVERAGE_REPORT_FILE": "coverage.txt", + "COVERAGE_DUMP_FILE": "coverage.exec", + }, fuzzer_args = [ "-use_value_profile=1", - "--coverage_report=coverage.exec", + "--coverage_report=coverage.txt", + "--coverage_dump=coverage.exec", + "--instrumentation_includes=com.example.**", ], target_class = "com.example.CoverageFuzzer", verify_crash_input = False, + verify_crash_reproducer = False, deps = [ "@jazzer_jacoco//:jacoco_internal", ], diff --git a/tests/src/test/java/com/example/CoverageFuzzer.java b/tests/src/test/java/com/example/CoverageFuzzer.java index e3a4880b..33129701 100644 --- a/tests/src/test/java/com/example/CoverageFuzzer.java +++ b/tests/src/test/java/com/example/CoverageFuzzer.java @@ -18,16 +18,48 @@ package com.example; import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow; +import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; +import org.jacoco.core.data.ExecutionData; +import org.jacoco.core.data.ExecutionDataReader; +import org.jacoco.core.data.ExecutionDataStore; +import org.jacoco.core.data.SessionInfoStore; +/** + * Test of coverage report and dump. + * + * Internally, JaCoCo is used to gather coverage information to guide the fuzzer to cover new + * branches. This information can be dumped in the JaCoCo format and used to generate reports later + * on. The dump only contains classes with at least one coverage data point. A JaCoCo report will + * also include completely uncovered files based on the available classes in the stated jar files + * in the report command. + * + * A human-readable coverage report can be generated directly by Jazzer. It contains information + * on file level about all classes that should have been instrumented according to the + * instrumentation_includes and instrumentation_exclude filters. + */ @SuppressWarnings({"unused", "UnusedReturnValue"}) public final class CoverageFuzzer { + // Not used during fuzz run, so not included in the dump + public static class ClassNotToCover { + private final int i; + public ClassNotToCover(int i) { + this.i = i; + } + public int getI() { + return i; + } + } + + // Used in the fuzz run and included in the dump public static class ClassToCover { private final int i; @@ -58,67 +90,99 @@ public final class CoverageFuzzer { } public static void fuzzerTearDown() throws IOException { - List coverage = Files.readAllLines(Paths.get("./coverage.exec")); - assertEquals(871, coverage.size()); + assertCoverageReport(); + assertCoverageDump(); + } + private static void assertCoverageReport() throws IOException { + List coverage = Files.readAllLines(Paths.get(System.getenv("COVERAGE_REPORT_FILE"))); List> sections = new ArrayList<>(4); sections.add(new ArrayList<>()); coverage.forEach(l -> { if (l.isEmpty()) { sections.add(new ArrayList<>()); + } else { + sections.get(sections.size() - 1).add(l); } - sections.get(sections.size() - 1).add(l); }); List branchCoverage = sections.get(0); - assertEquals(217, branchCoverage.size()); + assertEquals(2, branchCoverage.size()); List lineCoverage = sections.get(1); - assertEquals(218, lineCoverage.size()); + assertEquals(2, lineCoverage.size()); List incompleteCoverage = sections.get(2); - assertEquals(218, incompleteCoverage.size()); + assertEquals(2, incompleteCoverage.size()); List missedCoverage = sections.get(3); - assertEquals(218, missedCoverage.size()); + assertEquals(2, missedCoverage.size()); - String branch = + assertNotNull( branchCoverage.stream() .filter(l -> l.startsWith(CoverageFuzzer.class.getSimpleName())) .findFirst() - .orElseThrow(() -> new IllegalStateException("Could not find branch coverage")); - // assertEquals("CoverageFuzzer.java: 11/16 (68.75%)", branch); + .orElseThrow(() -> new IllegalStateException("Could not find branch coverage"))); - String line = lineCoverage.stream() - .filter(l -> l.startsWith(CoverageFuzzer.class.getSimpleName())) - .findFirst() - .orElseThrow(() -> new IllegalStateException("Could not find line coverage")); - assertEquals("CoverageFuzzer.java: 15/61 (24.59%)", line); + assertNotNull( + lineCoverage.stream() + .filter(l -> l.startsWith(CoverageFuzzer.class.getSimpleName())) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Could not find line coverage"))); - String incomplete = + assertNotNull( incompleteCoverage.stream() .filter(l -> l.startsWith(CoverageFuzzer.class.getSimpleName())) .findFirst() - .orElseThrow(() -> new IllegalStateException("Could not find incomplete coverage")); - assertEquals("CoverageFuzzer.java: []", incomplete); + .orElseThrow(() -> new IllegalStateException("Could not find incomplete coverage"))); String missed = missedCoverage.stream() .filter(l -> l.startsWith(CoverageFuzzer.class.getSimpleName())) .findFirst() .orElseThrow(() -> new IllegalStateException("Could not find missed coverage")); - if (IntStream.rangeClosed(15, 44).anyMatch(i -> missed.contains(String.valueOf(i)))) { - throw new IllegalStateException("No coverage collected for ClassToCover"); + List missingLines = IntStream.rangeClosed(63, 79) + .mapToObj(i -> " " + i) + .filter(missed::contains) + .collect(Collectors.toList()); + if (!missingLines.isEmpty()) { + throw new IllegalStateException(String.format( + "Missing coverage for ClassToCover on lines %s", String.join(", ", missingLines))); } + } - // TODO switch to JaCoCo coverage report format - // CoverageBuilder coverage = new CoverageBuilder(); - // ExecutionDataStore executionDataStore = new ExecutionDataStore(); - // SessionInfoStore sessionInfoStore = new SessionInfoStore(); - // try (FileInputStream bais = new FileInputStream("./coverage.exec")) { - // ExecutionDataReader reader = new ExecutionDataReader(bais); - // reader.setExecutionDataVisitor(executionDataStore); - // reader.setSessionInfoVisitor(sessionInfoStore); - // reader.read(); - // } - // System.out.println(coverage.getClasses()); + private static void assertCoverageDump() throws IOException { + ExecutionDataStore executionDataStore = new ExecutionDataStore(); + SessionInfoStore sessionInfoStore = new SessionInfoStore(); + try (FileInputStream bais = new FileInputStream(System.getenv("COVERAGE_DUMP_FILE"))) { + ExecutionDataReader reader = new ExecutionDataReader(bais); + reader.setExecutionDataVisitor(executionDataStore); + reader.setSessionInfoVisitor(sessionInfoStore); + reader.read(); + } + assertEquals(2, executionDataStore.getContents().size()); + + ExecutionData coverageFuzzerCoverage = new ExecutionData(0, "", 0); + ExecutionData classToCoverCoverage = new ExecutionData(0, "", 0); + for (ExecutionData content : executionDataStore.getContents()) { + if (content.getName().endsWith("ClassToCover")) { + classToCoverCoverage = content; + } else { + coverageFuzzerCoverage = content; + } + } + + assertEquals("com/example/CoverageFuzzer", coverageFuzzerCoverage.getName()); + assertEquals(6, countHits(coverageFuzzerCoverage.getProbes())); + + assertEquals("com/example/CoverageFuzzer$ClassToCover", classToCoverCoverage.getName()); + assertEquals(11, countHits(classToCoverCoverage.getProbes())); + } + + private static int countHits(boolean[] probes) { + int count = 0; + for (boolean probe : probes) { + if (probe) + count++; + } + return count; } private static void assertEquals(T expected, T actual) { @@ -127,4 +191,10 @@ public final class CoverageFuzzer { String.format("Expected \"%s\", got \"%s\"", expected, actual)); } } + + private static void assertNotNull(T actual) { + if (actual == null) { + throw new IllegalStateException("Expected none null value, got null"); + } + } } -- cgit v1.2.3 From e865b3a1a794f4a78437f9de5a6901ebbc1cdbd6 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Wed, 16 Mar 2022 15:17:22 +0100 Subject: Build jacoco cli internally --- README.md | 46 +++++++++++++++++++++++++-------------- WORKSPACE.bazel | 8 ++++++- third_party/jacoco_internal.BUILD | 23 ++++++++++++++++++++ 3 files changed, 60 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index fdceec99..53da74d9 100644 --- a/README.md +++ b/README.md @@ -473,30 +473,44 @@ via `--ignore=,`. ### Export coverage information -The internally gathered JaCoCo coverage information can be exported in a human-readable and the JaCoCo dump format. -These can help identify code areas that can not be reached through fuzzing and perhaps need changes to make them more -accessible for the fuzzer. +The internally gathered JaCoCo coverage information can be exported in human-readable and JaCoCo execution data format +(`.exec`). These can help identify code areas that have not been covered by the fuzzer and thus may require more +comprehensive fuzz targets or a more extensive initial corpus to reach. The human-readable report contains coverage information, like branch and line coverage, on file level. It's useful to -get a quick overview about the overall coverage. The flag `--coverage_report=` can be used to generate the report. - -Similar to the JaCoCo `dump` command the flag `--coverage_dump=` specifies the coverage dump file, often called -`coverage.exec`, that should be generated after the fuzzing run. It contains a binary representation of the gathered -coverage data in the JaCoCo format. - -The JaCoCo `report` command can be used to generate reports based on the coverage dump. For example the -following command generates an HTML report in the folder `./report/` containing all classes available in `classes.jar` -and their coverage as captured in the export `coverage.exec`. +get a quick overview about the overall coverage. The flag `--coverage_report=` can be used to generate it. + +Similar to the JaCoCo `dump` command, the flag `--coverage_dump=` specifies a coverage dump file, often called +`jacoco.exec`, that is generated after the fuzzing run. It contains a binary representation of the gathered coverage +data in the JaCoCo format. + +The JaCoCo `report` command can be used to generate reports based on this coverage dump. **Note:** The version of the +JaCoCo agent used by Jazzer internally differs slightly from the official one. As a result, a similarly modified version +of the JaCoCo CLI tool has to be used to generate correct reports. The correct version is available at its +[release page](https://github.com/CodeIntelligenceTesting/jacoco/releases) as `zip` file. The report tool is located in +the `lib` folder and can be used as described in the JaCoCo +[CLI documentation](https://www.eclemma.org/jacoco/trunk/doc/cli.html). For example the following command generates an +HTML report in the folder `report` containing all classes available in `classes.jar` and their coverage as captured in +the export `coverage.exec`. Source code to include in the report is searched for in `some/path/to/sources`. +After execution the `index.html` file in the output folder can be opened in a browser. ```shell -java -jar jacococli.jar report coverage.exec \ +java -jar path/to/jacococli.jar report coverage.exec \ --classfiles classes.jar \ --sourcefiles some/path/to/sources \ - --html ./report/ \ + --html report \ --name FuzzCoverageReport ``` -More information about coverage report generation is available on the JaCoCo -[CLI documentation](https://www.eclemma.org/jacoco/trunk/doc/cli.html) page. +Furthermore, it's also possible to directly use the CLI tools of the internal JaCoCo version via Bazel with the target +`@jazzer_jacoco//:jacoco_cli`. The following command builds an HTML report similar to the one mentioned above: +```shell +bazel run @jazzer_jacoco//:jacoco_cli -- \ + report /coverage.exec \ + --classfiles /classes.jar \ + --sourcefiles /some/path/to/sources \ + --html /tmp/report/ \ + --name FuzzCoverageReport +``` ## Advanced fuzz targets diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index 8cf3953f..1f55a2e8 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -1,6 +1,6 @@ workspace(name = "jazzer") -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_jar") load("//:repositories.bzl", "jazzer_dependencies") jazzer_dependencies() @@ -64,6 +64,12 @@ http_archive( url = "https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/2.0.90.tar.gz", ) +http_jar( + name = "org_kohsuke_args4j_args4j", + sha256 = "91ddeaba0b24adce72291c618c00bbdce1c884755f6c4dba9c5c46e871c69ed6", + url = "https://repo1.maven.org/maven2/args4j/args4j/2.33/args4j-2.33.jar", +) + load("@com_grail_bazel_toolchain//toolchain:deps.bzl", "bazel_toolchain_dependencies") bazel_toolchain_dependencies() diff --git a/third_party/jacoco_internal.BUILD b/third_party/jacoco_internal.BUILD index 4731525f..09b70d16 100644 --- a/third_party/jacoco_internal.BUILD +++ b/third_party/jacoco_internal.BUILD @@ -4,6 +4,7 @@ java_library( "org.jacoco.core/src/org/jacoco/core/**/*.java", ]), resources = glob([ + "org.jacoco.core/src/org/jacoco/core/**/*.properties", "org.jacoco.core/src/org/jacoco/core/internal/flow/java_no_throw_methods_list.dat", ]), javacopts = [ @@ -16,3 +17,25 @@ java_library( ], visibility = ["//visibility:public"], ) + +java_binary( + name = "jacoco_cli", + srcs = glob([ + "org.jacoco.cli/src/org/jacoco/cli/**/*.java", + "org.jacoco.report/src/org/jacoco/report/**/*.java", + ]), + resources = glob([ + "org.jacoco.report/src/org/jacoco/report/**/*.css", + "org.jacoco.report/src/org/jacoco/report/**/*.gif", + "org.jacoco.report/src/org/jacoco/report/**/*.js", + ]), + main_class = "org.jacoco.cli.internal.Main", + deps = [ + "//:jacoco_internal", + "@org_kohsuke_args4j_args4j//jar", + "@org_ow2_asm_asm//jar", + "@org_ow2_asm_asm_commons//jar", + "@org_ow2_asm_asm_tree//jar", + ], + visibility = ["//visibility:public"], +) -- cgit v1.2.3 From e8dddc8bda327b82c69508bf9c49298768d442c4 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Mon, 21 Mar 2022 10:38:27 +0100 Subject: Relax visibility of agent classes Relaxed visibility eases the usage of agent classes by third party software. --- .../java/com/code_intelligence/jazzer/agent/CoverageIdStrategy.kt | 6 +++--- .../java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/CoverageIdStrategy.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/CoverageIdStrategy.kt index 6a6661f5..5d1d28e3 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/CoverageIdStrategy.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/CoverageIdStrategy.kt @@ -25,7 +25,7 @@ import java.util.UUID /** * Indicates a fatal failure to generate synchronized coverage IDs. */ -internal class CoverageIdException(cause: Throwable? = null) : +class CoverageIdException(cause: Throwable? = null) : RuntimeException("Failed to synchronize coverage IDs", cause) /** @@ -54,7 +54,7 @@ interface CoverageIdStrategy { * * It only prevents races within one VM instance. */ -internal class MemSyncCoverageIdStrategy : CoverageIdStrategy { +class MemSyncCoverageIdStrategy : CoverageIdStrategy { private var nextEdgeId = 0 @Synchronized @@ -69,7 +69,7 @@ internal class MemSyncCoverageIdStrategy : CoverageIdStrategy { * This class takes care of synchronizing the access to the file between multiple processes as long as the general * contract of [CoverageIdStrategy] is followed. */ -internal class FileSyncCoverageIdStrategy(private val idSyncFile: Path) : CoverageIdStrategy { +class FileSyncCoverageIdStrategy(private val idSyncFile: Path) : CoverageIdStrategy { private val uuid: UUID = UUID.randomUUID() private var idFileLock: FileLock? = null diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt index 8490fb68..fe2efd54 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt @@ -27,7 +27,7 @@ import kotlin.math.roundToInt import kotlin.system.exitProcess import kotlin.time.measureTimedValue -internal class RuntimeInstrumentor( +class RuntimeInstrumentor( private val instrumentation: Instrumentation, private val classesToFullyInstrument: ClassNameGlobber, private val classesToHookInstrument: ClassNameGlobber, -- cgit v1.2.3 From 4b5d73e917b4bdd0392249495b2cad162a322e45 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 21 Mar 2022 12:08:53 +0100 Subject: Report simple GC stats on timeouts Timeouts can be caused by extreme GC stalls, in which case these rough statistics can provide a first indication. --- .../java/com/code_intelligence/jazzer/runtime/ExceptionUtils.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/ExceptionUtils.kt b/agent/src/main/java/com/code_intelligence/jazzer/runtime/ExceptionUtils.kt index 31a61740..2cc3b22a 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/ExceptionUtils.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/ExceptionUtils.kt @@ -163,4 +163,10 @@ fun dumpAllStackTraces() { } System.err.println() } + System.err.println("Garbage collector stats:") + System.err.println( + ManagementFactory.getGarbageCollectorMXBeans().joinToString("\n", "\n", "\n") { + "${it.name}: ${it.collectionCount} collections took ${it.collectionTime}ms" + } + ) } -- cgit v1.2.3 From af52ce1427889a45584350bade985bc3cc1c20bc Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 21 Mar 2022 12:36:58 +0100 Subject: Call rules_jni_init from tests Ensures that rules_jni finds the Bazel-provided Java runtime and unlocks additional functionality such as coverage support and runfiles lookup. --- driver/BUILD.bazel | 1 + driver/test_main.cpp | 3 +++ 2 files changed, 4 insertions(+) diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index f7f2fc64..44fe2e3d 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -313,6 +313,7 @@ cc_library( srcs = ["test_main.cpp"], linkstatic = True, deps = [ + "@fmeum_rules_jni//jni:libjvm", "@googletest//:gtest", "@jazzer_com_github_gflags_gflags//:gflags", ], diff --git a/driver/test_main.cpp b/driver/test_main.cpp index bf33517f..14340b87 100644 --- a/driver/test_main.cpp +++ b/driver/test_main.cpp @@ -12,10 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include + #include "gflags/gflags.h" #include "gtest/gtest.h" int main(int argc, char **argv) { + rules_jni_init(argv[0]); ::testing::InitGoogleTest(&argc, argv); gflags::ParseCommandLineFlags(&argc, &argv, true); return RUN_ALL_TESTS(); -- cgit v1.2.3 From efe14ed2e440460bbc2d71786f8b5f72e8531a4f Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 21 Mar 2022 13:45:57 +0100 Subject: Simplify agent shading rules --- agent/agent_shade_rules | 12 ++++++------ third_party/jacoco_internal.jarjar | 0 2 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 third_party/jacoco_internal.jarjar diff --git a/agent/agent_shade_rules b/agent/agent_shade_rules index 84fb2a74..db0267f3 100644 --- a/agent/agent_shade_rules +++ b/agent/agent_shade_rules @@ -1,6 +1,6 @@ -rule com.github.** com.code_intelligence.jazzer.third_party.com.github.@1 -rule io.** com.code_intelligence.jazzer.third_party.io.@1 -rule kotlin.** com.code_intelligence.jazzer.third_party.kotlin.@1 -rule net.jodah.** com.code_intelligence.jazzer.third_party.net.jodah.@1 -rule nonapi.** com.code_intelligence.jazzer.third_party.nonapi.@1 -rule org.** com.code_intelligence.jazzer.third_party.org.@1 +rule com.github.** com.code_intelligence.jazzer.third_party.@0 +rule io.** com.code_intelligence.jazzer.third_party.@0 +rule kotlin.** com.code_intelligence.jazzer.third_party.@0 +rule net.jodah.** com.code_intelligence.jazzer.third_party.@0 +rule nonapi.** com.code_intelligence.jazzer.third_party.@0 +rule org.** com.code_intelligence.jazzer.third_party.@0 diff --git a/third_party/jacoco_internal.jarjar b/third_party/jacoco_internal.jarjar new file mode 100644 index 00000000..e69de29b -- cgit v1.2.3 From 4efa20852d1685973596c01542a5606dbfe58010 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 21 Mar 2022 14:07:51 +0100 Subject: Shade JaCoCo internally to allow for coverage collection in Jazzer If we do not shade JaCoCo internally but only when preparing the agent deploy jar, Jazzer tests themselves can't be instrumented with JaCoCo as our patched version is incompatible with the one used by coverage tools. --- agent/agent_shade_rules | 2 +- .../jazzer/instrumentor/CoverageRecorder.kt | 12 ++++++------ .../instrumentor/EdgeCoverageInstrumentor.kt | 22 +++++++++++----------- .../jazzer/instrumentor/StaticMethodStrategy.java | 2 +- .../instrumentor/CoverageInstrumentationTest.kt | 2 +- .../src/test/java/com/example/CoverageFuzzer.java | 10 ++++------ third_party/BUILD.bazel | 2 ++ third_party/jacoco_internal.BUILD | 22 ++++++++++++++++++++-- third_party/jacoco_internal.jarjar | 1 + 9 files changed, 47 insertions(+), 28 deletions(-) diff --git a/agent/agent_shade_rules b/agent/agent_shade_rules index db0267f3..0677cb1d 100644 --- a/agent/agent_shade_rules +++ b/agent/agent_shade_rules @@ -3,4 +3,4 @@ rule io.** com.code_intelligence.jazzer.third_party.@0 rule kotlin.** com.code_intelligence.jazzer.third_party.@0 rule net.jodah.** com.code_intelligence.jazzer.third_party.@0 rule nonapi.** com.code_intelligence.jazzer.third_party.@0 -rule org.** com.code_intelligence.jazzer.third_party.@0 +rule org.objectweb.** com.code_intelligence.jazzer.third_party.@0 diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt index 7ad59086..275057f0 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt @@ -15,14 +15,14 @@ package com.code_intelligence.jazzer.instrumentor import com.code_intelligence.jazzer.runtime.CoverageMap +import com.code_intelligence.jazzer.third_party.org.jacoco.core.analysis.CoverageBuilder +import com.code_intelligence.jazzer.third_party.org.jacoco.core.data.ExecutionData +import com.code_intelligence.jazzer.third_party.org.jacoco.core.data.ExecutionDataStore +import com.code_intelligence.jazzer.third_party.org.jacoco.core.data.ExecutionDataWriter +import com.code_intelligence.jazzer.third_party.org.jacoco.core.data.SessionInfo +import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.data.CRC64 import com.code_intelligence.jazzer.utils.ClassNameGlobber import io.github.classgraph.ClassGraph -import org.jacoco.core.analysis.CoverageBuilder -import org.jacoco.core.data.ExecutionData -import org.jacoco.core.data.ExecutionDataStore -import org.jacoco.core.data.ExecutionDataWriter -import org.jacoco.core.data.SessionInfo -import org.jacoco.core.internal.data.CRC64 import java.io.File import java.io.FileOutputStream import java.io.OutputStream diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt index 397629c9..8fb3dc2b 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt @@ -14,17 +14,17 @@ package com.code_intelligence.jazzer.instrumentor -import org.jacoco.core.analysis.Analyzer -import org.jacoco.core.analysis.ICoverageVisitor -import org.jacoco.core.data.ExecutionDataStore -import org.jacoco.core.internal.flow.ClassProbesAdapter -import org.jacoco.core.internal.flow.ClassProbesVisitor -import org.jacoco.core.internal.flow.IClassProbesAdapterFactory -import org.jacoco.core.internal.instr.ClassInstrumenter -import org.jacoco.core.internal.instr.IProbeArrayStrategy -import org.jacoco.core.internal.instr.IProbeInserterFactory -import org.jacoco.core.internal.instr.InstrSupport -import org.jacoco.core.internal.instr.ProbeInserter +import com.code_intelligence.jazzer.third_party.org.jacoco.core.analysis.Analyzer +import com.code_intelligence.jazzer.third_party.org.jacoco.core.analysis.ICoverageVisitor +import com.code_intelligence.jazzer.third_party.org.jacoco.core.data.ExecutionDataStore +import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.flow.ClassProbesAdapter +import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.flow.ClassProbesVisitor +import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.flow.IClassProbesAdapterFactory +import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.instr.ClassInstrumenter +import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.instr.IProbeArrayStrategy +import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.instr.IProbeInserterFactory +import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.instr.InstrSupport +import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.instr.ProbeInserter import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassVisitor import org.objectweb.asm.ClassWriter diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/StaticMethodStrategy.java b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/StaticMethodStrategy.java index ca0bd3d7..0512ec2a 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/StaticMethodStrategy.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/StaticMethodStrategy.java @@ -14,7 +14,7 @@ package com.code_intelligence.jazzer.instrumentor; -import org.jacoco.core.internal.instr.InstrSupport; +import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.instr.InstrSupport; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt index ea9c8bb9..ac010399 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt @@ -14,7 +14,7 @@ package com.code_intelligence.jazzer.instrumentor -import org.jacoco.core.internal.flow.JavaNoThrowMethods +import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.flow.JavaNoThrowMethods import org.junit.Test import org.objectweb.asm.MethodVisitor import org.objectweb.asm.Opcodes diff --git a/tests/src/test/java/com/example/CoverageFuzzer.java b/tests/src/test/java/com/example/CoverageFuzzer.java index 33129701..435bcde8 100644 --- a/tests/src/test/java/com/example/CoverageFuzzer.java +++ b/tests/src/test/java/com/example/CoverageFuzzer.java @@ -18,20 +18,18 @@ package com.example; import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow; +import com.code_intelligence.jazzer.third_party.org.jacoco.core.data.ExecutionData; +import com.code_intelligence.jazzer.third_party.org.jacoco.core.data.ExecutionDataReader; +import com.code_intelligence.jazzer.third_party.org.jacoco.core.data.ExecutionDataStore; +import com.code_intelligence.jazzer.third_party.org.jacoco.core.data.SessionInfoStore; import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; -import org.jacoco.core.data.ExecutionData; -import org.jacoco.core.data.ExecutionDataReader; -import org.jacoco.core.data.ExecutionDataStore; -import org.jacoco.core.data.SessionInfoStore; /** * Test of coverage report and dump. diff --git a/third_party/BUILD.bazel b/third_party/BUILD.bazel index 0643e4f6..a234e836 100644 --- a/third_party/BUILD.bazel +++ b/third_party/BUILD.bazel @@ -12,3 +12,5 @@ config_setting( }, visibility = ["//visibility:public"], ) + +exports_files(["jacoco_internal.jarjar"]) diff --git a/third_party/jacoco_internal.BUILD b/third_party/jacoco_internal.BUILD index 09b70d16..a9f964b9 100644 --- a/third_party/jacoco_internal.BUILD +++ b/third_party/jacoco_internal.BUILD @@ -1,5 +1,24 @@ -java_library( +load("@com_github_johnynek_bazel_jar_jar//:jar_jar.bzl", "jar_jar") + +java_import( name = "jacoco_internal", + jars = ["jacoco_internal_shaded.jar"], + deps = [ + "@org_ow2_asm_asm//jar", + "@org_ow2_asm_asm_commons//jar", + "@org_ow2_asm_asm_tree//jar", + ], + visibility = ["//visibility:public"], +) + +jar_jar( + name = "jacoco_internal_shaded", + input_jar = "libjacoco_internal_unshaded.jar", + rules = "@jazzer//third_party:jacoco_internal.jarjar", +) + +java_library( + name = "jacoco_internal_unshaded", srcs = glob([ "org.jacoco.core/src/org/jacoco/core/**/*.java", ]), @@ -15,7 +34,6 @@ java_library( "@org_ow2_asm_asm_commons//jar", "@org_ow2_asm_asm_tree//jar", ], - visibility = ["//visibility:public"], ) java_binary( diff --git a/third_party/jacoco_internal.jarjar b/third_party/jacoco_internal.jarjar index e69de29b..04aa333c 100644 --- a/third_party/jacoco_internal.jarjar +++ b/third_party/jacoco_internal.jarjar @@ -0,0 +1 @@ +rule org.jacoco.** com.code_intelligence.jazzer.third_party.@0 -- cgit v1.2.3 From 687721eb07f3618af38a4b83149ac125368aa894 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 21 Mar 2022 14:09:24 +0100 Subject: Slightly increase runs count on RegexRoadblocks test Without this change, the test does not pass with JDK 11 on Linux. --- sanitizers/src/test/java/com/example/BUILD.bazel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index e90faa2f..3ba71386 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -104,7 +104,7 @@ java_fuzz_target_test( fuzzer_args = [ # Limit the number of runs to verify that the regex roadblocks are # cleared quickly. - "-runs=15000", + "-runs=22000", ], target_class = "com.example.RegexRoadblocks", ) -- cgit v1.2.3 From 5b8914a1e2d3765b42883589a5da1229b37ab89a Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 23 Mar 2022 08:38:52 +0100 Subject: Write coverage profiles also on a finding --- driver/libfuzzer_fuzz_target.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/driver/libfuzzer_fuzz_target.cpp b/driver/libfuzzer_fuzz_target.cpp index d258e519..77e0ec7d 100644 --- a/driver/libfuzzer_fuzz_target.cpp +++ b/driver/libfuzzer_fuzz_target.cpp @@ -63,6 +63,14 @@ extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) { return 0; } +#ifndef _WIN32 +__attribute__((weak)) +#endif +extern "C" int +__llvm_profile_write_file(void) { + return 0; +} + // Called by the fuzzer for every fuzzing input. extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, const size_t size) { auto result = gLibfuzzerDriver->TestOneInput(data, size); @@ -80,6 +88,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, const size_t size) { } // Exit directly without invoking libFuzzer's atexit hook. driver_cleanup(); + // When running with LLVM coverage instrumentation, write out the profile as + // the exit hook that write it won't run. + __llvm_profile_write_file(); _Exit(Driver::kErrorExitCode); } return 0; -- cgit v1.2.3 From fa1bbd58b149dda205481667aade4460c225d43a Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 21 Mar 2022 13:39:21 +0100 Subject: Properly handle driver-defined symbols in Java-only tests Previously, we simply ignored an UnsatisfiedLinkError, now we prevent one from being thrown in the first place by loading and exporting the symbols of a library that provides mock implementations of the driver's symbols. --- .../runtime/TraceDataFlowNativeCallbacks.java | 13 ++----- .../java/com/code_intelligence/jazzer/BUILD.bazel | 8 +++++ .../com/code_intelligence/jazzer/MockDriver.java | 23 +++++++++++++ .../code_intelligence/jazzer/api/AutofuzzTest.java | 7 ++++ .../com/code_intelligence/jazzer/api/BUILD.bazel | 1 + driver/BUILD.bazel | 14 ++++++++ driver/rtld_global_hack.cpp | 40 ++++++++++++++++++++++ 7 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/BUILD.bazel create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/MockDriver.java create mode 100644 driver/rtld_global_hack.cpp diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java index d759e282..5a773560 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java @@ -22,18 +22,9 @@ import java.nio.charset.Charset; @SuppressWarnings("unused") final public class TraceDataFlowNativeCallbacks { static { - try { + // On Windows, we instead statically link the fuzzer callbacks into the driver. + if (!System.getProperty("os.name").startsWith("Windows")) { RulesJni.loadLibrary("jazzer_fuzzer_callbacks", TraceDataFlowNativeCallbacks.class); - } catch (UnsatisfiedLinkError e) { - // On Windows, we link the fuzzer callbacks statically instead and thus expect this library - // load to fail. - if (!System.getProperty("os.name").startsWith("Windows")) { - // In some scenarios (e.g. Java unit tests that do not go through the driver), this native - // library load will expectedly fail due to missing symbols. We make this case non-fatal as - // every actual usage of native methods in this class would result in another - // UnsatisfiedLinkError. - e.printStackTrace(); - } } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/BUILD.bazel new file mode 100644 index 00000000..f0511ec3 --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/BUILD.bazel @@ -0,0 +1,8 @@ +load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library") + +java_jni_library( + name = "MockDriver", + srcs = ["MockDriver.java"], + native_libs = ["//driver:mock_driver"], + visibility = ["//agent/src/test/java/com/code_intelligence/jazzer:__subpackages__"], +) diff --git a/agent/src/test/java/com/code_intelligence/jazzer/MockDriver.java b/agent/src/test/java/com/code_intelligence/jazzer/MockDriver.java new file mode 100644 index 00000000..82590b7d --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/MockDriver.java @@ -0,0 +1,23 @@ +// Copyright 2022 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; + +import com.github.fmeum.rules_jni.RulesJni; + +public final class MockDriver { + public static void load() { + RulesJni.loadLibrary("mock_driver", "/driver"); + } +} diff --git a/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java b/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java index 66a85db6..ea6025b6 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java @@ -19,8 +19,10 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; +import com.code_intelligence.jazzer.MockDriver; import java.util.Arrays; import java.util.Collections; +import org.junit.BeforeClass; import org.junit.Test; public class AutofuzzTest { @@ -44,6 +46,11 @@ public class AutofuzzTest { } } + @BeforeClass + public static void loadMockDriver() { + MockDriver.load(); + } + @Test public void testConsume() { FuzzedDataProvider data = CannedFuzzedDataProvider.create( diff --git a/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel index 9192ff77..52e5e661 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel +++ b/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel @@ -16,6 +16,7 @@ java_test( ], deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/api", + "//agent/src/test/java/com/code_intelligence/jazzer:MockDriver", "@maven//:junit_junit", ], ) diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index 44fe2e3d..7a2d8e33 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -1,3 +1,4 @@ +load("@fmeum_rules_jni//jni:defs.bzl", "cc_jni_library") load("//bazel:cc.bzl", "cc_17_library") cc_library( @@ -253,6 +254,19 @@ cc_binary( cc_library( name = "sanitizer_symbols_for_tests", srcs = ["sanitizer_symbols_for_tests.cpp"], + alwayslink = True, +) + +# This JNI library can be loaded by Java-only tests to provide mock definitions +# of the symbols exported by the real jazzer_driver. +cc_jni_library( + name = "mock_driver", + srcs = select({ + "@platforms//os:windows": [], + "//conditions:default": ["rtld_global_hack.cpp"], + }), + visibility = ["//agent/src/test/java:__subpackages__"], + deps = [":sanitizer_symbols_for_tests"], ) cc_test( diff --git a/driver/rtld_global_hack.cpp b/driver/rtld_global_hack.cpp new file mode 100644 index 00000000..539f9d3e --- /dev/null +++ b/driver/rtld_global_hack.cpp @@ -0,0 +1,40 @@ +// Copyright 2022 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 +#include + +#include + +// Upgrades the current shared library to RTLD_GLOBAL so that its exported +// symbols are used to resolve unresolved symbols in shared libraries loaded +// afterwards. +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + Dl_info info; + + if (!dladdr(reinterpret_cast(&JNI_OnLoad), &info) || + !info.dli_fname) { + fprintf(stderr, "Failed to determine our dli_fname\n"); + abort(); + } + + void *handle = dlopen(info.dli_fname, RTLD_NOLOAD | RTLD_GLOBAL | RTLD_NOW); + if (handle == nullptr) { + fprintf(stderr, "Failed to upgrade self to RTLD_GLOBAL: %s", dlerror()); + abort(); + } + dlclose(handle); + + return JNI_VERSION_1_8; +} -- cgit v1.2.3 From aa5c217b9a3b9c07d07f0fa9ed76001718595351 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 23 Mar 2022 08:12:49 +0100 Subject: Forward coverage information through cc_17_library --- bazel/cc.bzl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bazel/cc.bzl b/bazel/cc.bzl index 65d298d5..1fab30cd 100644 --- a/bazel/cc.bzl +++ b/bazel/cc.bzl @@ -38,6 +38,10 @@ def _cc_17_library_impl(ctx): files = library[DefaultInfo].files, ), library[CcInfo], + coverage_common.instrumented_files_info( + ctx, + dependency_attributes = ["library"], + ), ] _cc_17_library = rule( -- cgit v1.2.3 From 1b86a61322c019c86865467006b7b1b6a4efcacf Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 24 Mar 2022 09:17:01 +0100 Subject: Fix JDK version specification for Maven publish --- .bazelrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bazelrc b/.bazelrc index 62b1b956..fa16060b 100644 --- a/.bazelrc +++ b/.bazelrc @@ -48,4 +48,4 @@ build:ci --remote_timeout=3600 build:maven --config=toolchain build:maven --stamp build:maven --define "maven_repo=https://oss.sonatype.org/service/local/staging/deploy/maven2" -build:maven --java_runtime_version=localjdk_8 +build:maven --java_runtime_version=local_jdk_8 -- cgit v1.2.3 From 2b9e71a2e6ddeb37b18806d055e556b903c94ef3 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 23 Mar 2022 18:40:10 +0100 Subject: Catch exception if fuzz target class not found Previously, if the fuzz target class could not be found, Jazzer would throw an uncaught exception, which results in a c++abi terminate message as well as a JVM native stack trace. Now, a simple error message is shown followed by a graceful exit. --- driver/fuzz_target_runner.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index d7e2d7ce..6c943e68 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -181,7 +181,12 @@ FuzzTargetRunner::FuzzTargetRunner( } env.DeleteLocalRef(fuzz_target_class); - jclass_ = jvm.FindClass(FLAGS_target_class); + try { + jclass_ = jvm.FindClass(FLAGS_target_class); + } catch (const std::runtime_error &error) { + std::cerr << "ERROR: " << error.what() << std::endl; + exit(1); + } // one of the following functions is required: // public static void fuzzerTestOneInput(byte[] input) // public static void fuzzerTestOneInput(FuzzedDataProvider data) -- cgit v1.2.3 From cba9e9bedfe7ec118372f418f6ee9ef738a38d7e Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 24 Mar 2022 17:36:39 +0100 Subject: Add a Starlark rule for jar stripping This rule is easier to use (and thus potentially upstreamable) and correctly forwards coverage information. --- BUILD.bazel | 2 +- agent/BUILD.bazel | 20 +++++++---------- bazel/fuzz_target.bzl | 2 +- bazel/jar.bzl | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ docker/jazzer/Dockerfile | 2 +- driver/BUILD.bazel | 10 ++++----- 6 files changed, 72 insertions(+), 20 deletions(-) create mode 100644 bazel/jar.bzl diff --git a/BUILD.bazel b/BUILD.bazel index a5ba2f52..5afcd034 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -24,7 +24,7 @@ define_kt_toolchain( pkg_tar( name = "jazzer_release", srcs = [ - "//agent:jazzer_agent_deploy.jar", + "//agent:jazzer_agent_deploy", "//agent:jazzer_api_deploy.jar", "//driver:jazzer_driver", ], diff --git a/agent/BUILD.bazel b/agent/BUILD.bazel index 5868b02f..3489097e 100644 --- a/agent/BUILD.bazel +++ b/agent/BUILD.bazel @@ -1,5 +1,6 @@ load("@com_github_johnynek_bazel_jar_jar//:jar_jar.bzl", "jar_jar") load("//bazel:compat.bzl", "SKIP_ON_WINDOWS") +load("//bazel:jar.bzl", "strip_jar") load("//sanitizers:sanitizers.bzl", "SANITIZER_CLASSES") java_binary( @@ -16,15 +17,12 @@ java_binary( ], ) -genrule( +strip_jar( name = "jazzer_agent_deploy", - srcs = [":jazzer_agent_shaded_deploy"], - outs = ["jazzer_agent_deploy.jar"], - cmd = """ -$(execpath //bazel/tools/java:JarStripper) $(execpath :jazzer_agent_shaded_deploy) $@ \ -module-info.class -""", - exec_tools = ["//bazel/tools/java:JarStripper"], + jar = ":jazzer_agent_shaded_deploy", + paths_to_strip = [ + "module-info.class", + ], visibility = ["//visibility:public"], ) @@ -38,12 +36,10 @@ sh_test( name = "jazzer_agent_shading_test", srcs = ["verify_shading.sh"], args = [ - "$(rootpath jazzer_agent_deploy.jar)", + "$(rootpath :jazzer_agent_deploy)", ], data = [ - # sh_test does not correctly forward runfiles associated to targets (such as - # ":jazzer_agent_deploy"), so depend on a generated file instead. - "jazzer_agent_deploy.jar", + ":jazzer_agent_deploy", "@local_jdk//:bin/jar", ], target_compatible_with = SKIP_ON_WINDOWS, diff --git a/bazel/fuzz_target.bzl b/bazel/fuzz_target.bzl index 0ae67b53..4b9f6d85 100644 --- a/bazel/fuzz_target.bzl +++ b/bazel/fuzz_target.bzl @@ -89,7 +89,7 @@ def java_fuzz_target_test( ] + additional_args + fuzzer_args, data = [ ":%s_deploy.jar" % target_name, - "//agent:jazzer_agent_deploy.jar", + "//agent:jazzer_agent_deploy", "//agent:jazzer_api_deploy.jar", driver, ] + data, diff --git a/bazel/jar.bzl b/bazel/jar.bzl new file mode 100644 index 00000000..57c4c560 --- /dev/null +++ b/bazel/jar.bzl @@ -0,0 +1,56 @@ +# Copyright 2022 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. + +def _strip_jar(ctx): + out_jar = ctx.actions.declare_file(ctx.attr.name + ".jar") + + args = ctx.actions.args() + args.add(ctx.file.jar) + args.add(out_jar) + args.add_all(ctx.attr.paths_to_strip) + ctx.actions.run( + outputs = [out_jar], + inputs = [ctx.file.jar], + arguments = [args], + executable = ctx.executable._jar_stripper, + tools = [ctx.attr._jar_stripper[DefaultInfo].files_to_run], + ) + + return [ + DefaultInfo( + files = depset([out_jar]), + # Workaround for https://github.com/bazelbuild/bazel/issues/15043. + runfiles = ctx.runfiles(files = [out_jar]), + ), + coverage_common.instrumented_files_info( + ctx, + dependency_attributes = ["jar"], + ), + ] + +strip_jar = rule( + implementation = _strip_jar, + attrs = { + "jar": attr.label( + mandatory = True, + allow_single_file = [".jar"], + ), + "paths_to_strip": attr.string_list(), + "_jar_stripper": attr.label( + default = "//bazel/tools/java:JarStripper", + cfg = "exec", + executable = True, + ), + }, +) diff --git a/docker/jazzer/Dockerfile b/docker/jazzer/Dockerfile index 7b788d8d..137e707b 100644 --- a/docker/jazzer/Dockerfile +++ b/docker/jazzer/Dockerfile @@ -27,7 +27,7 @@ RUN curl -L https://github.com/bazelbuild/bazelisk/releases/download/v1.11.0/baz touch /usr/bin/ld.gold && \ BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 \ bazelisk build --config=toolchain --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-linux \ - //agent:jazzer_agent_deploy.jar //driver:jazzer_driver + //agent:jazzer_agent_deploy //driver:jazzer_driver FROM gcr.io/distroless/java diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index 7a2d8e33..51689bc7 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -164,7 +164,7 @@ cc_binary( "sanitizer_symbols.cpp", ], data = [ - "//agent:jazzer_agent_deploy.jar", + "//agent:jazzer_agent_deploy", ], linkopts = select({ "@platforms//os:windows": [], @@ -193,7 +193,7 @@ alias( cc_binary( name = "jazzer_driver_asan", data = [ - "//agent:jazzer_agent_deploy.jar", + "//agent:jazzer_agent_deploy", ], linkopts = [ ] + select({ @@ -225,7 +225,7 @@ cc_binary( cc_binary( name = "jazzer_driver_ubsan", data = [ - "//agent:jazzer_agent_deploy.jar", + "//agent:jazzer_agent_deploy", ], linkopts = [ ] + select({ @@ -277,7 +277,7 @@ cc_test( "--cp=jazzer/$(rootpath //driver/testdata:fuzz_target_mocks_deploy.jar)", ], data = [ - "//agent:jazzer_agent_deploy.jar", + "//agent:jazzer_agent_deploy", "//driver/testdata:fuzz_target_mocks_deploy.jar", ], includes = ["."], @@ -308,7 +308,7 @@ cc_test( "--cp=jazzer/$(rootpath //driver/testdata:fuzz_target_mocks_deploy.jar)", ], data = [ - "//agent:jazzer_agent_deploy.jar", + "//agent:jazzer_agent_deploy", "//driver/testdata:fuzz_target_mocks_deploy.jar", ], includes = ["."], -- cgit v1.2.3 From c6423d55e6d23317babad86d061725b88da62f1d Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 23 Mar 2022 17:32:10 +0100 Subject: Remove redundant prefix flags from cifuzz/jazzer entrypoint The working directory of the entrypoint is /fuzzing, so there is no need to explicitly specify --reproducer_path and -artifact_prefix. --- docker/jazzer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/jazzer/Dockerfile b/docker/jazzer/Dockerfile index 137e707b..be70f8c8 100644 --- a/docker/jazzer/Dockerfile +++ b/docker/jazzer/Dockerfile @@ -33,4 +33,4 @@ FROM gcr.io/distroless/java COPY --from=builder /root/jazzer/bazel-bin/agent/jazzer_agent_deploy.jar /root/jazzer/bazel-bin/driver/jazzer_driver /app/ WORKDIR /fuzzing -ENTRYPOINT [ "/app/jazzer_driver", "-artifact_prefix=/fuzzing/", "--reproducer_path=/fuzzing" ] +ENTRYPOINT [ "/app/jazzer_driver" ] -- cgit v1.2.3 From b6cc92b90c6aedf3dfbc9f50f4ba46a837b50b2b Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 23 Mar 2022 17:32:51 +0100 Subject: Ensure that libFuzzer can start subprocesses in cifuzz/jazzer libFuzzer uses system() to start subprocesses, which requires a shell to exist at /bin/sh. The regular distroless images don't include a shell, which breaks -fork and -minimize_crash. Since we only need a very minimal shell, the :debug variants with their busybox shell seem well suited for this purpose, with the only caveat that we have to symlink the shell binary into the correct place. --- docker/jazzer/Dockerfile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docker/jazzer/Dockerfile b/docker/jazzer/Dockerfile index be70f8c8..bddfcb53 100644 --- a/docker/jazzer/Dockerfile +++ b/docker/jazzer/Dockerfile @@ -29,8 +29,13 @@ RUN curl -L https://github.com/bazelbuild/bazelisk/releases/download/v1.11.0/baz bazelisk build --config=toolchain --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-linux \ //agent:jazzer_agent_deploy //driver:jazzer_driver -FROM gcr.io/distroless/java +# :debug includes a busybox shell, which is needed for libFuzzer's use of system() for e.g. the +# -fork and -minimize_crash commands. +FROM gcr.io/distroless/java:debug COPY --from=builder /root/jazzer/bazel-bin/agent/jazzer_agent_deploy.jar /root/jazzer/bazel-bin/driver/jazzer_driver /app/ +# system() expects the shell at /bin/sh, but the image has it at /busybox/sh. We create a symlink, +# but have to use the long form as a simple RUN also requires /bin/sh. +RUN ["/busybox/sh", "-c", "ln -s /busybox/sh /bin/sh"] WORKDIR /fuzzing ENTRYPOINT [ "/app/jazzer_driver" ] -- cgit v1.2.3 From 9d712e60be10b8caea5ea7939eb7a90872f701f6 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 29 Mar 2022 17:52:10 +0200 Subject: Update Bazel to 5.1.0 --- .bazelversion | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bazelversion b/.bazelversion index 0062ac97..831446cb 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -5.0.0 +5.1.0 -- cgit v1.2.3 From 7e6133f67f350efa087ecb6d77dbcfa4ea8e5eac Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 31 Mar 2022 09:55:26 +0200 Subject: Verify OSS-Fuzz build in CI --- .github/workflows/oss-fuzz.yml | 26 ++++++++++++++++++++++++++ .github/workflows/run-all-tests.yml | 8 +------- 2 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/oss-fuzz.yml diff --git a/.github/workflows/oss-fuzz.yml b/.github/workflows/oss-fuzz.yml new file mode 100644 index 00000000..a7fb5fce --- /dev/null +++ b/.github/workflows/oss-fuzz.yml @@ -0,0 +1,26 @@ +name: OSS-Fuzz build + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + + workflow_dispatch: + +jobs: + + oss_fuzz: + runs-on: ubuntu-20.04 + container: gcr.io/oss-fuzz-base/base-builder-jvm + + steps: + - uses: actions/checkout@v2 + + - name: Build Jazzer + # Keep in sync with https://github.com/google/oss-fuzz/blob/221b39181a372ff16c0c813c5963a08aa58f19e2/infra/base-images/base-builder/install_java.sh#L33. + run: bazel build --java_runtime_version=local_jdk_15 -c opt --cxxopt="-stdlib=libc++" --linkopt=-lc++ //agent:jazzer_agent_deploy.jar //driver:jazzer_driver //driver:jazzer_driver_asan //driver:jazzer_driver_ubsan //agent:jazzer_api_deploy.jar + + - name: Test Jazzer build + # Keep in sync with https://github.com/google/oss-fuzz/blob/221b39181a372ff16c0c813c5963a08aa58f19e2/infra/base-images/base-builder/install_java.sh#L35-L36. + run: "test -f bazel-bin/agent/jazzer_agent_deploy.jar && test -f bazel-bin/driver/jazzer_driver && test -f bazel-bin/driver/jazzer_driver_asan && test -f bazel-bin/driver/jazzer_driver_ubsan && test -f bazel-bin/agent/jazzer_api_deploy.jar" diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-all-tests.yml index 970117b0..55c406c8 100644 --- a/.github/workflows/run-all-tests.yml +++ b/.github/workflows/run-all-tests.yml @@ -15,13 +15,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-11, windows-latest] - jdk: [8, 15, 17] - exclude: - # Only test against JDK 15 with Ubuntu since this is what OSS-Fuzz uses. - - os: macos-11 - jdk: 15 - - os: windows-latest - jdk: 15 + jdk: [8, 17] include: - os: ubuntu-latest arch: "linux" -- cgit v1.2.3 From af40684eb16292cdeb16b9ae3f3cb8022c8d1b42 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 31 Mar 2022 10:00:05 +0200 Subject: Restore the //agent:jazzer_agent_deploy.jar target This target got lost in a recent change, but is part of Jazzer's public "build API": Without it, the OSS-Fuzz build failed. --- agent/BUILD.bazel | 1 + bazel/jar.bzl | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/agent/BUILD.bazel b/agent/BUILD.bazel index 3489097e..7e10b5a3 100644 --- a/agent/BUILD.bazel +++ b/agent/BUILD.bazel @@ -19,6 +19,7 @@ java_binary( strip_jar( name = "jazzer_agent_deploy", + out = "jazzer_agent_deploy.jar", jar = ":jazzer_agent_shaded_deploy", paths_to_strip = [ "module-info.class", diff --git a/bazel/jar.bzl b/bazel/jar.bzl index 57c4c560..1c982ba4 100644 --- a/bazel/jar.bzl +++ b/bazel/jar.bzl @@ -13,7 +13,9 @@ # limitations under the License. def _strip_jar(ctx): - out_jar = ctx.actions.declare_file(ctx.attr.name + ".jar") + out_jar = ctx.outputs.out + if out_jar == None: + out_jar = ctx.actions.declare_file(ctx.attr.name + ".jar") args = ctx.actions.args() args.add(ctx.file.jar) @@ -42,6 +44,7 @@ def _strip_jar(ctx): strip_jar = rule( implementation = _strip_jar, attrs = { + "out": attr.output(), "jar": attr.label( mandatory = True, allow_single_file = [".jar"], -- cgit v1.2.3 From 293edd4b6fcb0c54963aba21e5653e29ce3d5a59 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 31 Mar 2022 21:53:18 +0200 Subject: Remove redundant class path entry Java agent jars are automatically added to the system class loader, which means that an explicit class path entry for them is both redundant and potentially confusing. --- driver/jvm_tooling.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index 09bca90a..688d7c4a 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -240,8 +240,6 @@ JVM::JVM(std::string_view executable_path, std::string_view seed) { if (class_path_from_env) { class_path += absl::StrFormat(ARG_SEPARATOR "%s", class_path_from_env); } - class_path += absl::StrFormat(ARG_SEPARATOR "%s", - getInstrumentorAgentPath(executable_path)); LOG(INFO) << "got class path " << class_path; std::vector options; -- cgit v1.2.3 From 68b8042a6a5adf06409af664d18c1c1bbca5447d Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Fri, 1 Apr 2022 11:25:30 +0200 Subject: Validate replace hook on void method --- .../main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt | 2 +- .../code_intelligence/jazzer/instrumentor/InvalidHookMocks.java | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt index 60b20a1c..ff68ad94 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt @@ -97,7 +97,7 @@ class Hook private constructor( if (potentialHook.targetMethodName == "") { require(hookMethod.returnType.name == potentialHook.targetClassName) { "$potentialHook: return type must be ${potentialHook.targetClassName} to match target constructor" } } else if (potentialHook.targetReturnTypeDescriptor == "V") { - require(hookMethod.returnType.descriptor == "V") { "$potentialHook: return type must be void to match targetMethodDescriptor" } + require(hookMethod.returnType.descriptor == "V") { "$potentialHook: return type must be void" } } else { require( hookMethod.returnType.descriptor in listOf( diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java index 38d79b55..0df349ca 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java @@ -46,6 +46,13 @@ class InvalidHookMocks { return true; } + @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.System", targetMethod = "gc", + targetMethodDescriptor = "()V") + public static Object + invalidReplaceVoidMethod(MethodHandle method, Object thisObject, Object[] arguments, int hookId) { + return null; + } + @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.StringBuilder", targetMethod = "", targetMethodDescriptor = "(Ljava/lang/String;)V") public static Object -- cgit v1.2.3 From 5e9943d135e71bfa3f4a9b3948d6f4d677a9eb5d Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 4 Apr 2022 11:27:42 +0200 Subject: Use go install instead of go get in format CI job go get for binaries is no longer supported with Go 1.18. --- .github/workflows/check-formatting.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-formatting.yml b/.github/workflows/check-formatting.yml index c32aaff1..f410ef4c 100644 --- a/.github/workflows/check-formatting.yml +++ b/.github/workflows/check-formatting.yml @@ -28,8 +28,8 @@ jobs: sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-13 main' sudo apt-get install clang-format-13 curl -sSLO https://github.com/pinterest/ktlint/releases/download/0.42.1/ktlint && chmod a+x ktlint && sudo mv ktlint /usr/bin/ktlint - go get -u github.com/google/addlicense - go get github.com/bazelbuild/buildtools/buildifier + go install github.com/google/addlicense@latest + go install github.com/bazelbuild/buildtools/buildifier@latest - name: Run format.sh and print changes run: | -- cgit v1.2.3 From 35032dcbaf124133b506bd67ad791e92011db2ec Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 4 Apr 2022 09:40:46 +0200 Subject: Use common instead of build in .bazelrc where possible This ensures that environment variables such as CC are set consistently, preventing reanalysis when switching between bazel build and other Bazel commands. --- .bazelrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bazelrc b/.bazelrc index fa16060b..827f0f39 100644 --- a/.bazelrc +++ b/.bazelrc @@ -10,7 +10,7 @@ build -c opt build:linux --cxxopt='-std=c++17' build:macos --cxxopt='-std=c++17' build:windows --cxxopt='/std:c++17' -build --repo_env=CC=clang +common --repo_env=CC=clang build --incompatible_enable_cc_toolchain_resolution # Requires a relatively modern clang. build:ci --features=layering_check -- cgit v1.2.3 From 21e7044eceefe6b6eb1e2e6ffb121459c6e97682 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 4 Apr 2022 09:41:40 +0200 Subject: Simplify strip_jar implementation --- bazel/jar.bzl | 1 - 1 file changed, 1 deletion(-) diff --git a/bazel/jar.bzl b/bazel/jar.bzl index 1c982ba4..b4de3623 100644 --- a/bazel/jar.bzl +++ b/bazel/jar.bzl @@ -26,7 +26,6 @@ def _strip_jar(ctx): inputs = [ctx.file.jar], arguments = [args], executable = ctx.executable._jar_stripper, - tools = [ctx.attr._jar_stripper[DefaultInfo].files_to_run], ) return [ -- cgit v1.2.3 From d275e4ac1a7eeceb6de7c9e48d65f0b76e2574e7 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Mon, 4 Apr 2022 12:42:39 +0200 Subject: Support autofuzz of inner classes Method references use "." as separator between outer and inner classes. Class loaders expect a "$". Autofuzz should support the proper method reference format. --- .../jazzer/autofuzz/FuzzTarget.java | 35 ++++++++++++++-------- tests/BUILD.bazel | 20 +++++++++++++ .../java/com/example/AutofuzzInnerClassTarget.java | 32 ++++++++++++++++++++ 3 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 tests/src/test/java/com/example/AutofuzzInnerClassTarget.java diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java index b036f538..3b0d046b 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java @@ -84,19 +84,28 @@ public final class FuzzTarget { descriptor = null; } - Class targetClass; - try { - // Explicitly invoking static initializers to trigger some coverage in the code. - targetClass = Class.forName(className, true, ClassLoader.getSystemClassLoader()); - } catch (ClassNotFoundException e) { - System.err.printf( - "Failed to find class %s for autofuzz, please ensure it is contained in the classpath " - + "specified with --cp and specify the full package name%n", - className); - e.printStackTrace(); - System.exit(1); - return; - } + Class targetClass = null; + String targetClassName = className; + do { + try { + // Explicitly invoking static initializers to trigger some coverage in the code. + targetClass = Class.forName(targetClassName, true, ClassLoader.getSystemClassLoader()); + } catch (ClassNotFoundException e) { + int classSeparatorIndex = targetClassName.lastIndexOf("."); + if (classSeparatorIndex == -1) { + System.err.printf( + "Failed to find class %s for autofuzz, please ensure it is contained in the classpath " + + "specified with --cp and specify the full package name%n", + className); + e.printStackTrace(); + System.exit(1); + return; + } + StringBuilder classNameBuilder = new StringBuilder(targetClassName); + classNameBuilder.setCharAt(classSeparatorIndex, '$'); + targetClassName = classNameBuilder.toString(); + } + } while (targetClass == null); boolean isConstructor = methodName.equals("new"); if (isConstructor) { diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 19ccac22..bf0f95cc 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -94,3 +94,23 @@ java_fuzz_target_test( "@jazzer_jacoco//:jacoco_internal", ], ) + +java_library( + name = "autofuzz_inner_class_target", + srcs = ["src/test/java/com/example/AutofuzzInnerClassTarget.java"], + deps = [ + "//agent:jazzer_api_compile_only", + ], +) + +java_fuzz_target_test( + name = "AutofuzzInnerClassFuzzer", + expected_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow"], + fuzzer_args = [ + "--autofuzz=com.example.AutofuzzInnerClassTarget.Middle.Inner::test", + "--keep_going=1", + ], + runtime_deps = [ + ":autofuzz_inner_class_target", + ], +) diff --git a/tests/src/test/java/com/example/AutofuzzInnerClassTarget.java b/tests/src/test/java/com/example/AutofuzzInnerClassTarget.java new file mode 100644 index 00000000..16240eff --- /dev/null +++ b/tests/src/test/java/com/example/AutofuzzInnerClassTarget.java @@ -0,0 +1,32 @@ +/* + * Copyright 2022 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.example; + +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow; + +@SuppressWarnings("unused") +public class AutofuzzInnerClassTarget { + public static class Middle { + public static class Inner { + public void test(int a, int b) { + if (a == b) { + throw new FuzzerSecurityIssueLow("Finished Autofuzz Target"); + } + } + } + } +} -- cgit v1.2.3 From 9107b16b9ec05ba9c4f4ec047bd335639099126d Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 5 Apr 2022 10:46:26 +0200 Subject: Add the agent to the classpath with --nohooks Fixes https://github.com/google/oss-fuzz/issues/7507 --- driver/jvm_tooling.cpp | 9 ++++++- tests/BUILD.bazel | 16 +++++++++++ .../src/test/java/com/example/JazzerApiFuzzer.java | 31 ++++++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/src/test/java/com/example/JazzerApiFuzzer.java diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index 688d7c4a..35bd8bad 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -238,7 +238,14 @@ JVM::JVM(std::string_view executable_path, std::string_view seed) { std::string class_path = absl::StrFormat("-Djava.class.path=%s", FLAGS_cp); const auto class_path_from_env = std::getenv("JAVA_FUZZER_CLASSPATH"); if (class_path_from_env) { - class_path += absl::StrFormat(ARG_SEPARATOR "%s", class_path_from_env); + class_path += absl::StrCat(ARG_SEPARATOR, class_path_from_env); + } + if (!FLAGS_hooks) { + // A Java agent is implicitly added to the system class loader's classpath, + // so there is no need to add the Jazzer agent here if we are running with + // the agent enabled. + class_path += + absl::StrCat(ARG_SEPARATOR, getInstrumentorAgentPath(executable_path)); } LOG(INFO) << "got class path " << class_path; diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index bf0f95cc..4b704c90 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -114,3 +114,19 @@ java_fuzz_target_test( ":autofuzz_inner_class_target", ], ) + +JAZZER_API_TEST_CASES = { + "default": [], + "nohooks": ["--nohooks"], +} + +[ + java_fuzz_target_test( + name = "JazzerApiFuzzer_" + case, + srcs = ["src/test/java/com/example/JazzerApiFuzzer.java"], + expected_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow"], + fuzzer_args = args, + target_class = "com.example.JazzerApiFuzzer", + ) + for case, args in JAZZER_API_TEST_CASES.items() +] diff --git a/tests/src/test/java/com/example/JazzerApiFuzzer.java b/tests/src/test/java/com/example/JazzerApiFuzzer.java new file mode 100644 index 00000000..2428d21f --- /dev/null +++ b/tests/src/test/java/com/example/JazzerApiFuzzer.java @@ -0,0 +1,31 @@ +/* + * Copyright 2022 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.example; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow; +import com.code_intelligence.jazzer.api.Jazzer; + +public class JazzerApiFuzzer { + public static void fuzzerTestOneInput(FuzzedDataProvider data) { + Jazzer.exploreState(data.consumeByte(), 1); + Jazzer.guideTowardsEquality(data.consumeString(10), data.pickValue(new String[] {"foo"}), 1); + Jazzer.guideTowardsEquality(data.consumeBytes(10), new byte[] {}, 2); + Jazzer.guideTowardsContainment(data.consumeAsciiString(10), "bar", 2); + throw new FuzzerSecurityIssueLow("Jazzer API calls succeed"); + } +} -- cgit v1.2.3 From e54df3aeca43be2a5510baf6286b998aee200967 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 5 Apr 2022 11:46:52 +0200 Subject: Include JVM error files in test outputs --- .github/workflows/run-all-tests.yml | 4 +--- .../com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java | 5 +++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-all-tests.yml index 55c406c8..89587148 100644 --- a/.github/workflows/run-all-tests.yml +++ b/.github/workflows/run-all-tests.yml @@ -63,6 +63,4 @@ jobs: with: name: testlogs-${{ matrix.arch }}-${{ matrix.jdk }} # https://github.com/actions/upload-artifact/issues/92#issuecomment-711107236 - path: | - bazel-testlogs*/**/test.log - bazel-out*/**/hs_err_pid*.log + path: bazel-testlogs*/**/test.log diff --git a/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java b/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java index ecbd6e19..0cafc036 100644 --- a/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java +++ b/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java @@ -83,6 +83,11 @@ public class FuzzTargetTestWrapper { arguments) .collect(Collectors.toList()); processBuilder.inheritIO(); + if (JAZZER_CI) { + // Make JVM error reports available in test outputs. + processBuilder.environment().put( + "JAVA_TOOL_OPTIONS", String.format("-XX:ErrorFile=%s/hs_err_pid%%p.log", outputDir)); + } processBuilder.command(command); try { -- cgit v1.2.3 From 45843317e3ed558ffb4b4275cbf8a4e0a31ac741 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 5 Apr 2022 12:17:08 +0200 Subject: Refactor FuzzTargetRunner destructor --- driver/fuzz_target_runner.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index 6c943e68..1585750e 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -269,16 +269,17 @@ FuzzTargetRunner::FuzzTargetRunner( } FuzzTargetRunner::~FuzzTargetRunner() { + auto &env = jvm_.GetEnv(); if (FLAGS_hooks && !FLAGS_coverage_report.empty()) { - CoverageTracker::ReportCoverage(jvm_.GetEnv(), FLAGS_coverage_report); + CoverageTracker::ReportCoverage(env, FLAGS_coverage_report); } if (FLAGS_hooks && !FLAGS_coverage_dump.empty()) { - CoverageTracker::DumpCoverage(jvm_.GetEnv(), FLAGS_coverage_dump); + CoverageTracker::DumpCoverage(env, FLAGS_coverage_dump); } if (fuzzer_tear_down_ != nullptr) { std::cerr << "calling fuzzer teardown function" << std::endl; - jvm_.GetEnv().CallStaticVoidMethod(jclass_, fuzzer_tear_down_); - if (jthrowable exception = jvm_.GetEnv().ExceptionOccurred()) { + env.CallStaticVoidMethod(jclass_, fuzzer_tear_down_); + if (jthrowable exception = env.ExceptionOccurred()) { std::cerr << getStackTrace(exception) << std::endl; _Exit(1); } -- cgit v1.2.3 From 7464773467a97ab64bfedbf80d438a41c0b2799a Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 5 Apr 2022 12:19:27 +0200 Subject: Clear exceptions before calling into the JVM --- driver/fuzz_target_runner.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index 1585750e..0dabf3c8 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -242,6 +242,7 @@ FuzzTargetRunner::FuzzTargetRunner( } if (jthrowable exception = env.ExceptionOccurred()) { + env.ExceptionClear(); LOG(ERROR) << "== Java Exception in fuzzerInitialize: "; LOG(ERROR) << getStackTrace(exception); std::exit(1); @@ -280,6 +281,7 @@ FuzzTargetRunner::~FuzzTargetRunner() { std::cerr << "calling fuzzer teardown function" << std::endl; env.CallStaticVoidMethod(jclass_, fuzzer_tear_down_); if (jthrowable exception = env.ExceptionOccurred()) { + env.ExceptionClear(); std::cerr << getStackTrace(exception) << std::endl; _Exit(1); } -- cgit v1.2.3 From 933f0fcf829804c43302b6b7da77747750bb3302 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 5 Apr 2022 15:51:39 +0200 Subject: Move GetArrayLength calls out of {Get,Release}PrimitiveArrayCritical scope Fixes a few -Xcheck:jni warnings. --- .../code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/agent/src/main/native/com/code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp b/agent/src/main/native/com/code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp index da85efbb..6be2daed 100644 --- a/agent/src/main/native/com/code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp +++ b/agent/src/main/native/com/code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp @@ -47,9 +47,9 @@ inline __attribute__((always_inline)) void *idToPc(jint id) { [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceStrstr0( JNIEnv *env, jclass cls, jbyteArray needle, jint id) { + jint needle_length = env->GetArrayLength(needle); auto *needle_native = static_cast(env->GetPrimitiveArrayCritical(needle, nullptr)); - jint needle_length = env->GetArrayLength(needle); __sanitizer_weak_hook_memmem(idToPc(id), nullptr, 0, needle_native, needle_length, nullptr); env->ReleasePrimitiveArrayCritical(needle, needle_native, JNI_ABORT); @@ -66,12 +66,12 @@ JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_ Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceMemcmp( JNIEnv *env, jclass cls, jbyteArray b1, jbyteArray b2, jint result, jint id) { + jint b1_length = env->GetArrayLength(b1); + jint b2_length = env->GetArrayLength(b2); auto *b1_native = static_cast(env->GetPrimitiveArrayCritical(b1, nullptr)); auto *b2_native = static_cast(env->GetPrimitiveArrayCritical(b2, nullptr)); - jint b1_length = env->GetArrayLength(b1); - jint b2_length = env->GetArrayLength(b2); __sanitizer_weak_hook_compare_bytes(idToPc(id), b1_native, b2_native, b1_length, b2_length, result); env->ReleasePrimitiveArrayCritical(b1, b1_native, JNI_ABORT); -- cgit v1.2.3 From 2df09a545b5559ecdbba3ee25c7c9d16894a8f06 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 5 Apr 2022 16:59:06 +0200 Subject: Fix a JNI use-after-free in GetFinding --- driver/fuzz_target_runner.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index 0dabf3c8..196f9014 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -362,7 +362,11 @@ jthrowable FuzzTargetRunner::GetFinding() const { unprocessed_finding = reported_finding; } jthrowable processed_finding = preprocessException(unprocessed_finding); - env.DeleteLocalRef(unprocessed_finding); + // If preprocessException returns the same object that we passed to it, we + // must not delete the reference count. + if (!env.IsSameObject(processed_finding, unprocessed_finding)) { + env.DeleteLocalRef(unprocessed_finding); + } return processed_finding; } -- cgit v1.2.3 From c276a45046a93342e1924d5f14bc5d25d2aa8f00 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Wed, 6 Apr 2022 08:47:22 +0200 Subject: Use the official JaCoCo version Switch from the internal fork to the official JaCoCo version. This looses the call optimizations but removes the burden of maintaining a dedicated fork. Tests using the example fuzzers and JMH don't show huge performance differences. Some are more in favor of the fork, some of the official version. --- README.md | 19 +---- .../jazzer/instrumentor/BUILD.bazel | 37 +++++++++ .../instrumentor/EdgeCoverageInstrumentation.java | 74 ++++++++++++++++++ .../jazzer/instrumentor/EdgeCoverageTarget.java | 44 +++++++++++ .../code_intelligence/jazzer/runtime/BUILD.bazel | 1 + .../java/com/code_intelligence/jazzer/BUILD.bazel | 5 +- .../jazzer/instrumentor/AfterHooksPatchTest.kt | 2 + .../jazzer/instrumentor/BUILD.bazel | 1 + .../jazzer/instrumentor/BeforeHooksPatchTest.kt | 2 + .../instrumentor/CoverageInstrumentationTest.kt | 91 +++++++++++----------- .../jazzer/instrumentor/PatchTestUtils.kt | 52 ++++++++----- .../jazzer/instrumentor/ReplaceHooksPatchTest.kt | 2 + .../TraceDataFlowInstrumentationTest.kt | 2 + driver/BUILD.bazel | 9 +++ repositories.bzl | 6 +- .../src/test/java/com/example/CoverageFuzzer.java | 2 +- third_party/jacoco_internal.BUILD | 23 ------ 17 files changed, 263 insertions(+), 109 deletions(-) create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentation.java create mode 100644 agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageTarget.java diff --git a/README.md b/README.md index 53da74d9..2fd7542b 100644 --- a/README.md +++ b/README.md @@ -484,11 +484,9 @@ Similar to the JaCoCo `dump` command, the flag `--coverage_dump=` specifie `jacoco.exec`, that is generated after the fuzzing run. It contains a binary representation of the gathered coverage data in the JaCoCo format. -The JaCoCo `report` command can be used to generate reports based on this coverage dump. **Note:** The version of the -JaCoCo agent used by Jazzer internally differs slightly from the official one. As a result, a similarly modified version -of the JaCoCo CLI tool has to be used to generate correct reports. The correct version is available at its -[release page](https://github.com/CodeIntelligenceTesting/jacoco/releases) as `zip` file. The report tool is located in -the `lib` folder and can be used as described in the JaCoCo +The JaCoCo `report` command can be used to generate reports based on this coverage dump. The JaCoCo CLI tools are +available on their [GitHub release page](https://github.com/jacoco/jacoco/releases) as `zip` file. The report tool is +located in the `lib` folder and can be used as described in the JaCoCo [CLI documentation](https://www.eclemma.org/jacoco/trunk/doc/cli.html). For example the following command generates an HTML report in the folder `report` containing all classes available in `classes.jar` and their coverage as captured in the export `coverage.exec`. Source code to include in the report is searched for in `some/path/to/sources`. @@ -501,17 +499,6 @@ java -jar path/to/jacococli.jar report coverage.exec \ --name FuzzCoverageReport ``` -Furthermore, it's also possible to directly use the CLI tools of the internal JaCoCo version via Bazel with the target -`@jazzer_jacoco//:jacoco_cli`. The following command builds an HTML report similar to the one mentioned above: -```shell -bazel run @jazzer_jacoco//:jacoco_cli -- \ - report /coverage.exec \ - --classfiles /classes.jar \ - --sourcefiles /some/path/to/sources \ - --html /tmp/report/ \ - --name FuzzCoverageReport -``` - ## Advanced fuzz targets ### Fuzzing with Native Libraries diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel index f38cbc6a..ada825f1 100644 --- a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -1,3 +1,4 @@ +load("@fmeum_rules_jni//jni:defs.bzl", "cc_jni_library", "java_jni_library") load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") load("//agent/src/jmh/java/com/code_intelligence/jazzer:jmh.bzl", "JMH_TEST_ARGS") @@ -64,3 +65,39 @@ kt_jvm_library( "@org_ow2_asm_asm//jar", ], ) + +java_binary( + name = "EdgeCoverageInstrumentationBenchmark", + main_class = "org.openjdk.jmh.Main", + runtime_deps = [ + ":edge_coverage_instrumentation_benchmark", + ], +) + +java_test( + name = "EdgeCoverageInstrumentationBenchmarkTest", + args = JMH_TEST_ARGS, + main_class = "org.openjdk.jmh.Main", + # Directly invoke JMH's main without using a testrunner. + use_testrunner = False, + runtime_deps = [ + ":edge_coverage_instrumentation_benchmark", + ], +) + +java_jni_library( + name = "edge_coverage_instrumentation_benchmark", + srcs = [ + "EdgeCoverageInstrumentation.java", + "EdgeCoverageTarget.java", + ], + native_libs = ["//driver:coverage_tracker_jni"], + plugins = ["//agent/src/jmh/java/com/code_intelligence/jazzer:JmhGeneratorAnnotationProcessor"], + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:coverage_map", + "//agent/src/test/java/com/code_intelligence/jazzer:MockDriver", + "//agent/src/test/java/com/code_intelligence/jazzer/instrumentor:patch_test_utils", + "@maven//:org_openjdk_jmh_jmh_core", + ], +) diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentation.java b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentation.java new file mode 100644 index 00000000..c2c2697d --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentation.java @@ -0,0 +1,74 @@ +// Copyright 2022 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.instrumentor; + +import static com.code_intelligence.jazzer.instrumentor.PatchTestUtils.*; +import static java.lang.invoke.MethodHandles.lookup; +import static java.lang.invoke.MethodType.methodType; + +import com.code_intelligence.jazzer.MockDriver; +import com.code_intelligence.jazzer.runtime.CoverageMap; +import com.github.fmeum.rules_jni.RulesJni; +import java.io.*; +import java.lang.invoke.*; +import java.nio.file.Files; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.*; + +@Warmup(iterations = 10, time = 3) +@Measurement(iterations = 10, time = 3) +@Fork(value = 3) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Benchmark) +@SuppressWarnings("unused") +public class EdgeCoverageInstrumentation { + private MethodHandle exampleMethod; + + static { + MockDriver.load(); + RulesJni.loadLibrary("coverage_tracker_jni", "/driver"); + } + + @Setup + public void setupInstrumentation() throws Throwable { + String outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR"); + if (outDir == null || outDir.isEmpty()) { + outDir = + Files.createTempDirectory(EdgeCoverageInstrumentation.class.getSimpleName()).toString(); + } + + byte[] originalBytecode = classToBytecode(EdgeCoverageTarget.class); + dumpBytecode(outDir, EdgeCoverageTarget.class.getName(), originalBytecode); + + byte[] patchedBytecode = applyInstrumentation(originalBytecode); + dumpBytecode(outDir, EdgeCoverageTarget.class.getName() + ".patched", patchedBytecode); + + Class patchedClass = bytecodeToClass(EdgeCoverageTarget.class.getName(), patchedBytecode); + Object obj = lookup().findConstructor(patchedClass, methodType(void.class)).invoke(); + exampleMethod = lookup().bind(obj, "exampleMethod", methodType(List.class)); + } + + private byte[] applyInstrumentation(byte[] bytecode) { + return new EdgeCoverageInstrumentor(new StaticMethodStrategy(), CoverageMap.class, 0) + .instrument(bytecode); + } + + @Benchmark + public Object benchmarkInstrumentedMethodCall() throws Throwable { + return exampleMethod.invoke(); + } +} diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageTarget.java b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageTarget.java new file mode 100644 index 00000000..57eb8807 --- /dev/null +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageTarget.java @@ -0,0 +1,44 @@ +/* + * Copyright 2022 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.instrumentor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; + +public class EdgeCoverageTarget { + private final Random rnd = new Random(); + + @SuppressWarnings("unused") + public List exampleMethod() { + ArrayList rnds = new ArrayList<>(); + rnds.add(rnd.nextInt()); + rnds.add(rnd.nextInt()); + rnds.add(rnd.nextInt()); + rnds.add(rnd.nextInt()); + rnds.add(rnd.nextInt()); + int i = rnd.nextInt() + rnd.nextInt(); + if (i > 0 && i < Integer.MAX_VALUE / 2) { + i--; + } else { + i++; + } + rnds.add(i); + return rnds.stream().map(n -> n + 1).collect(Collectors.toList()); + } +} 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 bd2dcf66..b5f2d317 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 @@ -19,6 +19,7 @@ java_library( name = "coverage_map", srcs = ["CoverageMap.java"], visibility = [ + "//agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor:__pkg__", "//driver/testdata:__pkg__", ], deps = [ diff --git a/agent/src/test/java/com/code_intelligence/jazzer/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/BUILD.bazel index f0511ec3..f2595077 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/BUILD.bazel +++ b/agent/src/test/java/com/code_intelligence/jazzer/BUILD.bazel @@ -4,5 +4,8 @@ java_jni_library( name = "MockDriver", srcs = ["MockDriver.java"], native_libs = ["//driver:mock_driver"], - visibility = ["//agent/src/test/java/com/code_intelligence/jazzer:__subpackages__"], + visibility = [ + "//agent/src/jmh/java/com/code_intelligence/jazzer:__subpackages__", + "//agent/src/test/java/com/code_intelligence/jazzer:__subpackages__", + ], ) diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt index e47dd30b..c5a2e156 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt @@ -14,6 +14,8 @@ package com.code_intelligence.jazzer.instrumentor +import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.bytecodeToClass +import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.classToBytecode import org.junit.Test import java.io.File diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel index 1bdcd194..036559ec 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -7,6 +7,7 @@ kt_jvm_library( "DynamicTestContract.java", "PatchTestUtils.kt", ], + visibility = ["//visibility:public"], ) wrapped_kt_jvm_test( diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt index ced5896c..4fde7ee1 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt @@ -14,6 +14,8 @@ package com.code_intelligence.jazzer.instrumentor +import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.bytecodeToClass +import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.classToBytecode import org.junit.Test import java.io.File diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt index ac010399..f2cf2f08 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt @@ -14,7 +14,8 @@ package com.code_intelligence.jazzer.instrumentor -import com.code_intelligence.jazzer.third_party.org.jacoco.core.internal.flow.JavaNoThrowMethods +import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.bytecodeToClass +import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.classToBytecode import org.junit.Test import org.objectweb.asm.MethodVisitor import org.objectweb.asm.Opcodes @@ -24,16 +25,9 @@ import kotlin.test.assertEquals /** * Amends the instrumentation performed by [strategy] to call the map's public static void method * updated() after every update to coverage counters. - * - * Note: Calling this method also enables the testing mode of [JavaNoThrowMethods] globally, which - * means that calls to methods in com.code_intelligence.jazzer.** will also be instrumented. */ private fun makeTestable(strategy: EdgeCoverageStrategy): EdgeCoverageStrategy = object : EdgeCoverageStrategy by strategy { - init { - JavaNoThrowMethods.isTesting = true - } - override fun instrumentControlFlowEdge( mv: MethodVisitor, edgeId: Int, @@ -72,26 +66,34 @@ private fun assertControlFlow(expectedLocations: List) { assertEquals(expectedLocations, MockCoverageMap.locations.toList()) } +@Suppress("unused") class CoverageInstrumentationTest { private val constructorReturn = 0 - private val ifFirstBranch = 1 - @Suppress("unused") - private val ifSecondBranch = 2 - private val ifEnd = 3 - private val outerForCondition = 4 - private val innerForBodyIfFirstRun = 6 - private val innerForBodyIfSecondRun = 5 - private val innerForIncrementCounter = 7 - private val outerForIncrementCounter = 8 - private val afterFooInvocation = 9 - private val beforeReturn = 10 - private val fooAfterBarInvocation = 11 - private val fooBeforeReturn = 12 - private val barAfterMapPutInvocation = 13 - private val barBeforeReturn = 14 - @Suppress("unused") - private val bazReturn = 15 + + private val mapConstructor = 1 + private val addFor0 = 2 + private val addFor1 = 3 + private val addFor2 = 4 + private val addFor3 = 5 + private val addFor4 = 6 + private val addFoobar = 7 + + private val ifTrueBranch = 8 + private val addBlock1 = 9 + private val ifFalseBranch = 10 + private val ifEnd = 11 + + private val outerForCondition = 12 + private val innerForCondition = 13 + private val innerForBodyIfTrueBranch = 14 + private val innerForBodyIfFalseBranch = 15 + private val innerForBodyPutInvocation = 16 + private val outerForIncrementCounter = 17 + + private val afterFooInvocation = 18 + private val fooAfterBarInvocation = 19 + private val barAfterPutInvocation = 20 @Test fun testOriginal() { @@ -103,31 +105,32 @@ class CoverageInstrumentationTest { MockCoverageMap.clear() assertSelfCheck(getInstrumentedInstrumentationTargetInstance()) - val innerForFirstRunControlFlow = mutableListOf().apply { + val mapControlFlow = listOf(mapConstructor, addFor0, addFor1, addFor2, addFor3, addFor4, addFoobar) + val ifControlFlow = listOf(ifTrueBranch, addBlock1, ifEnd) + val forFirstRunControlFlow = mutableListOf().apply { + add(outerForCondition) repeat(5) { - addAll(listOf(innerForBodyIfFirstRun, innerForIncrementCounter)) + addAll(listOf(innerForCondition, innerForBodyIfFalseBranch, innerForBodyPutInvocation)) } + add(outerForIncrementCounter) }.toList() - val innerForSecondRunControlFlow = mutableListOf().apply { + val forSecondRunControlFlow = mutableListOf().apply { + add(outerForCondition) repeat(5) { - addAll(listOf(innerForBodyIfSecondRun, innerForIncrementCounter)) + addAll(listOf(innerForCondition, innerForBodyIfTrueBranch, innerForBodyPutInvocation)) } + add(outerForIncrementCounter) }.toList() - val outerForControlFlow = - listOf(outerForCondition) + - innerForFirstRunControlFlow + - listOf(outerForIncrementCounter, outerForCondition) + - innerForSecondRunControlFlow + - listOf(outerForIncrementCounter) - + val forControlFlow = forFirstRunControlFlow + forSecondRunControlFlow + val fooCallControlFlow = listOf( + barAfterPutInvocation, fooAfterBarInvocation, afterFooInvocation + ) assertControlFlow( - listOf(constructorReturn, ifFirstBranch, ifEnd) + - outerForControlFlow + - listOf( - barAfterMapPutInvocation, barBeforeReturn, - fooAfterBarInvocation, fooBeforeReturn, - afterFooInvocation, beforeReturn - ) + listOf(constructorReturn) + + mapControlFlow + + ifControlFlow + + forControlFlow + + fooCallControlFlow ) } @@ -140,7 +143,7 @@ class CoverageInstrumentationTest { // The constructor of the target is run only once. val takenOnceEdge = constructorReturn // Control flows through the first if branch once per run. - val takenOnEveryRunEdge = ifFirstBranch + val takenOnEveryRunEdge = ifTrueBranch var lastCounter = 0.toUByte() for (i in 1..600) { diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt index f286d03f..00279c35 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt @@ -14,30 +14,40 @@ package com.code_intelligence.jazzer.instrumentor -fun classToBytecode(targetClass: Class<*>): ByteArray { - return ClassLoader - .getSystemClassLoader() - .getResourceAsStream("${targetClass.name.replace('.', '/')}.class")!! - .use { - it.readBytes() - } -} +import java.io.FileOutputStream -fun bytecodeToClass(name: String, bytecode: ByteArray): Class<*> { - return BytecodeClassLoader(name, bytecode).loadClass(name) -} +object PatchTestUtils { + @JvmStatic + fun classToBytecode(targetClass: Class<*>): ByteArray { + return ClassLoader + .getSystemClassLoader() + .getResourceAsStream("${targetClass.name.replace('.', '/')}.class")!! + .use { + it.readBytes() + } + } -/** - * A ClassLoader that dynamically loads a single specified class from byte code and delegates all other class loads to - * its own ClassLoader. - */ -class BytecodeClassLoader(val className: String, private val classBytecode: ByteArray) : - ClassLoader(BytecodeClassLoader::class.java.classLoader) { - override fun loadClass(name: String): Class<*> { - if (name != className) - return super.loadClass(name) + @JvmStatic + fun bytecodeToClass(name: String, bytecode: ByteArray): Class<*> { + return BytecodeClassLoader(name, bytecode).loadClass(name) + } + + @JvmStatic + public fun dumpBytecode(outDir: String, name: String, originalBytecode: ByteArray) { + FileOutputStream("$outDir/$name.class").use { fos -> fos.write(originalBytecode) } + } - return defineClass(className, classBytecode, 0, classBytecode.size) + /** + * A ClassLoader that dynamically loads a single specified class from byte code and delegates all other class loads to + * its own ClassLoader. + */ + class BytecodeClassLoader(val className: String, private val classBytecode: ByteArray) : + ClassLoader(BytecodeClassLoader::class.java.classLoader) { + override fun loadClass(name: String): Class<*> { + if (name != className) + return super.loadClass(name) + return defineClass(className, classBytecode, 0, classBytecode.size) + } } } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt index 5759246e..b6266d12 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt @@ -14,6 +14,8 @@ package com.code_intelligence.jazzer.instrumentor +import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.bytecodeToClass +import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.classToBytecode import org.junit.Test import java.io.File diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt index c6fd218f..4d4b0318 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt @@ -14,6 +14,8 @@ package com.code_intelligence.jazzer.instrumentor +import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.bytecodeToClass +import com.code_intelligence.jazzer.instrumentor.PatchTestUtils.classToBytecode import org.junit.Test import java.io.File diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index 51689bc7..79358612 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -52,6 +52,15 @@ cc_library( alwayslink = True, ) +cc_jni_library( + name = "coverage_tracker_jni", + visibility = ["//agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor:__pkg__"], + deps = [ + ":coverage_tracker", + ":sanitizer_symbols_for_tests", + ], +) + cc_library( name = "coverage_tracker", srcs = ["coverage_tracker.cpp"], diff --git a/repositories.bzl b/repositories.bzl index 36e9aebd..214e829e 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -136,9 +136,9 @@ def jazzer_dependencies(): Label("//third_party:jacoco-make-probe-adapter-subclassable.patch"), Label("//third_party:jacoco-make-probe-inserter-subclassable.patch"), ], - sha256 = "65bfdf38047a3bbefc5f68b180ec4a933068a547d4f569578f3ce3f7355168ba", - strip_prefix = "jacoco-0.8.7-jazzer-1", - url = "https://github.com/CodeIntelligenceTesting/jacoco/archive/refs/tags/v0.8.7-jazzer+1.tar.gz", + sha256 = "d764c2c02caf8836a12ac582263a109dcac9c1389d3ddee0548aed1322f6e01c", + strip_prefix = "jacoco-0.8.7", + url = "https://github.com/jacoco/jacoco/archive/refs/tags/v0.8.7.tar.gz", ) maybe( diff --git a/tests/src/test/java/com/example/CoverageFuzzer.java b/tests/src/test/java/com/example/CoverageFuzzer.java index 435bcde8..8f63639d 100644 --- a/tests/src/test/java/com/example/CoverageFuzzer.java +++ b/tests/src/test/java/com/example/CoverageFuzzer.java @@ -168,7 +168,7 @@ public final class CoverageFuzzer { } assertEquals("com/example/CoverageFuzzer", coverageFuzzerCoverage.getName()); - assertEquals(6, countHits(coverageFuzzerCoverage.getProbes())); + assertEquals(7, countHits(coverageFuzzerCoverage.getProbes())); assertEquals("com/example/CoverageFuzzer$ClassToCover", classToCoverCoverage.getName()); assertEquals(11, countHits(classToCoverCoverage.getProbes())); diff --git a/third_party/jacoco_internal.BUILD b/third_party/jacoco_internal.BUILD index a9f964b9..d16a166b 100644 --- a/third_party/jacoco_internal.BUILD +++ b/third_party/jacoco_internal.BUILD @@ -24,7 +24,6 @@ java_library( ]), resources = glob([ "org.jacoco.core/src/org/jacoco/core/**/*.properties", - "org.jacoco.core/src/org/jacoco/core/internal/flow/java_no_throw_methods_list.dat", ]), javacopts = [ "-Xep:EqualsHashCode:OFF", @@ -35,25 +34,3 @@ java_library( "@org_ow2_asm_asm_tree//jar", ], ) - -java_binary( - name = "jacoco_cli", - srcs = glob([ - "org.jacoco.cli/src/org/jacoco/cli/**/*.java", - "org.jacoco.report/src/org/jacoco/report/**/*.java", - ]), - resources = glob([ - "org.jacoco.report/src/org/jacoco/report/**/*.css", - "org.jacoco.report/src/org/jacoco/report/**/*.gif", - "org.jacoco.report/src/org/jacoco/report/**/*.js", - ]), - main_class = "org.jacoco.cli.internal.Main", - deps = [ - "//:jacoco_internal", - "@org_kohsuke_args4j_args4j//jar", - "@org_ow2_asm_asm//jar", - "@org_ow2_asm_asm_commons//jar", - "@org_ow2_asm_asm_tree//jar", - ], - visibility = ["//visibility:public"], -) -- cgit v1.2.3 From bc620dc3f57f59d29fe10c3e98cb811669316837 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 4 Apr 2022 09:44:30 +0200 Subject: Print JVM stack traces on a fatal sanitizer finding --- driver/libfuzzer_driver.cpp | 12 ------------ driver/libfuzzer_fuzz_target.cpp | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/driver/libfuzzer_driver.cpp b/driver/libfuzzer_driver.cpp index c3deec38..d808e2c4 100644 --- a/driver/libfuzzer_driver.cpp +++ b/driver/libfuzzer_driver.cpp @@ -48,18 +48,6 @@ DECLARE_string(coverage_report); // Defined in fuzz_target_runner.cpp DECLARE_string(coverage_dump); -// This symbol is defined by sanitizers if linked into Jazzer or in -// sanitizer_symbols.cpp if no sanitizer is used. -extern "C" void __sanitizer_set_death_callback(void (*)()); - -// We apply a patch to libFuzzer to make it call this function instead of -// __sanitizer_set_death_callback to pass us the death callback. -extern "C" [[maybe_unused]] void __jazzer_set_death_callback( - void (*callback)()) { - jazzer::AbstractLibfuzzerDriver::libfuzzer_print_crashing_input_ = callback; - __sanitizer_set_death_callback(callback); -} - namespace { std::vector modified_argv; diff --git a/driver/libfuzzer_fuzz_target.cpp b/driver/libfuzzer_fuzz_target.cpp index 77e0ec7d..9c2c6ecb 100644 --- a/driver/libfuzzer_fuzz_target.cpp +++ b/driver/libfuzzer_fuzz_target.cpp @@ -50,6 +50,29 @@ extern "C" void driver_cleanup() { gLibfuzzerDriver.reset(nullptr); } +// This symbol is defined by sanitizers if linked into Jazzer or in +// sanitizer_symbols.cpp if no sanitizer is used. +extern "C" void __sanitizer_set_death_callback(void (*)()); + +// We apply a patch to libFuzzer to make it call this function instead of +// __sanitizer_set_death_callback to pass us the death callback. +extern "C" [[maybe_unused]] void __jazzer_set_death_callback( + void (*callback)()) { + jazzer::AbstractLibfuzzerDriver::libfuzzer_print_crashing_input_ = callback; + __sanitizer_set_death_callback([]() { + jazzer::DumpJvmStackTraces(); + jazzer::AbstractLibfuzzerDriver::libfuzzer_print_crashing_input_(); + // Ideally, we would be able to call driver_cleanup here to perform a + // graceful shutdown of the JVM. However, doing this directly results in a + // nested bug report by ASan or UBSan, likely because something about the + // stack/thread context in which they generate reports is incompatible with + // the JVM shutdown process. use_sigaltstack=0 does not help though, so this + // might be on us. The alternative of calling driver_cleanup in a new thread + // and joining on it results in an endless wait in DestroyJavaVM, even when + // the main thread is detached beforehand - it is not clear why. + }); +} + // Entry point called by libfuzzer before any LLVMFuzzerTestOneInput(...) // invocations. extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) { -- cgit v1.2.3 From d26d0089203b96eec65ce9fc18407490b3c52618 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 6 Apr 2022 13:21:25 +0200 Subject: Update rules_jni to 0.5.1 --- repositories.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/repositories.bzl b/repositories.bzl index 214e829e..f4587116 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -91,9 +91,9 @@ def jazzer_dependencies(): maybe( http_archive, name = "fmeum_rules_jni", - sha256 = "39902411424856ce51c0137665171a4d08f2b767d30978dbded29dd099319890", - strip_prefix = "rules_jni-0.4.2", - url = "https://github.com/fmeum/rules_jni/archive/refs/tags/v0.4.2.tar.gz", + sha256 = "45acc80812e0ecafc3f1b2f17efa6141d014aa2cf49c94a83252f570124d161a", + strip_prefix = "rules_jni-0.5.1", + url = "https://github.com/fmeum/rules_jni/archive/refs/tags/v0.5.1.tar.gz", ) maybe( -- cgit v1.2.3 From a635153a5a44ab7f3c3e70b00608bb59dc0b1275 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Wed, 6 Apr 2022 14:34:25 +0200 Subject: Use CallStaticVoidMethod where applicable --- driver/coverage_tracker.cpp | 12 ++++++------ driver/fuzz_target_runner.cpp | 5 ++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/driver/coverage_tracker.cpp b/driver/coverage_tracker.cpp index 4be0db16..63aefd30 100644 --- a/driver/coverage_tracker.cpp +++ b/driver/coverage_tracker.cpp @@ -132,9 +132,9 @@ void CoverageTracker::ReportCoverage(JNIEnv &env, std::string report_file) { covered_edge_ids.data()); AssertNoException(env); jstring report_file_str = env.NewStringUTF(report_file.c_str()); - env.CallStaticObjectMethod(coverage_recorder, - coverage_recorder_dump_coverage_report, - covered_edge_ids_jni, report_file_str); + env.CallStaticVoidMethod(coverage_recorder, + coverage_recorder_dump_coverage_report, + covered_edge_ids_jni, report_file_str); AssertNoException(env); } @@ -156,9 +156,9 @@ void CoverageTracker::DumpCoverage(JNIEnv &env, std::string dump_file) { covered_edge_ids.data()); AssertNoException(env); jstring dump_file_str = env.NewStringUTF(dump_file.c_str()); - env.CallStaticObjectMethod(coverage_recorder, - coverage_recorder_dump_jacoco_coverage, - covered_edge_ids_jni, dump_file_str); + env.CallStaticVoidMethod(coverage_recorder, + coverage_recorder_dump_jacoco_coverage, + covered_edge_ids_jni, dump_file_str); AssertNoException(env); } } // namespace jazzer diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index 196f9014..663971e8 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -174,7 +174,7 @@ FuzzTargetRunner::FuzzTargetRunner( auto on_fuzz_target_ready = jvm.GetStaticMethodID( jazzer_, "onFuzzTargetReady", "(Ljava/lang/String;)V", true); jstring fuzz_target_class = env.NewStringUTF(FLAGS_target_class.c_str()); - env.CallStaticObjectMethod(jazzer_, on_fuzz_target_ready, fuzz_target_class); + env.CallStaticVoidMethod(jazzer_, on_fuzz_target_ready, fuzz_target_class); if (env.ExceptionCheck()) { env.ExceptionDescribe(); return; @@ -233,8 +233,7 @@ FuzzTargetRunner::FuzzTargetRunner( jstring str = env.NewStringUTF(fuzz_target_args_tokens[i].c_str()); env.SetObjectArrayElement(arg_array, i, str); } - env.CallStaticObjectMethod(jclass_, fuzzer_initialize_with_args_, - arg_array); + env.CallStaticVoidMethod(jclass_, fuzzer_initialize_with_args_, arg_array); } else if (fuzzer_initialize_) { env.CallStaticVoidMethod(jclass_, fuzzer_initialize_); } else { -- cgit v1.2.3 From 9e60e921ccf859071703672e5ad525afa1811409 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Thu, 7 Apr 2022 08:52:03 +0200 Subject: Update bazel_jar_jar to version supporting Java 17+ --- repositories.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/repositories.bzl b/repositories.bzl index f4587116..dcc06e45 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -65,9 +65,9 @@ def jazzer_dependencies(): maybe( http_archive, name = "com_github_johnynek_bazel_jar_jar", - sha256 = "44dbf93907c361594057bcf1205dab91e1169f3b7a89db8c778161459588b5d6", - strip_prefix = "bazel_jar_jar-commit-171f268569384c57c19474b04aebe574d85fde0d", - url = "https://github.com/CodeIntelligenceTesting/bazel_jar_jar/archive/refs/tags/commit-171f268569384c57c19474b04aebe574d85fde0d.tar.gz", + sha256 = "138a33a5c6ed9355e4411caa22f2fe45460b7e1e4468cbc29f7955367d7a001a", + strip_prefix = "bazel_jar_jar-commit-d97cfd22d47cba9a20708fa092f20348b72fb5ed", + url = "https://github.com/CodeIntelligenceTesting/bazel_jar_jar/archive/refs/tags/commit-d97cfd22d47cba9a20708fa092f20348b72fb5ed.tar.gz", ) maybe( -- cgit v1.2.3 From f22f61b3ac3b19ef4671a6cd7870f7a17af2c478 Mon Sep 17 00:00:00 2001 From: Khaled Yakdan Date: Mon, 28 Feb 2022 20:51:45 +0100 Subject: Enable multiple BEFORE and AFTER hooks for the same method --- .../com/code_intelligence/jazzer/api/HookType.java | 1 + .../code_intelligence/jazzer/api/MethodHook.java | 7 + .../jazzer/instrumentor/HookMethodVisitor.kt | 371 ++++++++++----------- 3 files changed, 187 insertions(+), 192 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/HookType.java b/agent/src/main/java/com/code_intelligence/jazzer/api/HookType.java index 1c564a78..8ed4337f 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/HookType.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/api/HookType.java @@ -17,6 +17,7 @@ package com.code_intelligence.jazzer.api; /** * The type of a {@link MethodHook}. */ +// Note: The order of entries is important and is used during instrumentation. public enum HookType { BEFORE, REPLACE, diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java b/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java index 5638c232..46c5c112 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java @@ -115,6 +115,13 @@ import java.lang.invoke.MethodType; * will be wrapped into their corresponding wrapper type (e.g. {@link Boolean}). * If the original method has return type {@code void}, this value will be * {@code null}. + *

    + * Multiple {@link HookType#BEFORE} and {@link HookType#AFTER} hooks are + * allowed to reference the same target method. Exclusively one + * {@link HookType#REPLACE} hook may reference a target method, no other types + * allowed. Attention must be paid to not guide the Fuzzer in different + * directions via {@link Jazzer}'s {@code guideTowardsXY} methods in the + * different hooks. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt index da9e7150..64b96e03 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt @@ -56,7 +56,7 @@ private class HookMethodVisitor( } } - private val hooks = hooks.associateBy { hook -> + private val hooks = hooks.groupBy { hook -> var hookKey = "${hook.hookType}#${hook.targetInternalClassName}#${hook.targetMethodName}" if (hook.targetMethodDescriptor != null) hookKey += "#${hook.targetMethodDescriptor}" @@ -74,76 +74,23 @@ private class HookMethodVisitor( mv.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface) return } - handleMethodInsn(HookType.BEFORE, opcode, owner, methodName, methodDescriptor, isInterface) - } - - /** - * Emits the bytecode for a method call instruction for the next applicable hook type in order (BEFORE, REPLACE, - * AFTER). Since the instrumented code is indistinguishable from an uninstrumented call instruction, it can be - * safely nested. Combining REPLACE hooks with other hooks is however not supported as these hooks already subsume - * the functionality of BEFORE and AFTER hooks. - */ - private fun visitNextHookTypeOrCall( - hookType: HookType, - appliedHook: Boolean, - opcode: Int, - owner: String, - methodName: String, - methodDescriptor: String, - isInterface: Boolean, - ) = when (hookType) { - HookType.BEFORE -> { - val nextHookType = if (appliedHook) { - // After a BEFORE hook has been applied, we can safely apply an AFTER hook by replacing the actual - // call instruction with the full bytecode injected for the AFTER hook. - HookType.AFTER - } else { - // If no BEFORE hook is registered, look for a REPLACE hook next. - HookType.REPLACE - } - handleMethodInsn(nextHookType, opcode, owner, methodName, methodDescriptor, isInterface) - } - HookType.REPLACE -> { - // REPLACE hooks can't (and don't need to) be mixed with other hooks. We only cycle through them if we - // couldn't find a matching REPLACE hook, in which case we try an AFTER hook next. - require(!appliedHook) - handleMethodInsn(HookType.AFTER, opcode, owner, methodName, methodDescriptor, isInterface) - } - // An AFTER hook is always the last in the chain. Whether a hook has been applied or not, always emit the - // actual call instruction. - HookType.AFTER -> mv.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface) + handleMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface) } fun handleMethodInsn( - hookType: HookType, opcode: Int, owner: String, methodName: String, methodDescriptor: String, isInterface: Boolean, ) { - val hook = findMatchingHook(hookType, owner, methodName, methodDescriptor) - if (hook == null) { - visitNextHookTypeOrCall(hookType, false, opcode, owner, methodName, methodDescriptor, isInterface) - return - } + val matchingHooks = findMatchingHooks(owner, methodName, methodDescriptor) - if (java6Mode && hookType == HookType.REPLACE) { - if (showUnsupportedHookWarning.getAndSet(false)) { - println( - """WARN: Some hooks could not be applied to class files built for Java 7 or lower. - |WARN: Ensure that the fuzz target and its dependencies are compiled with - |WARN: -target 8 or higher to identify as many bugs as possible. - """.trimMargin() - ) - } - visitNextHookTypeOrCall(hookType, false, opcode, owner, methodName, methodDescriptor, isInterface) + if (matchingHooks.isEmpty()) { + mv.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface) return } - // The hookId is used to identify a call site. - val hookId = random.nextInt() - val paramDescriptors = extractParameterTypeDescriptors(methodDescriptor) val localObjArr = storeMethodArguments(paramDescriptors) // If the method we're hooking is not static there is now a reference to @@ -160,144 +107,158 @@ private class HookMethodVisitor( // We now removed all values for the original method call from the operand stack // and saved them to local variables. - // Start to build the arguments for the hook method. - if (methodName == "") { - // Constructor is invoked on an uninitialized object, and that's still on the stack. - // In case of REPLACE pop it from the stack and replace it afterwards with the returned - // one from the hook. - if (hook.hookType == HookType.REPLACE) { - mv.visitInsn(Opcodes.POP) - } - // Special case for constructors: - // We cannot create a MethodHandle for a constructor, so we push null instead. - mv.visitInsn(Opcodes.ACONST_NULL) // push nullref - // Only pass the this object if it has been initialized by the time the hook is invoked. - if (hook.hookType == HookType.AFTER) { - mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj) - } else { - mv.visitInsn(Opcodes.ACONST_NULL) // push nullref - } - } else { - // Push a MethodHandle representing the hooked method. - val handleOpcode = when (opcode) { - Opcodes.INVOKEVIRTUAL -> Opcodes.H_INVOKEVIRTUAL - Opcodes.INVOKEINTERFACE -> Opcodes.H_INVOKEINTERFACE - Opcodes.INVOKESTATIC -> Opcodes.H_INVOKESTATIC - Opcodes.INVOKESPECIAL -> Opcodes.H_INVOKESPECIAL - else -> -1 - } - if (java6Mode) { - // MethodHandle constants (type 15) are not supported in Java 6 class files (major version 50). + val returnTypeDescriptor = extractReturnTypeDescriptor(methodDescriptor) + // Create a local variable to store the return value + val localReturnObj = lvs.newLocal(Type.getType(getWrapperTypeDescriptor(returnTypeDescriptor))) + + matchingHooks.forEachIndexed { index, hook -> + // The hookId is used to identify a call site. + val hookId = random.nextInt() + + // Start to build the arguments for the hook method. + if (methodName == "") { + // Constructor is invoked on an uninitialized object, and that's still on the stack. + // In case of REPLACE pop it from the stack and replace it afterwards with the returned + // one from the hook. + if (hook.hookType == HookType.REPLACE) { + mv.visitInsn(Opcodes.POP) + } + // Special case for constructors: + // We cannot create a MethodHandle for a constructor, so we push null instead. mv.visitInsn(Opcodes.ACONST_NULL) // push nullref + // Only pass the this object if it has been initialized by the time the hook is invoked. + if (hook.hookType == HookType.AFTER) { + mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj) + } else { + mv.visitInsn(Opcodes.ACONST_NULL) // push nullref + } } else { - mv.visitLdcInsn( - Handle( - handleOpcode, - owner, - methodName, - methodDescriptor, - isInterface - ) - ) // push MethodHandle - } - // Stack layout: ... | MethodHandle (objectref) - // Push the owner object again - mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj) - } - // Stack layout: ... | MethodHandle (objectref) | owner (objectref) - // Push a reference to our object array with the saved arguments - mv.visitVarInsn(Opcodes.ALOAD, localObjArr) - // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) - // Push the hook id - mv.visitLdcInsn(hookId) - // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int) - // How we proceed depends on the type of hook we want to implement - when (hook.hookType) { - HookType.BEFORE -> { - // Call the hook method - mv.visitMethodInsn( - Opcodes.INVOKESTATIC, - hook.hookInternalClassName, - hook.hookMethodName, - hook.hookMethodDescriptor, - false - ) - // Stack layout: ... - // Push the values for the original method call onto the stack again - if (opcode != Opcodes.INVOKESTATIC) { - mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj) // push owner object + // Push a MethodHandle representing the hooked method. + val handleOpcode = when (opcode) { + Opcodes.INVOKEVIRTUAL -> Opcodes.H_INVOKEVIRTUAL + Opcodes.INVOKEINTERFACE -> Opcodes.H_INVOKEINTERFACE + Opcodes.INVOKESTATIC -> Opcodes.H_INVOKESTATIC + Opcodes.INVOKESPECIAL -> Opcodes.H_INVOKESPECIAL + else -> -1 + } + if (java6Mode) { + // MethodHandle constants (type 15) are not supported in Java 6 class files (major version 50). + mv.visitInsn(Opcodes.ACONST_NULL) // push nullref + } else { + mv.visitLdcInsn( + Handle( + handleOpcode, + owner, + methodName, + methodDescriptor, + isInterface + ) + ) // push MethodHandle } - loadMethodArguments(paramDescriptors, localObjArr) // push all method arguments - // Stack layout: ... | [owner (objectref)] | arg1 (primitive/objectref) | arg2 (primitive/objectref) | ... - // Call the original method or the next hook in order. - visitNextHookTypeOrCall(hookType, true, opcode, owner, methodName, methodDescriptor, isInterface) + // Stack layout: ... | MethodHandle (objectref) + // Push the owner object again + mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj) } - HookType.REPLACE -> { - // Call the hook method - mv.visitMethodInsn( - Opcodes.INVOKESTATIC, - hook.hookInternalClassName, - hook.hookMethodName, - hook.hookMethodDescriptor, - false - ) - // Stack layout: ... | [return value (primitive/objectref)] - // Check if we need to process the return value - val returnTypeDescriptor = extractReturnTypeDescriptor(methodDescriptor) - if (returnTypeDescriptor != "V") { - val hookMethodReturnType = extractReturnTypeDescriptor(hook.hookMethodDescriptor) - // if the hook method's return type is primitive we don't need to unwrap or cast it - if (!isPrimitiveType(hookMethodReturnType)) { - // Check if the returned object type is different than the one that should be returned - // If a primitive should be returned we check it's wrapper type - val expectedType = getWrapperTypeDescriptor(returnTypeDescriptor) - if (expectedType != hookMethodReturnType) { - // Cast object - mv.visitTypeInsn(Opcodes.CHECKCAST, extractInternalClassName(expectedType)) + // Stack layout: ... | MethodHandle (objectref) | owner (objectref) + // Push a reference to our object array with the saved arguments + mv.visitVarInsn(Opcodes.ALOAD, localObjArr) + // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) + // Push the hook id + mv.visitLdcInsn(hookId) + // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int) + // How we proceed depends on the type of hook we want to implement + when (hook.hookType) { + HookType.BEFORE -> { + // Call the hook method + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + hook.hookInternalClassName, + hook.hookMethodName, + hook.hookMethodDescriptor, + false + ) + + // Call the original method if this is the last BEFORE hook. If not, the original method will be + // called by the next AFTER hook. + if (index == matchingHooks.lastIndex) { + // Stack layout: ... + // Push the values for the original method call onto the stack again + if (opcode != Opcodes.INVOKESTATIC) { + mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj) // push owner object } - // Check if we need to unwrap the returned object - unwrapTypeIfPrimitive(returnTypeDescriptor) + loadMethodArguments(paramDescriptors, localObjArr) // push all method arguments + // Stack layout: ... | [owner (objectref)] | arg1 (primitive/objectref) | arg2 (primitive/objectref) | ... + mv.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface) } } - } - HookType.AFTER -> { - // Push the values for the original method call again onto the stack - if (opcode != Opcodes.INVOKESTATIC) { - mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj) // push owner object - } - loadMethodArguments(paramDescriptors, localObjArr) // push all method arguments - // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int) - // | [owner (objectref)] | arg1 (primitive/objectref) | arg2 (primitive/objectref) | ... - // Call the original method or the next hook in order - visitNextHookTypeOrCall(hookType, true, opcode, owner, methodName, methodDescriptor, isInterface) - val returnTypeDescriptor = extractReturnTypeDescriptor(methodDescriptor) - if (returnTypeDescriptor == "V") { - // If the method didn't return anything, we push a nullref as placeholder - mv.visitInsn(Opcodes.ACONST_NULL) // push nullref + HookType.REPLACE -> { + // Call the hook method + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + hook.hookInternalClassName, + hook.hookMethodName, + hook.hookMethodDescriptor, + false + ) + // Stack layout: ... | [return value (primitive/objectref)] + // Check if we need to process the return value + if (returnTypeDescriptor != "V") { + val hookMethodReturnType = extractReturnTypeDescriptor(hook.hookMethodDescriptor) + // if the hook method's return type is primitive we don't need to unwrap or cast it + if (!isPrimitiveType(hookMethodReturnType)) { + // Check if the returned object type is different than the one that should be returned + // If a primitive should be returned we check it's wrapper type + val expectedType = getWrapperTypeDescriptor(returnTypeDescriptor) + if (expectedType != hookMethodReturnType) { + // Cast object + mv.visitTypeInsn(Opcodes.CHECKCAST, extractInternalClassName(expectedType)) + } + // Check if we need to unwrap the returned object + unwrapTypeIfPrimitive(returnTypeDescriptor) + } + } } - // Wrap return value if it is a primitive type - wrapTypeIfPrimitive(returnTypeDescriptor) - // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int) - // | return value (objectref) - // Store the result value in a local variable (but keep it on the stack) - val localReturnObj = lvs.newLocal(Type.getType(getWrapperTypeDescriptor(returnTypeDescriptor))) - mv.visitVarInsn(Opcodes.ASTORE, localReturnObj) // consume objectref - mv.visitVarInsn(Opcodes.ALOAD, localReturnObj) // push objectref - // Call the hook method - mv.visitMethodInsn( - Opcodes.INVOKESTATIC, - hook.hookInternalClassName, - hook.hookMethodName, - hook.hookMethodDescriptor, - false - ) - // Stack layout: ... - if (returnTypeDescriptor != "V") { - // Push the return value again + HookType.AFTER -> { + // Call the original method before the first AFTER hook + if (index == 0 || matchingHooks[index - 1].hookType != HookType.AFTER) { + // Push the values for the original method call again onto the stack + if (opcode != Opcodes.INVOKESTATIC) { + mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj) // push owner object + } + loadMethodArguments(paramDescriptors, localObjArr) // push all method arguments + // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int) + // | [owner (objectref)] | arg1 (primitive/objectref) | arg2 (primitive/objectref) | ... + mv.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface) + if (returnTypeDescriptor == "V") { + // If the method didn't return anything, we push a nullref as placeholder + mv.visitInsn(Opcodes.ACONST_NULL) // push nullref + } + // Wrap return value if it is a primitive type + wrapTypeIfPrimitive(returnTypeDescriptor) + mv.visitVarInsn(Opcodes.ASTORE, localReturnObj) // consume objectref + } mv.visitVarInsn(Opcodes.ALOAD, localReturnObj) // push objectref - // Unwrap it, if it was a primitive value - unwrapTypeIfPrimitive(returnTypeDescriptor) - // Stack layout: ... | return value (primitive/objectref) + + // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int) + // | return value (objectref) + // Store the result value in a local variable (but keep it on the stack) + // Call the hook method + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + hook.hookInternalClassName, + hook.hookMethodName, + hook.hookMethodDescriptor, + false + ) + // Stack layout: ... + // Push the return value on the stack after the last AFTER hook if the original method returns a value + if (index == matchingHooks.size - 1 && returnTypeDescriptor != "V") { + // Push the return value again + mv.visitVarInsn(Opcodes.ALOAD, localReturnObj) // push objectref + // Unwrap it, if it was a primitive value + unwrapTypeIfPrimitive(returnTypeDescriptor) + // Stack layout: ... | return value (primitive/objectref) + } } } } @@ -310,10 +271,36 @@ private class HookMethodVisitor( Opcodes.INVOKESPECIAL ) - private fun findMatchingHook(hookType: HookType, owner: String, name: String, descriptor: String): Hook? { - val withoutDescriptorKey = "$hookType#$owner#$name" - val withDescriptorKey = "$withoutDescriptorKey#$descriptor" - return hooks[withDescriptorKey] ?: hooks[withoutDescriptorKey] + private fun findMatchingHooks(owner: String, name: String, descriptor: String): List { + val result = HookType.values().flatMap { hookType -> + val withoutDescriptorKey = "$hookType#$owner#$name" + val withDescriptorKey = "$withoutDescriptorKey#$descriptor" + hooks[withDescriptorKey].orEmpty() + hooks[withoutDescriptorKey].orEmpty() + }.sortedBy { it.hookType } + check( + result.isEmpty() || + result.count { it.hookType != HookType.REPLACE } == result.size || + result.count { it.hookType == HookType.REPLACE } == 1 + ) { + "For a given method, You can either have a single REPLACE hook or BEFORE/AFTER hooks. Found:\n $result" + } + + return result.filter { !isReplaceHookInJava6mode(it) } + } + + private fun isReplaceHookInJava6mode(hook: Hook): Boolean { + if (java6Mode && hook.hookType == HookType.REPLACE) { + if (showUnsupportedHookWarning.getAndSet(false)) { + println( + """WARN: Some hooks could not be applied to class files built for Java 7 or lower. + |WARN: Ensure that the fuzz target and its dependencies are compiled with + |WARN: -target 8 or higher to identify as many bugs as possible. + """.trimMargin() + ) + } + return true + } + return false } // Stores all arguments for a method call in a local object array. @@ -374,7 +361,7 @@ private class HookMethodVisitor( } // Removes a primitive value from the top of the operand stack - // and pushes it enclosed in it's wrapper type (e.g. removes int, pushes Integer). + // and pushes it enclosed in its wrapper type (e.g. removes int, pushes Integer). // This is done by calling .valueOf(...) on the wrapper class. private fun wrapTypeIfPrimitive(unwrappedTypeDescriptor: String) { if (!isPrimitiveType(unwrappedTypeDescriptor) || unwrappedTypeDescriptor == "V") return -- cgit v1.2.3 From 099ce0549f5e87dfa702bcbfb44dc5df49e116e9 Mon Sep 17 00:00:00 2001 From: Khaled Yakdan Date: Mon, 28 Feb 2022 21:07:41 +0100 Subject: Add hooks for loading arbitrary libraries We also add a test that checks having multiple BEFORE hooks for the `java.lang.System.loadLibrary()` method since it is used in the builtin native hooks --- .../jazzer/instrumentor/HookMethodVisitor.kt | 10 +++++--- .../jazzer/sanitizers/ReflectiveCall.kt | 20 +++++++++++++++ .../code_intelligence/jazzer/sanitizers/Utils.kt | 1 + sanitizers/src/test/java/com/example/BUILD.bazel | 12 +++++++++ .../src/test/java/com/example/LibraryLoad.java | 29 ++++++++++++++++++++++ 5 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 sanitizers/src/test/java/com/example/LibraryLoad.java diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt index 64b96e03..1694be58 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt @@ -277,15 +277,17 @@ private class HookMethodVisitor( val withDescriptorKey = "$withoutDescriptorKey#$descriptor" hooks[withDescriptorKey].orEmpty() + hooks[withoutDescriptorKey].orEmpty() }.sortedBy { it.hookType } + val replaceHookCount = result.count { it.hookType == HookType.REPLACE } check( - result.isEmpty() || - result.count { it.hookType != HookType.REPLACE } == result.size || - result.count { it.hookType == HookType.REPLACE } == 1 + replaceHookCount == 0 || + (replaceHookCount == 1 && result.size == 1) ) { "For a given method, You can either have a single REPLACE hook or BEFORE/AFTER hooks. Found:\n $result" } - return result.filter { !isReplaceHookInJava6mode(it) } + return result + .filter { !isReplaceHookInJava6mode(it) } + .sortedByDescending { it.toString() } } private fun isReplaceHookInJava6mode(hook: Hook): Boolean { diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt index 7072cc7d..0fcabe36 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt @@ -14,6 +14,7 @@ package com.code_intelligence.jazzer.sanitizers +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh import com.code_intelligence.jazzer.api.HookType import com.code_intelligence.jazzer.api.Jazzer import com.code_intelligence.jazzer.api.MethodHook @@ -49,4 +50,23 @@ object ReflectiveCall { val className = args[1] as? String ?: return Jazzer.guideTowardsEquality(className, HONEYPOT_CLASS_NAME, hookId) } + + @MethodHooks( + MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Runtime", targetMethod = "load"), + MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Runtime", targetMethod = "loadLibrary"), + MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.System", targetMethod = "load"), + MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.System", targetMethod = "loadLibrary"), + MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.System", targetMethod = "mapLibraryName"), + MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.ClassLoader", targetMethod = "findLibrary"), + ) + @JvmStatic + fun loadLibraryHook(method: MethodHandle?, alwaysNull: Any?, args: Array, hookId: Int) { + val libraryName = args[0] as? String ?: return + if (libraryName == HONEYPOT_LIBRARY_NAME) { + Jazzer.reportFindingFromHook( + FuzzerSecurityIssueHigh("load arbitrary library") + ) + } + Jazzer.guideTowardsEquality(libraryName, HONEYPOT_LIBRARY_NAME, hookId) + } } diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt index 3166773b..52519cac 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt @@ -21,6 +21,7 @@ import java.io.InputStream * jaz.Zer is a honeypot class: All of its methods report a finding when called. */ const val HONEYPOT_CLASS_NAME = "jaz.Zer" +const val HONEYPOT_LIBRARY_NAME = "jazzer_honeypot" internal fun Short.toBytes(): ByteArray { return byteArrayOf( diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index 3ba71386..5152e1e6 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -1,4 +1,5 @@ load("//bazel:fuzz_target.bzl", "java_fuzz_target_test") +load("//bazel:compat.bzl", "SKIP_ON_MACOS") java_fuzz_target_test( name = "ObjectInputStreamDeserialization", @@ -16,6 +17,17 @@ java_fuzz_target_test( target_class = "com.example.ReflectiveCall", ) +java_fuzz_target_test( + name = "LibraryLoad", + srcs = [ + "LibraryLoad.java", + ], + target_class = "com.example.LibraryLoad", + # loading of native libraries is very slow on macos, + # especially using Java 17 + target_compatible_with = SKIP_ON_MACOS, +) + java_fuzz_target_test( name = "ExpressionLanguageInjection", srcs = [ diff --git a/sanitizers/src/test/java/com/example/LibraryLoad.java b/sanitizers/src/test/java/com/example/LibraryLoad.java new file mode 100644 index 00000000..81411767 --- /dev/null +++ b/sanitizers/src/test/java/com/example/LibraryLoad.java @@ -0,0 +1,29 @@ +// 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.example; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; + +public class LibraryLoad { + public static void fuzzerTestOneInput(FuzzedDataProvider data) { + String input = data.consumeRemainingAsAsciiString(); + + try { + System.loadLibrary(input); + } catch (SecurityException | UnsatisfiedLinkError | NullPointerException + | IllegalArgumentException ignored) { + } + } +} -- cgit v1.2.3 From 5f1eff59b44389aecb881d85276b134b360a93d6 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Mon, 11 Apr 2022 08:30:16 +0200 Subject: Remove unused JaCoCo fork package Missed in #379 to remove the package "generated". This package is only relevant for the internal JaCoCo fork which is not used anymore. --- .../code_intelligence/jazzer/generated/BUILD.bazel | 40 ---- .../jazzer/generated/NoThrowDoclet.java | 215 --------------------- .../generated/update_java_no_throw_methods_list.sh | 18 -- 3 files changed, 273 deletions(-) delete mode 100644 agent/src/main/java/com/code_intelligence/jazzer/generated/BUILD.bazel delete mode 100644 agent/src/main/java/com/code_intelligence/jazzer/generated/NoThrowDoclet.java delete mode 100755 agent/src/main/java/com/code_intelligence/jazzer/generated/update_java_no_throw_methods_list.sh diff --git a/agent/src/main/java/com/code_intelligence/jazzer/generated/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/generated/BUILD.bazel deleted file mode 100644 index fceda64c..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/generated/BUILD.bazel +++ /dev/null @@ -1,40 +0,0 @@ -java_binary( - name = "NoThrowDoclet", - srcs = ["NoThrowDoclet.java"], - create_executable = False, - tags = ["manual"], -) - -# To regenerate the list of methods, ensure that your local JDK is as recent as possible and contains `lib/src.zip`. -# This will be the case if you are using the release binaries of the OpenJDK or if the `openjdk--source` -# package is installed. -# Then, execute -# agent/src/main/java/com/code_intelligence/jazzer/generated/update_java_no_throw_methods_list.sh -# from the Bazel root and copy the file into -# org.jacoco.core/src/org/jacoco/core/internal/flow/java_no_throw_methods_list.dat -# in the CodeIntelligenceTesting/jacoco repository. -genrule( - name = "java_no_throw_methods_list", - srcs = [ - "@local_jdk//:lib/src.zip", - ], - outs = [ - "java_no_throw_methods_list.dat.generated", - ], - cmd = """ - TMP=$$(mktemp -d) && \ - unzip $(execpath @local_jdk//:lib/src.zip) -d $$TMP && \ - $(execpath @local_jdk//:bin/javadoc) \ - -doclet com.code_intelligence.jazzer.generated.NoThrowDoclet \ - -docletpath $(execpath :NoThrowDoclet_deploy.jar) \ - --module java.base \ - --source-path $$TMP/java.base \ - --out $@ && \ - sort -o $@ $@ && \ - rm -rf $$TMP""", - tags = ["manual"], - tools = [ - ":NoThrowDoclet_deploy.jar", - "@local_jdk//:bin/javadoc", - ], -) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/generated/NoThrowDoclet.java b/agent/src/main/java/com/code_intelligence/jazzer/generated/NoThrowDoclet.java deleted file mode 100644 index 1b52a228..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/generated/NoThrowDoclet.java +++ /dev/null @@ -1,215 +0,0 @@ -// 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.generated; - -import com.sun.source.doctree.DocCommentTree; -import com.sun.source.doctree.DocTree; -import com.sun.source.doctree.ThrowsTree; -import com.sun.source.util.DocTrees; -import java.io.BufferedWriter; -import java.io.FileWriter; -import java.io.IOException; -import java.util.List; -import java.util.Locale; -import java.util.Set; -import java.util.stream.Collectors; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.ModuleElement; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementFilter; -import jdk.javadoc.doclet.Doclet; -import jdk.javadoc.doclet.DocletEnvironment; -import jdk.javadoc.doclet.Reporter; - -/** - * A Doclet that extracts a list of all method signatures in {@code java.*} that are declared not to - * throw any exceptions, including {@link RuntimeException} but excluding {@link - * VirtualMachineError}. - * - * Crucially, whereas the throws declaration of a method does not contain subclasses of {@link - * RuntimeException}, the {@code @throws} Javadoc tag does. - */ -public class NoThrowDoclet implements Doclet { - private BufferedWriter out; - - @Override - public void init(Locale locale, Reporter reporter) {} - - @Override - public String getName() { - return getClass().getSimpleName(); - } - - @Override - public Set getSupportedOptions() { - return Set.of(new Option() { - @Override - public int getArgumentCount() { - return 1; - } - - @Override - public String getDescription() { - return "Output file (.kt)"; - } - - @Override - public Kind getKind() { - return Kind.STANDARD; - } - - @Override - public List getNames() { - return List.of("--out"); - } - - @Override - public String getParameters() { - return ""; - } - - @Override - public boolean process(String option, List args) { - try { - out = new BufferedWriter(new FileWriter(args.get(0))); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - return true; - } - }); - } - - @Override - public SourceVersion getSupportedSourceVersion() { - return null; - } - - private String toDescriptor(TypeMirror type) { - switch (type.getKind()) { - case BOOLEAN: - return "Z"; - case BYTE: - return "B"; - case CHAR: - return "C"; - case DOUBLE: - return "D"; - case FLOAT: - return "F"; - case INT: - return "I"; - case LONG: - return "J"; - case SHORT: - return "S"; - case VOID: - return "V"; - case ARRAY: - return "[" + toDescriptor(((ArrayType) type).getComponentType()); - case DECLARED: - return "L" + getFullyQualifiedName((DeclaredType) type) + ";"; - case TYPEVAR: - return "Ljava/lang/Object;"; - } - throw new IllegalArgumentException( - "Unexpected kind " + type.getKind() + ": " + type.toString()); - } - - private String getFullyQualifiedName(DeclaredType declaredType) { - TypeElement element = (TypeElement) declaredType.asElement(); - return element.getQualifiedName().toString().replace('.', '/'); - } - - private void handleExecutableElement(DocTrees trees, ExecutableElement executable) - throws IOException { - if (!executable.getModifiers().contains(Modifier.PUBLIC)) - return; - - DocCommentTree tree = trees.getDocCommentTree(executable); - if (tree != null) { - for (DocTree tag : tree.getBlockTags()) { - if (tag instanceof ThrowsTree) { - return; - } - } - } - - String methodName = executable.getSimpleName().toString(); - String className = - ((TypeElement) executable.getEnclosingElement()).getQualifiedName().toString(); - String internalClassName = className.replace('.', '/'); - - String paramTypeDescriptors = executable.getParameters() - .stream() - .map(VariableElement::asType) - .map(this::toDescriptor) - .collect(Collectors.joining("")); - String returnTypeDescriptor = toDescriptor(executable.getReturnType()); - String methodDescriptor = String.format("(%s)%s", paramTypeDescriptors, returnTypeDescriptor); - - out.write(String.format("%s#%s#%s%n", internalClassName, methodName, methodDescriptor)); - } - - public void handleTypeElement(DocTrees trees, TypeElement typeElement) throws IOException { - List executables = - ElementFilter.constructorsIn(typeElement.getEnclosedElements()); - executables.addAll(ElementFilter.methodsIn(typeElement.getEnclosedElements())); - for (ExecutableElement executableElement : executables) { - handleExecutableElement(trees, executableElement); - } - } - - @Override - public boolean run(DocletEnvironment docletEnvironment) { - try { - DocTrees trees = docletEnvironment.getDocTrees(); - for (ModuleElement moduleElement : - ElementFilter.modulesIn(docletEnvironment.getSpecifiedElements())) { - for (PackageElement packageElement : - ElementFilter.packagesIn(moduleElement.getEnclosedElements())) { - if (packageElement.getQualifiedName().toString().startsWith("java.")) { - for (TypeElement typeElement : - ElementFilter.typesIn(packageElement.getEnclosedElements())) { - ElementKind kind = typeElement.getKind(); - if (kind == ElementKind.CLASS || kind == ElementKind.ENUM - || kind == ElementKind.INTERFACE) { - handleTypeElement(trees, typeElement); - } - } - } - } - } - } catch (IOException e) { - e.printStackTrace(); - return false; - } - try { - out.close(); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - return true; - } -} \ No newline at end of file diff --git a/agent/src/main/java/com/code_intelligence/jazzer/generated/update_java_no_throw_methods_list.sh b/agent/src/main/java/com/code_intelligence/jazzer/generated/update_java_no_throw_methods_list.sh deleted file mode 100755 index 1463c602..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/generated/update_java_no_throw_methods_list.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env sh -# 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. - -set -e -bazel build //agent/src/main/java/com/code_intelligence/jazzer/generated:java_no_throw_methods_list -cp bazel-bin/agent/src/main/java/com/code_intelligence/jazzer/generated/java_no_throw_methods_list.dat.generated agent/src/main/java/com/code_intelligence/jazzer/generated/java_no_throw_methods_list.dat -- cgit v1.2.3 From ee446d1bbd6965604fd02a487cf4e0ec710ef8fe Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Wed, 13 Apr 2022 17:59:04 +0200 Subject: Workaround GitHub checkout action issue Issue https://github.com/actions/checkout/issues/760 --- .github/workflows/oss-fuzz.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/oss-fuzz.yml b/.github/workflows/oss-fuzz.yml index a7fb5fce..2c6bbf5e 100644 --- a/.github/workflows/oss-fuzz.yml +++ b/.github/workflows/oss-fuzz.yml @@ -15,6 +15,10 @@ jobs: container: gcr.io/oss-fuzz-base/base-builder-jvm steps: + - name: Adding github workspace as safe directory + # See issue https://github.com/actions/checkout/issues/760 + run: git config --global --add safe.directory $GITHUB_WORKSPACE + - uses: actions/checkout@v2 - name: Build Jazzer -- cgit v1.2.3 From 9dd4e76e68a38a2bba70b97ca08b012e1788572f Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Wed, 13 Apr 2022 16:55:55 +0200 Subject: Prevent ConcurrentModificationException in TraceCmpHooks The hooked map can be modified concurrently and could cause concurrent modification exceptions as seen during startup of Spring Boot applications. --- .../jazzer/runtime/TraceCmpHooks.java | 66 ++++++++++++---------- .../code_intelligence/jazzer/runtime/BUILD.bazel | 15 +++++ .../jazzer/runtime/TraceCmpHooksTest.java | 60 ++++++++++++++++++++ 3 files changed, 111 insertions(+), 30 deletions(-) create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/runtime/TraceCmpHooksTest.java diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java index e6d74187..37e8eaeb 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java @@ -18,6 +18,7 @@ import com.code_intelligence.jazzer.api.HookType; import com.code_intelligence.jazzer.api.MethodHook; import java.lang.invoke.MethodHandle; import java.util.Arrays; +import java.util.ConcurrentModificationException; import java.util.Map; import java.util.TreeMap; @@ -291,42 +292,47 @@ final public class TraceCmpHooks { // https://github.com/llvm/llvm-project/blob/318942de229beb3b2587df09e776a50327b5cef0/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp#L564 Object lowerBoundKey = null; Object upperBoundKey = null; - if (map instanceof TreeMap) { - final TreeMap treeMap = (TreeMap) map; - try { - lowerBoundKey = treeMap.floorKey(currentKey); - upperBoundKey = treeMap.ceilingKey(currentKey); - } catch (ClassCastException ignored) { - // Can be thrown by floorKey and ceilingKey if currentKey is of a type that can't be - // compared to the maps keys. - } - } else if (currentKey instanceof Comparable) { - final Comparable comparableCurrentKey = (Comparable) currentKey; - // Find two keys that bracket currentKey. - // Note: This is not deterministic if map.size() > MAX_NUM_KEYS_TO_ENUMERATE. - int enumeratedKeys = 0; - for (Object validKey : map.keySet()) { - if (!(validKey instanceof Comparable)) - continue; - final Comparable comparableValidKey = (Comparable) validKey; - // If the key sorts lower than the non-existing key, but higher than the current lower - // bound, update the lower bound and vice versa for the upper bound. + try { + if (map instanceof TreeMap) { + final TreeMap treeMap = (TreeMap) map; try { - if (comparableValidKey.compareTo(comparableCurrentKey) < 0 - && (lowerBoundKey == null || comparableValidKey.compareTo(lowerBoundKey) > 0)) { - lowerBoundKey = validKey; - } - if (comparableValidKey.compareTo(comparableCurrentKey) > 0 - && (upperBoundKey == null || comparableValidKey.compareTo(upperBoundKey) < 0)) { - upperBoundKey = validKey; - } + lowerBoundKey = treeMap.floorKey(currentKey); + upperBoundKey = treeMap.ceilingKey(currentKey); } catch (ClassCastException ignored) { // Can be thrown by floorKey and ceilingKey if currentKey is of a type that can't be // compared to the maps keys. } - if (enumeratedKeys++ > MAX_NUM_KEYS_TO_ENUMERATE) - break; + } else if (currentKey instanceof Comparable) { + final Comparable comparableCurrentKey = (Comparable) currentKey; + // Find two keys that bracket currentKey. + // Note: This is not deterministic if map.size() > MAX_NUM_KEYS_TO_ENUMERATE. + int enumeratedKeys = 0; + for (Object validKey : map.keySet()) { + if (!(validKey instanceof Comparable)) + continue; + final Comparable comparableValidKey = (Comparable) validKey; + // If the key sorts lower than the non-existing key, but higher than the current lower + // bound, update the lower bound and vice versa for the upper bound. + try { + if (comparableValidKey.compareTo(comparableCurrentKey) < 0 + && (lowerBoundKey == null || comparableValidKey.compareTo(lowerBoundKey) > 0)) { + lowerBoundKey = validKey; + } + if (comparableValidKey.compareTo(comparableCurrentKey) > 0 + && (upperBoundKey == null || comparableValidKey.compareTo(upperBoundKey) < 0)) { + upperBoundKey = validKey; + } + } catch (ClassCastException ignored) { + // Can be thrown by floorKey and ceilingKey if currentKey is of a type that can't be + // compared to the maps keys. + } + if (enumeratedKeys++ > MAX_NUM_KEYS_TO_ENUMERATE) + break; + } } + } catch (ConcurrentModificationException ignored) { + // map was modified by another thread, skip this invocation + return; } // Modify the hook ID so that compares against distinct valid keys are traced separately. if (lowerBoundKey != null) { diff --git a/agent/src/test/java/com/code_intelligence/jazzer/runtime/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/runtime/BUILD.bazel index 360c4518..ad7ddb01 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/runtime/BUILD.bazel +++ b/agent/src/test/java/com/code_intelligence/jazzer/runtime/BUILD.bazel @@ -1,3 +1,5 @@ +load("//bazel:compat.bzl", "SKIP_ON_WINDOWS") + java_test( name = "RecordingFuzzedDataProviderTest", srcs = [ @@ -10,3 +12,16 @@ java_test( "@maven//:junit_junit", ], ) + +java_test( + name = "TraceCmpHooksTest", + srcs = [ + "TraceCmpHooksTest.java", + ], + target_compatible_with = SKIP_ON_WINDOWS, + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/runtime", + "//agent/src/test/java/com/code_intelligence/jazzer:MockDriver", + "@maven//:junit_junit", + ], +) diff --git a/agent/src/test/java/com/code_intelligence/jazzer/runtime/TraceCmpHooksTest.java b/agent/src/test/java/com/code_intelligence/jazzer/runtime/TraceCmpHooksTest.java new file mode 100644 index 00000000..0e838386 --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/runtime/TraceCmpHooksTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2022 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.runtime; + +import com.code_intelligence.jazzer.MockDriver; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import org.junit.BeforeClass; +import org.junit.Test; + +public class TraceCmpHooksTest { + private static final ExecutorService ES = Executors.newFixedThreadPool(5); + + @BeforeClass + public static void setup() { + MockDriver.load(); + } + + @Test + public void cmpHookShouldHandleConcurrentModifications() throws InterruptedException { + String arg = "test"; + Map map = new HashMap<>(); + map.put(arg, arg); + + // Add elements to map asynchronously + Function put = (final Integer num) -> () -> { + map.put(String.valueOf(num), num); + }; + for (int i = 0; i < 1_000_000; i++) { + ES.submit(put.apply(i)); + } + + // Call hook + for (int i = 0; i < 1_000; i++) { + TraceCmpHooks.mapGet(null, map, new Object[] {arg}, 1, null); + } + + ES.shutdown(); + // noinspection ResultOfMethodCallIgnored + ES.awaitTermination(5, TimeUnit.SECONDS); + } +} -- cgit v1.2.3 From 0a763c402e729008b85edc2843c765c81aad477d Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 25 Apr 2022 11:51:17 +0200 Subject: Return null less often in Meta.consume Experiments show that the fuzzer performance improves noticeably if the share of null return values is decreased. --- agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java index 33e30839..e65e4316 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java @@ -270,11 +270,11 @@ public class Meta { visitor.addCharLiteral(result); return result; } - // Return null for non-primitive and non-boxed types in ~5% of the cases. + // Sometimes, but rarely return null for non-primitive and non-boxed types. // TODO: We might want to return null for boxed types sometimes, but this is complicated by the // fact that TypeUtils can't distinguish between a primitive type and its wrapper and may // thus easily cause false-positive NullPointerExceptions. - if (!type.isPrimitive() && data.consumeByte((byte) 0, (byte) 19) == 0) { + if (!type.isPrimitive() && data.consumeByte() == 0) { if (visitor != null) { if (type == Object.class) { visitor.pushElement("null"); -- cgit v1.2.3 From aa5a6aef213b6a53cccc9029738f57a9a11b377b Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sun, 24 Apr 2022 17:14:48 +0200 Subject: Execute fuzz target tests with -Dfile.encoding=UTF-8 Otherwise the Java compiler will fail to compile reproducers containing UTF-8 characters. --- bazel/fuzz_target.bzl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bazel/fuzz_target.bzl b/bazel/fuzz_target.bzl index 4b9f6d85..05d88ee1 100644 --- a/bazel/fuzz_target.bzl +++ b/bazel/fuzz_target.bzl @@ -74,6 +74,8 @@ def java_fuzz_target_test( # Use the same memory settings for reproducers as those suggested by Jazzer when # encountering an OutOfMemoryError. "-Xmx1620m", + # Ensure that reproducers can be compiled even if they contain UTF-8 characters. + "-Dfile.encoding=UTF-8", ], size = size or "enormous", timeout = timeout or "moderate", -- cgit v1.2.3 From 3c990207a207cd92098fece612e8766c451da6c2 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sun, 24 Apr 2022 17:06:03 +0200 Subject: Retransform all dependencies of hooks Since a recent refactoring, the RuntimeInstrumentor is registered only after hooks have been loaded, resulting in dependencies of hooks missing all instrumentation, rather than just custom hooks. With the new initialization order, it should now be safe to fully retransform all such classes since they are known not to have been instrumented for coverage before. --- .../com/code_intelligence/jazzer/agent/Agent.kt | 47 +++++++++------------- tests/BUILD.bazel | 12 ++++++ 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index 3ce50e03..e1d08d91 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -161,11 +161,6 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { else MemSyncCoverageIdStrategy() - val classesToHookBeforeLoadingCustomHooks = instrumentation.allLoadedClasses - .map { it.name } - .filter { customHookClassNameGlobber.includes(it) } - .toSet() - val (includedHooks, customHooks) = Hooks.loadHooks(includedHookNames.toSet(), customHookNames.toSet()) // If we don't append the JARs containing the custom hooks to the bootstrap class loader, // third-party hooks not contained in the agent JAR will not be able to instrument Java standard @@ -189,33 +184,29 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { coverageIdSynchronizer, dumpClassesDir, ) - instrumentation.addTransformer(runtimeInstrumentor, true) - val classesToHookAfterLoadingCustomHooks = instrumentation.allLoadedClasses - .map { it.name } + // These classes are e.g. dependencies of the RuntimeInstrumentor or hooks and thus were loaded + // before the instrumentor was ready. Since we haven't enabled it yet, they can safely be + // "retransformed": They haven't been transformed yet. + val classesToRetransform = instrumentation.allLoadedClasses .filter { - customHookClassNameGlobber.includes(it) || - customHooks.additionalHookClassNameGlobber.includes(it) + classNameGlobber.includes(it.name) || + customHookClassNameGlobber.includes(it.name) || + customHooks.additionalHookClassNameGlobber.includes(it.name) } - .toSet() - val classesMissingHooks = - (classesToHookAfterLoadingCustomHooks - classesToHookBeforeLoadingCustomHooks).toMutableSet() - if (classesMissingHooks.isNotEmpty()) { - if (instrumentation.isRetransformClassesSupported) { - // Only retransform classes that are not subject to coverage instrumentation since - // our coverage instrumentation does not support retransformation yet. - val classesToHook = classesMissingHooks - .filter { !classNameGlobber.includes(it) } - .map { Class.forName(it) } - .toTypedArray() - if (classesToHook.isNotEmpty()) { - instrumentation.retransformClasses(*classesToHook) - } - classesMissingHooks -= classesToHook.map { it.name }.toSet() + .filter { + instrumentation.isModifiableClass(it) } - if (classesMissingHooks.isNotEmpty()) { - println("WARN: Hooks were not applied to the following classes as they are dependencies of hooks:") - println("WARN: ${classesMissingHooks.joinToString()}") + .toTypedArray() + + instrumentation.addTransformer(runtimeInstrumentor, true) + + if (classesToRetransform.isNotEmpty()) { + if (instrumentation.isRetransformClassesSupported) { + instrumentation.retransformClasses(*classesToRetransform) + } else { + println("WARN: Instrumentation was not applied to the following classes as they are dependencies of hooks:") + println("WARN: ${classesToRetransform.joinToString()}") } } diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 4b704c90..86613558 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -48,6 +48,18 @@ java_fuzz_target_test( ], ) +java_fuzz_target_test( + name = "AutofuzzHookDependencies", + # The reproducer does not include the hook on OOM and thus throws a regular error. + expected_findings = ["java.lang.OutOfMemoryError"], + fuzzer_args = [ + "--instrumentation_includes=java.util.regex.**", + "--autofuzz=java.util.regex.Pattern::compile", + "--autofuzz_ignore=java.lang.Exception", + "--keep_going=1", + ], +) + java_fuzz_target_test( name = "ForkModeFuzzer", size = "enormous", -- cgit v1.2.3 From ab21bdc5f0b8788381ded9b8e6398d4de9b0c172 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 25 Apr 2022 17:41:20 +0200 Subject: Simplify jazzer-autofuzz entrypoint Setting the prefixes explicitly is not necessary due to the WORKDIR directive. Dropping them has the additional benefit that printed paths will resolve on the host when mounting $(pwd). --- docker/jazzer-autofuzz/entrypoint.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker/jazzer-autofuzz/entrypoint.sh b/docker/jazzer-autofuzz/entrypoint.sh index 78c57f71..6c17f125 100755 --- a/docker/jazzer-autofuzz/entrypoint.sh +++ b/docker/jazzer-autofuzz/entrypoint.sh @@ -17,8 +17,6 @@ set -e CP="$(/app/coursier.jar fetch --classpath "$1")" /app/jazzer_driver \ - -artifact_prefix=/fuzzing/ \ - --reproducer_path=/fuzzing \ --cp="$CP" \ --autofuzz="$2" \ "${@:3}" -- cgit v1.2.3 From 82234415ac6c19473978fd7e3a41b92fded199f0 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Wed, 27 Apr 2022 15:04:04 +0200 Subject: Update changelog with missing versions Co-authored-by: Fabian Meumertzheim --- CHANGELOG.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 516b32c0..fee9e148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,52 @@ **Note:** Before version 1.0.0, every release may contain breaking changes. +## Unreleased + +* Feature: Add sanitizer for context lookups +* Feature: Add sanitizer for OS command injection +* Feature: Add sanitizer for regex injection +* Feature: Add sanitizer for LDAP injections +* Feature: Add sanitizer for arbitrary class loading +* Feature: Guide fuzzer to generate proper map lookups keys +* Feature: Generate standalone Java reproducers for autofuzz +* Feature: Hooks targeting interfaces and abstract classes hook all implementations +* Feature: Enable multiple BEFORE and AFTER hooks for the same target +* Feature: Greatly improve performance of coverage instrumentation +* Feature: Improve performance of interactions between Jazzer and libFuzzer +* Feature: Export JaCoCo coverage dump using `--coverage_dump` flag +* Feature: Honor `JAVA_OPTS` +* API: Add `exploreState` to help the fuzzer maximize state coverage +* API: Provide `additionalClassesToHook` field in `MethodHook` annotation to hook dependent classes +* Fix: Synchronize coverage ID generation +* Fix: Support REPLACE hooks for constructors +* Fix: Do not apply REPLACE hooks in Java 6 class files + +This release also includes smaller improvements and bugfixes. + +## Version 0.10.0 + +* **Breaking change**: Use OS-specific classpath separator to split jvm_args +* Feature: Add support to "autofuzz" targets without the need to manually write fuzz targets +* Feature: Add macOS and Windows support +* Feature: Add option to generate coverage report +* Feature: Support multiple hook annotations per hook method +* Feature: Support hooking internal classes +* Feature: Add sanitizer for insecure deserialization +* Feature: Add sanitizer for arbitrary reflective calls +* Feature: Add sanitizer for expression language injection +* Feature: Provide Jazzer and Jazzer Autofuzz docker images +* Feature: Add a stand-alone replayer to reproduce findings +* API: Add `reportFindingFromHook(Throwable finding)` to report findings from hooks +* API: Add `guideTowardsEquality(String current, String target, int id)` and `guideTowardsContainment(String haystack, String needle, int id)` to guide the fuzzer to generate more useful inputs +* API: Add `consume(FuzzedDataProvider data, Class type)` to create an object instance of the given type from the fuzzer input +* API: Add multiple `autofuzz()` methods to invoke given functions with arguments automatically created from the fuzzer input +* Fixed: Prevent dependency version conflicts in fuzzed application by shading internal dependencies +* Fixed: Make initialized `this` object available to `` AFTER hooks +* Fixed: Allow instrumented classes loaded by custom class loaders to find Jazzer internals + +This release also includes smaller improvements and bugfixes. + ## Version 0.9.1 * **Breaking change**: The static `fuzzerTestOneInput` method in a fuzz target now has to return `void` instead of `boolean`. Fuzz targets that previously returned `true` should now throw an exception or use `assert`. -- cgit v1.2.3 From 2d2505366e3f1f5790e5e93281483fb6759f36d2 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 27 Apr 2022 18:11:03 +0200 Subject: Update JaCoCo to 0.8.8 --- repositories.bzl | 6 +++--- third_party/jacoco-make-probe-inserter-subclassable.patch | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/repositories.bzl b/repositories.bzl index dcc06e45..bd9739b6 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -136,9 +136,9 @@ def jazzer_dependencies(): Label("//third_party:jacoco-make-probe-adapter-subclassable.patch"), Label("//third_party:jacoco-make-probe-inserter-subclassable.patch"), ], - sha256 = "d764c2c02caf8836a12ac582263a109dcac9c1389d3ddee0548aed1322f6e01c", - strip_prefix = "jacoco-0.8.7", - url = "https://github.com/jacoco/jacoco/archive/refs/tags/v0.8.7.tar.gz", + sha256 = "c603cfcc5f3d95ecda46fb369dc54c82a453bb6b640a605c3970607d10896725", + strip_prefix = "jacoco-0.8.8", + url = "https://github.com/jacoco/jacoco/archive/refs/tags/v0.8.8.tar.gz", ) maybe( diff --git a/third_party/jacoco-make-probe-inserter-subclassable.patch b/third_party/jacoco-make-probe-inserter-subclassable.patch index 04b5be0e..d867da7e 100644 --- a/third_party/jacoco-make-probe-inserter-subclassable.patch +++ b/third_party/jacoco-make-probe-inserter-subclassable.patch @@ -83,8 +83,8 @@ index 0f5b99ff..80965dfe 100644 - private final int variable; + protected final int variable; - /** Maximum stack usage of the code to access the probe array. */ - private int accessorStackSize; + /** Label for the new beginning of the method */ + private final Label beginLabel; @@ -56,7 +56,7 @@ class ProbeInserter extends MethodVisitor implements IProbeInserter { * callback to create the code that retrieves the reference to * the probe array @@ -104,7 +104,7 @@ index 0f5b99ff..80965dfe 100644 + @Override public void visitCode() { - accessorStackSize = arrayStrategy.storeInstance(mv, clinit, variable); + mv.visitLabel(beginLabel); @@ -118,6 +122,10 @@ class ProbeInserter extends MethodVisitor implements IProbeInserter { public AnnotationVisitor visitLocalVariableAnnotation(final int typeRef, final TypePath typePath, final Label[] start, final Label[] end, -- cgit v1.2.3 From 8a49448b3b1d0ab125d7b96b8315cf08447b05e1 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Thu, 28 Apr 2022 15:23:02 +0200 Subject: Release version 0.11.0 --- CHANGELOG.md | 2 +- maven.bzl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fee9e148..0898a241 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ **Note:** Before version 1.0.0, every release may contain breaking changes. -## Unreleased +## Version 0.11.0 * Feature: Add sanitizer for context lookups * Feature: Add sanitizer for OS command injection diff --git a/maven.bzl b/maven.bzl index 1c104839..284706d2 100644 --- a/maven.bzl +++ b/maven.bzl @@ -14,7 +14,7 @@ load("@rules_jvm_external//:specs.bzl", "maven") -JAZZER_API_VERSION = "0.10.0" +JAZZER_API_VERSION = "0.11.0" JAZZER_API_COORDINATES = "com.code-intelligence:jazzer-api:%s" % JAZZER_API_VERSION # **WARNING**: These Maven dependencies have known vulnerabilities and are only used to test that -- cgit v1.2.3 From 3b40a06f7296c0a5baf809a70b73afe1eff49f72 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Thu, 28 Apr 2022 16:05:13 +0200 Subject: Use same runners in release as in test workflow windows-2016 runners were removed (https://github.com/actions/virtual-environments/issues/4312) --- .github/workflows/release.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4667727c..bde7ec68 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,12 +4,11 @@ on: workflow_dispatch: jobs: - build_release: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-10.15, windows-2016] + os: [ubuntu-latest, macos-10.15, windows-2019] include: - os: ubuntu-latest arch: "linux" @@ -17,7 +16,7 @@ jobs: - os: macos-10.15 arch: "macos-x86_64" bazel_args: "--config=toolchain --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-darwin --xcode_version_config=//.github:host_xcodes" - - os: windows-2016 + - os: windows-2019 arch: "windows" bazel_args: "" -- cgit v1.2.3 From 42a5f511a2719c8ee289c24f396a11cf997753a8 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Thu, 28 Apr 2022 16:33:35 +0200 Subject: Remove JDK debug output in workflows --- .github/workflows/release.yml | 4 ---- .github/workflows/run-all-tests.yml | 4 ---- 2 files changed, 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bde7ec68..6fa7db73 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,10 +38,6 @@ jobs: cp -L bazel-bin/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer_deploy.jar replayer.jar cp -L bazel-bin/jazzer_release.tar.gz release-${{ matrix.arch }}.tar.gz - - name: Print JDK used - if: matrix.os != 'windows-2016' - run: cat $(bazel info execution_root)/external/local_jdk/BUILD.bazel | grep java_home - - name: Upload replayer uses: actions/upload-artifact@v2 with: diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-all-tests.yml index 89587148..35334b89 100644 --- a/.github/workflows/run-all-tests.yml +++ b/.github/workflows/run-all-tests.yml @@ -53,10 +53,6 @@ jobs: - name: Test run: bazelisk test ${{env.BUILD_BUDDY_CONFIG}} --java_runtime_version=local_jdk_${{ matrix.jdk }} --disk_cache=${{ matrix.cache }} ${{ matrix.bazel_args }} //... - - name: Print JDK used - if: matrix.os != 'windows-latest' - run: cat $(bazel info execution_root)/external/local_jdk/BUILD.bazel | grep java_home - - name: Upload test logs if: always() uses: actions/upload-artifact@v2 -- cgit v1.2.3 From 3b473ad5b2a142150e3a9182b5670b160261f2c8 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Thu, 28 Apr 2022 17:23:26 +0200 Subject: Fix replayer names in release workflow --- .github/workflows/release.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6fa7db73..2cbfbaae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,6 +8,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: + # Keep arch names in sync with replayer download and merge os: [ubuntu-latest, macos-10.15, windows-2019] include: - os: ubuntu-latest @@ -58,8 +59,8 @@ jobs: - name: Download macOS jar uses: actions/download-artifact@v2 with: - name: replayer_darwin - path: replayer_darwin + name: replayer_macos-x86_64 + path: replayer_macos-x86_64 - name: Download Linux jar uses: actions/download-artifact@v2 @@ -76,7 +77,7 @@ jobs: - name: Merge jars run: | mkdir merged - unzip -o replayer_darwin/replayer.jar -d merged + unzip -o replayer_macos-x86_64/replayer.jar -d merged unzip -o replayer_linux/replayer.jar -d merged unzip -o replayer_windows/replayer.jar -d merged jar cvmf merged/META-INF/MANIFEST.MF replayer.jar -C merged . -- cgit v1.2.3 From d4967bbcb997a02ecfc7b0634caaa4c85b0399cc Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Thu, 5 May 2022 09:12:07 +0200 Subject: Move honeypot class to API The honeypot class jaz.Zer is needed to successfully execute reproducers of deserialization issues. It's moved to the API artifact to be available on the classpath. Furthermore, reproducers not necessarily reproduce the found issue, e.g. due to global state that leads to different behavior. To take that into account the reproducer check is relaxed. --- .../com/code_intelligence/jazzer/api/BUILD.bazel | 1 + .../com/code_intelligence/jazzer/api/Jazzer.java | 7 +- agent/src/main/java/jaz/BUILD.bazel | 8 + agent/src/main/java/jaz/Ter.java | 24 +++ agent/src/main/java/jaz/Zer.java | 234 +++++++++++++++++++++ .../jazzer/tools/FuzzTargetTestWrapper.java | 1 + driver/fuzz_target_runner.cpp | 1 - sanitizers/BUILD.bazel | 1 - .../jazzer/sanitizers/BUILD.bazel | 1 - .../code_intelligence/jazzer/sanitizers/Utils.kt | 13 +- sanitizers/src/main/java/jaz/BUILD.bazel | 12 -- sanitizers/src/main/java/jaz/Ter.java | 24 --- sanitizers/src/main/java/jaz/Zer.java | 234 --------------------- sanitizers/src/test/java/com/example/BUILD.bazel | 3 + 14 files changed, 286 insertions(+), 278 deletions(-) create mode 100644 agent/src/main/java/jaz/BUILD.bazel create mode 100644 agent/src/main/java/jaz/Ter.java create mode 100644 agent/src/main/java/jaz/Zer.java delete mode 100644 sanitizers/src/main/java/jaz/BUILD.bazel delete mode 100644 sanitizers/src/main/java/jaz/Ter.java delete mode 100644 sanitizers/src/main/java/jaz/Zer.java diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/api/BUILD.bazel index e573e757..b26bb846 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/BUILD.bazel +++ b/agent/src/main/java/com/code_intelligence/jazzer/api/BUILD.bazel @@ -23,6 +23,7 @@ java_library( "Jazzer.java", "MethodHook.java", "MethodHooks.java", + "//agent/src/main/java/jaz", ], visibility = ["//visibility:public"], ) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java index 8f9024fc..2d2e82b0 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java @@ -593,10 +593,9 @@ final public class Jazzer { try { JAZZER_INTERNAL.getMethod("reportFindingFromHook", Throwable.class).invoke(null, finding); } catch (NullPointerException | IllegalAccessException | NoSuchMethodException e) { - // We can only reach this point if the runtime is not in the classpath, but it must be if - // hooks work and this function should only be called from them. - System.err.println("ERROR: Jazzer.reportFindingFromHook must be called from a method hook"); - System.exit(1); + // We can only reach this point if the runtime is not on the classpath, e.g. in case of a + // reproducer. Just throw the finding. + rethrowUnchecked(finding); } catch (InvocationTargetException e) { // reportFindingFromHook throws a HardToCatchThrowable, which will bubble up wrapped in an // InvocationTargetException that should not be stopped here. diff --git a/agent/src/main/java/jaz/BUILD.bazel b/agent/src/main/java/jaz/BUILD.bazel new file mode 100644 index 00000000..c6cdcf13 --- /dev/null +++ b/agent/src/main/java/jaz/BUILD.bazel @@ -0,0 +1,8 @@ +filegroup( + name = "jaz", + srcs = [ + "Ter.java", + "Zer.java", + ], + visibility = ["//agent/src/main/java/com/code_intelligence/jazzer/api:__pkg__"], +) diff --git a/agent/src/main/java/jaz/Ter.java b/agent/src/main/java/jaz/Ter.java new file mode 100644 index 00000000..7814396f --- /dev/null +++ b/agent/src/main/java/jaz/Ter.java @@ -0,0 +1,24 @@ +// 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 jaz; + +/** + * A safe to use companion of {@link jaz.Zer} that is used to produce serializable instances of it + * with only light patching. + */ +@SuppressWarnings("unused") +public class Ter implements java.io.Serializable { + static final long serialVersionUID = 42L; +} diff --git a/agent/src/main/java/jaz/Zer.java b/agent/src/main/java/jaz/Zer.java new file mode 100644 index 00000000..08ca3d2e --- /dev/null +++ b/agent/src/main/java/jaz/Zer.java @@ -0,0 +1,234 @@ +// 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 jaz; + +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh; +import com.code_intelligence.jazzer.api.Jazzer; +import java.io.Closeable; +import java.io.Flushable; +import java.io.Serializable; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.function.Function; + +/** + * A honeypot class that reports a finding on initialization. + * + * Class loading based on externally controlled data could lead to RCE + * depending on available classes on the classpath. Even if no applicable + * gadget class is available, allowing input to control class loading is a bad + * idea and should be prevented. A finding is generated whenever the class + * is loaded and initialized, regardless of its further use. + *

    + * This class needs to implement {@link Serializable} to be considered in + * deserialization scenarios. It also implements common constructors, getter + * and setter and common interfaces to increase chances of passing + * deserialization checks. + *

    + * Note: Jackson provides a nice list of "nasty classes" at + * SubTypeValidator. + *

    + * Note: This class must not be referenced in any way by the rest of the code, not even + * statically. When referring to it, always use its hardcoded class name {@code jaz.Zer}. + */ +@SuppressWarnings({"rawtypes", "unused"}) +public class Zer + implements Serializable, Cloneable, Comparable, Comparator, Closeable, Flushable, Iterable, + Iterator, Runnable, Callable, Function, Collection, List { + static final long serialVersionUID = 42L; + + static { + Jazzer.reportFindingFromHook(new FuzzerSecurityIssueHigh("Remote Code Execution\n" + + "Unrestricted class loading based on externally controlled data may allow\n" + + "remote code execution depending on available classes on the classpath.")); + } + + // Common constructors + + public Zer() {} + + public Zer(String arg1) {} + + public Zer(String arg1, Throwable arg2) {} + + // Getter/Setter + + public Object getJaz() { + return this; + } + + public void setJaz(String jaz) {} + + // Common interface stubs + + @Override + public void close() {} + + @Override + public void flush() {} + + @Override + public int compareTo(Zer o) { + return 0; + } + + @Override + public int compare(Object o1, Object o2) { + return 0; + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean contains(Object o) { + return false; + } + + @Override + public Object[] toArray() { + return new Object[0]; + } + + @Override + public boolean add(Object o) { + return false; + } + + @Override + public boolean remove(Object o) { + return false; + } + + @Override + public boolean addAll(Collection c) { + return false; + } + + @Override + public boolean addAll(int index, Collection c) { + return false; + } + + @Override + public void clear() {} + + @Override + public Object get(int index) { + return this; + } + + @Override + public Object set(int index, Object element) { + return this; + } + + @Override + public void add(int index, Object element) {} + + @Override + public Object remove(int index) { + return this; + } + + @Override + public int indexOf(Object o) { + return 0; + } + + @Override + public int lastIndexOf(Object o) { + return 0; + } + + @Override + @SuppressWarnings("ConstantConditions") + public ListIterator listIterator() { + return null; + } + + @Override + @SuppressWarnings("ConstantConditions") + public ListIterator listIterator(int index) { + return null; + } + + @Override + public List subList(int fromIndex, int toIndex) { + return this; + } + + @Override + public boolean retainAll(Collection c) { + return false; + } + + @Override + public boolean removeAll(Collection c) { + return false; + } + + @Override + public boolean containsAll(Collection c) { + return false; + } + + @Override + public Object[] toArray(Object[] a) { + return new Object[0]; + } + + @Override + public Iterator iterator() { + return this; + } + + @Override + public void run() {} + + @Override + public boolean hasNext() { + return false; + } + + @Override + public Object next() { + return this; + } + + @Override + public Object call() throws Exception { + return this; + } + + @Override + public Object apply(Object o) { + return this; + } + + @Override + @SuppressWarnings("MethodDoesntCallSuperMethod") + public Object clone() { + return this; + } +} diff --git a/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java b/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java index 0cafc036..edf3887e 100644 --- a/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java +++ b/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java @@ -209,6 +209,7 @@ public class FuzzTargetTestWrapper { throw new IllegalStateException("Expected crash with any of " + String.join(", ", expectedFindings) + " not reproduced by " + classFile); } + System.out.println("Reproducer finished successfully without finding"); } catch (InvocationTargetException e) { // expect the invocation to fail with the prescribed finding Throwable finding = e.getCause(); diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index 663971e8..455d6e72 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -410,7 +410,6 @@ void FuzzTargetRunner::DumpReproducer(const uint8_t *data, std::size_t size) { const auto finding = GetFinding(); if (finding == nullptr) { LOG(ERROR) << "Failed to reproduce crash when rerunning with recorder"; - return; } base64_data = SerializeRecordingFuzzedDataProvider(jvm_, recorder); } else { diff --git a/sanitizers/BUILD.bazel b/sanitizers/BUILD.bazel index fa84208e..fdc616a3 100644 --- a/sanitizers/BUILD.bazel +++ b/sanitizers/BUILD.bazel @@ -3,6 +3,5 @@ java_library( visibility = ["//visibility:public"], runtime_deps = [ "//sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers", - "//sanitizers/src/main/java/jaz", ], ) diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel index b585ecb8..f066b35f 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel @@ -28,6 +28,5 @@ kt_jvm_library( ], deps = [ "//agent:jazzer_api_compile_only", - "//sanitizers/src/main/java/jaz", ], ) diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt index 52519cac..219490d8 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt @@ -44,9 +44,20 @@ internal fun ByteArray.indexOf(needle: ByteArray): Int { } internal fun guideMarkableInputStreamTowardsEquality(stream: InputStream, target: ByteArray, id: Int) { + fun readBytes(stream: InputStream, size: Int): ByteArray { + val current = ByteArray(size) + var n = 0 + while (n < size) { + val count = stream.read(current, n, size - n) + if (count < 0) break + n += count + } + return current + } + check(stream.markSupported()) stream.mark(target.size) - val current = stream.readNBytes(target.size) + val current = readBytes(stream, target.size) stream.reset() Jazzer.guideTowardsEquality(current, target, id) } diff --git a/sanitizers/src/main/java/jaz/BUILD.bazel b/sanitizers/src/main/java/jaz/BUILD.bazel deleted file mode 100644 index 81275a31..00000000 --- a/sanitizers/src/main/java/jaz/BUILD.bazel +++ /dev/null @@ -1,12 +0,0 @@ -java_library( - name = "jaz", - srcs = [ - "Ter.java", - "Zer.java", - ], - visibility = [ - "//sanitizers:__pkg__", - "//sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers:__pkg__", - ], - deps = ["//agent:jazzer_api_compile_only"], -) diff --git a/sanitizers/src/main/java/jaz/Ter.java b/sanitizers/src/main/java/jaz/Ter.java deleted file mode 100644 index 7814396f..00000000 --- a/sanitizers/src/main/java/jaz/Ter.java +++ /dev/null @@ -1,24 +0,0 @@ -// 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 jaz; - -/** - * A safe to use companion of {@link jaz.Zer} that is used to produce serializable instances of it - * with only light patching. - */ -@SuppressWarnings("unused") -public class Ter implements java.io.Serializable { - static final long serialVersionUID = 42L; -} diff --git a/sanitizers/src/main/java/jaz/Zer.java b/sanitizers/src/main/java/jaz/Zer.java deleted file mode 100644 index 08ca3d2e..00000000 --- a/sanitizers/src/main/java/jaz/Zer.java +++ /dev/null @@ -1,234 +0,0 @@ -// 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 jaz; - -import com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh; -import com.code_intelligence.jazzer.api.Jazzer; -import java.io.Closeable; -import java.io.Flushable; -import java.io.Serializable; -import java.util.*; -import java.util.concurrent.Callable; -import java.util.function.Function; - -/** - * A honeypot class that reports a finding on initialization. - * - * Class loading based on externally controlled data could lead to RCE - * depending on available classes on the classpath. Even if no applicable - * gadget class is available, allowing input to control class loading is a bad - * idea and should be prevented. A finding is generated whenever the class - * is loaded and initialized, regardless of its further use. - *

    - * This class needs to implement {@link Serializable} to be considered in - * deserialization scenarios. It also implements common constructors, getter - * and setter and common interfaces to increase chances of passing - * deserialization checks. - *

    - * Note: Jackson provides a nice list of "nasty classes" at - * SubTypeValidator. - *

    - * Note: This class must not be referenced in any way by the rest of the code, not even - * statically. When referring to it, always use its hardcoded class name {@code jaz.Zer}. - */ -@SuppressWarnings({"rawtypes", "unused"}) -public class Zer - implements Serializable, Cloneable, Comparable, Comparator, Closeable, Flushable, Iterable, - Iterator, Runnable, Callable, Function, Collection, List { - static final long serialVersionUID = 42L; - - static { - Jazzer.reportFindingFromHook(new FuzzerSecurityIssueHigh("Remote Code Execution\n" - + "Unrestricted class loading based on externally controlled data may allow\n" - + "remote code execution depending on available classes on the classpath.")); - } - - // Common constructors - - public Zer() {} - - public Zer(String arg1) {} - - public Zer(String arg1, Throwable arg2) {} - - // Getter/Setter - - public Object getJaz() { - return this; - } - - public void setJaz(String jaz) {} - - // Common interface stubs - - @Override - public void close() {} - - @Override - public void flush() {} - - @Override - public int compareTo(Zer o) { - return 0; - } - - @Override - public int compare(Object o1, Object o2) { - return 0; - } - - @Override - public int size() { - return 0; - } - - @Override - public boolean isEmpty() { - return false; - } - - @Override - public boolean contains(Object o) { - return false; - } - - @Override - public Object[] toArray() { - return new Object[0]; - } - - @Override - public boolean add(Object o) { - return false; - } - - @Override - public boolean remove(Object o) { - return false; - } - - @Override - public boolean addAll(Collection c) { - return false; - } - - @Override - public boolean addAll(int index, Collection c) { - return false; - } - - @Override - public void clear() {} - - @Override - public Object get(int index) { - return this; - } - - @Override - public Object set(int index, Object element) { - return this; - } - - @Override - public void add(int index, Object element) {} - - @Override - public Object remove(int index) { - return this; - } - - @Override - public int indexOf(Object o) { - return 0; - } - - @Override - public int lastIndexOf(Object o) { - return 0; - } - - @Override - @SuppressWarnings("ConstantConditions") - public ListIterator listIterator() { - return null; - } - - @Override - @SuppressWarnings("ConstantConditions") - public ListIterator listIterator(int index) { - return null; - } - - @Override - public List subList(int fromIndex, int toIndex) { - return this; - } - - @Override - public boolean retainAll(Collection c) { - return false; - } - - @Override - public boolean removeAll(Collection c) { - return false; - } - - @Override - public boolean containsAll(Collection c) { - return false; - } - - @Override - public Object[] toArray(Object[] a) { - return new Object[0]; - } - - @Override - public Iterator iterator() { - return this; - } - - @Override - public void run() {} - - @Override - public boolean hasNext() { - return false; - } - - @Override - public Object next() { - return this; - } - - @Override - public Object call() throws Exception { - return this; - } - - @Override - public Object apply(Object o) { - return this; - } - - @Override - @SuppressWarnings("MethodDoesntCallSuperMethod") - public Object clone() { - return this; - } -} diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index 5152e1e6..8498ab32 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -6,6 +6,7 @@ java_fuzz_target_test( srcs = [ "ObjectInputStreamDeserialization.java", ], + expected_findings = ["java.lang.ExceptionInInitializerError"], target_class = "com.example.ObjectInputStreamDeserialization", ) @@ -14,6 +15,7 @@ java_fuzz_target_test( srcs = [ "ReflectiveCall.java", ], + expected_findings = ["java.lang.ExceptionInInitializerError"], target_class = "com.example.ReflectiveCall", ) @@ -107,6 +109,7 @@ java_fuzz_target_test( srcs = [ "ClassLoaderLoadClass.java", ], + expected_findings = ["java.lang.ExceptionInInitializerError"], target_class = "com.example.ClassLoaderLoadClass", ) -- cgit v1.2.3 From 0b550fdc87b918d9df56517079aaf47bd5565445 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 2 May 2022 13:24:19 +0200 Subject: Disable host toolchain detection with --config=toolchain We want --config=toolchain to enforce using a hermetic toolchain and thus disable Bazel's auto detection of the host toolchain. --- .bazelrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.bazelrc b/.bazelrc index 827f0f39..dd3094b9 100644 --- a/.bazelrc +++ b/.bazelrc @@ -31,6 +31,7 @@ run:windows --noincompatible_strict_action_env # Toolchain # Since the toolchain is conditional on OS and architecture, set it on the particular GitHub Action. +build:toolchain --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 build:toolchain --//third_party:toolchain # Forward debug variables to tests -- cgit v1.2.3 From d16f0e5464817279f0c3fad9dddf5ac4dcc1fbf2 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 2 May 2022 14:42:55 +0200 Subject: Clear reported finding after it has been handled Otherwise this finding will stick around and cause dedup token computation for every subsequent run, even if that run would otherwise produce no finding. --- driver/fuzz_target_runner.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index 455d6e72..976490c6 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -359,6 +359,7 @@ jthrowable FuzzTargetRunner::GetFinding() const { reported_finding != nullptr) { env.DeleteLocalRef(unprocessed_finding); unprocessed_finding = reported_finding; + env.SetStaticObjectField(jazzer_, last_finding_, nullptr); } jthrowable processed_finding = preprocessException(unprocessed_finding); // If preprocessException returns the same object that we passed to it, we -- cgit v1.2.3 From 2045811f87e17bb9676ded47960f20feed681fd9 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 2 May 2022 14:58:19 +0200 Subject: Fix a reference leak in DumpReproducer --- driver/fuzz_target_runner.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index 976490c6..0ebc79f1 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -412,6 +412,7 @@ void FuzzTargetRunner::DumpReproducer(const uint8_t *data, std::size_t size) { if (finding == nullptr) { LOG(ERROR) << "Failed to reproduce crash when rerunning with recorder"; } + env.DeleteLocalRef(finding); base64_data = SerializeRecordingFuzzedDataProvider(jvm_, recorder); } else { absl::string_view data_str(reinterpret_cast(data), size); -- cgit v1.2.3 From bf3bf3e16a8c08816be258ff3688c1c13f1bc712 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 2 May 2022 14:58:36 +0200 Subject: Fix a reference leak in GetFinding We need to delete each local reference created in native code separately to prevent memory leaks, even if they refer to the same Java object. --- driver/fuzz_target_runner.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index 0ebc79f1..4af0c1d0 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -362,9 +362,11 @@ jthrowable FuzzTargetRunner::GetFinding() const { env.SetStaticObjectField(jazzer_, last_finding_, nullptr); } jthrowable processed_finding = preprocessException(unprocessed_finding); - // If preprocessException returns the same object that we passed to it, we - // must not delete the reference count. - if (!env.IsSameObject(processed_finding, unprocessed_finding)) { + // If preprocessException returns the same local reference that we passed to + // it, we must not decrease the reference count as the returned object will + // outlive this method. Otherwise, we have to delete it to prevent leaking the + // unprocessed exception. + if (processed_finding != unprocessed_finding) { env.DeleteLocalRef(unprocessed_finding); } return processed_finding; -- cgit v1.2.3 From 15c601faeac8d77dd991f8a2c01ba539a14be81d Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 2 May 2022 13:25:41 +0200 Subject: Consolidate error handling in FuzzTargetTestWrapper Use 1 as the universal error case exit code and instead print helpful messages in all cases where no stack trace is printed. Also perform all argument parsing at the beginning of the main function. --- .../jazzer/tools/FuzzTargetTestWrapper.java | 26 ++++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java b/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java index edf3887e..f14f2499 100644 --- a/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java +++ b/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java @@ -47,6 +47,7 @@ public class FuzzTargetTestWrapper { boolean verifyCrashInput; boolean verifyCrashReproducer; Set expectedFindings; + Stream arguments; try { runfiles = Runfiles.create(); driverActualPath = lookUpRunfile(runfiles, args[0]); @@ -56,6 +57,9 @@ public class FuzzTargetTestWrapper { verifyCrashReproducer = Boolean.parseBoolean(args[4]); expectedFindings = Arrays.stream(args[5].split(",")).filter(s -> !s.isEmpty()).collect(Collectors.toSet()); + // Map all files/dirs to real location + arguments = Arrays.stream(args).skip(6).map( + arg -> arg.startsWith("-") ? arg : lookUpRunfileWithFallback(runfiles, arg)); } catch (IOException | ArrayIndexOutOfBoundsException e) { e.printStackTrace(); System.exit(1); @@ -71,10 +75,6 @@ public class FuzzTargetTestWrapper { // so this is only useful for examples. String outputDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR"); - // Map all files/dirs to real location - Stream arguments = Arrays.stream(args).skip(6).map( - arg -> arg.startsWith("-") ? arg : lookUpRunfileWithFallback(runfiles, arg)); - List command = Stream .concat(Stream.of(driverActualPath, String.format("-artifact_prefix=%s/", outputDir), @@ -95,27 +95,29 @@ public class FuzzTargetTestWrapper { // Assert that we either found a crash in Java (exit code 77) or a sanitizer crash (exit code // 76). if (exitCode != 76 && exitCode != 77) { - System.exit(3); + System.err.printf("Did expect a crash, but Jazzer exited with exit code %d%n", exitCode); + System.exit(1); } String[] outputFiles = new File(outputDir).list(); if (outputFiles == null) { - System.exit(4); + System.err.printf("Jazzer did not write a crashing input into %s%n", outputDir); + System.exit(1); } // Verify that libFuzzer dumped a crashing input. if (JAZZER_CI && verifyCrashInput && Arrays.stream(outputFiles).noneMatch(name -> name.startsWith("crash-"))) { - System.out.printf("No crashing input found in %s%n", outputDir); - System.exit(5); + System.err.printf("No crashing input found in %s%n", outputDir); + System.exit(1); } // Verify that libFuzzer dumped a crash reproducer. if (JAZZER_CI && verifyCrashReproducer && Arrays.stream(outputFiles).noneMatch(name -> name.startsWith("Crash_"))) { - System.out.printf("No crash reproducer found in %s%n", outputDir); - System.exit(6); + System.err.printf("No crash reproducer found in %s%n", outputDir); + System.exit(1); } } catch (IOException | InterruptedException e) { e.printStackTrace(); - System.exit(2); + System.exit(1); } if (JAZZER_CI && verifyCrashReproducer) { @@ -124,7 +126,7 @@ public class FuzzTargetTestWrapper { outputDir, driverActualPath, apiActualPath, jarActualPath, expectedFindings); } catch (Exception e) { e.printStackTrace(); - System.exit(6); + System.exit(1); } } System.exit(0); -- cgit v1.2.3 From 5c15820f8c0fd7478c0f77e71fbcfc30a3f170f6 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 2 May 2022 13:26:56 +0200 Subject: Allow java_fuzz_target_tests to specify that they don't crash --- bazel/fuzz_target.bzl | 2 ++ .../jazzer/tools/FuzzTargetTestWrapper.java | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/bazel/fuzz_target.bzl b/bazel/fuzz_target.bzl index 05d88ee1..8760d4eb 100644 --- a/bazel/fuzz_target.bzl +++ b/bazel/fuzz_target.bzl @@ -28,6 +28,7 @@ def java_fuzz_target_test( env = None, verify_crash_input = True, verify_crash_reproducer = True, + expect_crash = True, # Default is that the reproducer does not throw any exception. expected_findings = [], **kwargs): @@ -85,6 +86,7 @@ def java_fuzz_target_test( "$(rootpath :%s_deploy.jar)" % target_name, str(verify_crash_input), str(verify_crash_reproducer), + str(expect_crash), # args are shell tokenized and thus quotes are required in the case where # expected_findings is empty. "'" + ",".join(expected_findings) + "'", diff --git a/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java b/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java index f14f2499..f4ca0ce7 100644 --- a/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java +++ b/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java @@ -46,6 +46,7 @@ public class FuzzTargetTestWrapper { String jarActualPath; boolean verifyCrashInput; boolean verifyCrashReproducer; + boolean expectCrash; Set expectedFindings; Stream arguments; try { @@ -55,10 +56,11 @@ public class FuzzTargetTestWrapper { jarActualPath = lookUpRunfile(runfiles, args[2]); verifyCrashInput = Boolean.parseBoolean(args[3]); verifyCrashReproducer = Boolean.parseBoolean(args[4]); + expectCrash = Boolean.parseBoolean(args[5]); expectedFindings = - Arrays.stream(args[5].split(",")).filter(s -> !s.isEmpty()).collect(Collectors.toSet()); + Arrays.stream(args[6].split(",")).filter(s -> !s.isEmpty()).collect(Collectors.toSet()); // Map all files/dirs to real location - arguments = Arrays.stream(args).skip(6).map( + arguments = Arrays.stream(args).skip(7).map( arg -> arg.startsWith("-") ? arg : lookUpRunfileWithFallback(runfiles, arg)); } catch (IOException | ArrayIndexOutOfBoundsException e) { e.printStackTrace(); @@ -92,6 +94,14 @@ public class FuzzTargetTestWrapper { try { int exitCode = processBuilder.start().waitFor(); + if (!expectCrash) { + if (exitCode != 0) { + System.err.printf( + "Did not expect a crash, but Jazzer exited with exit code %d%n", exitCode); + System.exit(1); + } + System.exit(0); + } // Assert that we either found a crash in Java (exit code 77) or a sanitizer crash (exit code // 76). if (exitCode != 76 && exitCode != 77) { -- cgit v1.2.3 From b74c4b7838ae6b72127a206dc4ecb4bfdeda7d08 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 2 May 2022 13:44:23 +0200 Subject: Verify the absence of memory leaks in a test --- tests/BUILD.bazel | 20 +++++++++++++++++ .../test/java/com/example/MemoryLeakFuzzer.java | 26 ++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 tests/src/test/java/com/example/MemoryLeakFuzzer.java diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 86613558..fa5f8aea 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -127,6 +127,26 @@ java_fuzz_target_test( ], ) +# Regression test for https://github.com/CodeIntelligenceTesting/jazzer/issues/405. +java_fuzz_target_test( + name = "MemoryLeakFuzzer", + timeout = "short", + srcs = ["src/test/java/com/example/MemoryLeakFuzzer.java"], + env = { + "JAVA_OPTS": "-Xmx800m", + }, + expect_crash = False, + fuzzer_args = [ + # Before the bug was fixed, either the GC overhead limit or the overall heap limit was + # reached by this target in this number of runs. + "-runs=1000000", + # Skip over the first and only exception to keep the fuzzer running until it hits the runs + # limit. + "--keep_going=2", + ], + target_class = "com.example.MemoryLeakFuzzer", +) + JAZZER_API_TEST_CASES = { "default": [], "nohooks": ["--nohooks"], diff --git a/tests/src/test/java/com/example/MemoryLeakFuzzer.java b/tests/src/test/java/com/example/MemoryLeakFuzzer.java new file mode 100644 index 00000000..9f38a1e2 --- /dev/null +++ b/tests/src/test/java/com/example/MemoryLeakFuzzer.java @@ -0,0 +1,26 @@ +/* + * Copyright 2022 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.example; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow; + +public class MemoryLeakFuzzer { + public static void fuzzerTestOneInput(FuzzedDataProvider data) { + throw new FuzzerSecurityIssueLow(); + } +} -- cgit v1.2.3 From 5841674927e1af9a61fd51f37fc48b55ac693ee4 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Fri, 6 May 2022 08:11:00 +0200 Subject: Ignore Azul JDK internal classes Some Azul JDK internal classes get instrumented during startup. Add the `com.azul.tooling` package to the exclusion list. --- .../code_intelligence/jazzer/utils/ClassNameGlobber.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt b/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt index 78d97230..763f230d 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt @@ -21,16 +21,21 @@ private val BASE_INCLUDED_CLASS_NAME_GLOBS = listOf( ) private val BASE_EXCLUDED_CLASS_NAME_GLOBS = listOf( + // JDK internals "\\[**", // array types - "com.code_intelligence.jazzer.**", - "com.sun.**", // package for Proxy objects "java.**", "javax.**", - "jaz.Ter", // safe companion of the honeypot class used by sanitizers - "jaz.Zer", // honeypot class used by sanitizers "jdk.**", - "kotlin.**", "sun.**", + "com.sun.**", // package for Proxy objects + // Azul JDK internals + "com.azul.tooling.**", + // Kotlin internals + "kotlin.**", + // Jazzer internals + "com.code_intelligence.jazzer.**", + "jaz.Ter", // safe companion of the honeypot class used by sanitizers + "jaz.Zer", // honeypot class used by sanitizers ) class ClassNameGlobber(includes: List, excludes: List) { -- cgit v1.2.3 From c950a1e84641ca11eccd209b39eb9b0e83faf3dc Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 11 May 2022 10:35:53 +0200 Subject: Remove optional opcode parameter at default value --- driver/sanitizer_hooks_with_pc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/sanitizer_hooks_with_pc.cpp b/driver/sanitizer_hooks_with_pc.cpp index b50b2d83..eb68addf 100644 --- a/driver/sanitizer_hooks_with_pc.cpp +++ b/driver/sanitizer_hooks_with_pc.cpp @@ -90,7 +90,7 @@ __attribute__((noinline)) void trampoline(uint64_t arg1, uint64_t arg2, // multiples of 4. "and %[fake_pc], %[fake_pc], #0xFFFFFFFFFFFFFFFC \n\t" // Add the offset of the fake_pc-th ret (rounded to 0 mod 4 above). - "add x30, x30, %[fake_pc], lsl 0 \n\t" + "add x30, x30, %[fake_pc] \n\t" // Call the function by jumping to it and reusing all registers except // for the modified return address register r30. "br %[func] \n\t" -- cgit v1.2.3 From ecfcff8170e12a1d44122dc0f847d27521d0370f Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 20 Apr 2022 16:29:57 +0200 Subject: Update Bazel to 5.2.0rc1 This release contains a number of fixes to coverage collection for multi-language targets. A new ErrorProne check has to be disabled for the JaCoCo build to pass. --- .bazelversion | 2 +- third_party/jacoco_internal.BUILD | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.bazelversion b/.bazelversion index 831446cb..64ba3a1a 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -5.1.0 +5.2.0rc1 diff --git a/third_party/jacoco_internal.BUILD b/third_party/jacoco_internal.BUILD index d16a166b..38ac7f6c 100644 --- a/third_party/jacoco_internal.BUILD +++ b/third_party/jacoco_internal.BUILD @@ -27,6 +27,7 @@ java_library( ]), javacopts = [ "-Xep:EqualsHashCode:OFF", + "-Xep:ReturnValueIgnored:OFF", ], deps = [ "@org_ow2_asm_asm//jar", -- cgit v1.2.3 From 224339c9e5b9b5ea0770dec7e74e53a40d75f72c Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 20 Apr 2022 16:29:57 +0200 Subject: Generate end-to-end coverage reports for tests Coverage reports can be generated via: bazel run //bazel/coverage Due to https://github.com/bazelbuild/bazel/pull/15166, coverage collection does not yet encompass tests that end with a native sanitizer report. Temporarily updates .bazelversion to an unreleased version of Bazel that includes required coverage fixes. --- .bazelrc | 12 +++++++++ .gitignore | 1 + WORKSPACE.bazel | 10 +++++++- agent/BUILD.bazel | 5 ++++ .../jazzer/utils/ClassNameGlobber.kt | 14 +++++++--- bazel/coverage/BUILD.bazel | 10 ++++++++ bazel/coverage/coverage.sh | 30 ++++++++++++++++++++++ driver/libfuzzer_fuzz_target.cpp | 4 ++- 8 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 bazel/coverage/BUILD.bazel create mode 100755 bazel/coverage/coverage.sh diff --git a/.bazelrc b/.bazelrc index dd3094b9..2484b419 100644 --- a/.bazelrc +++ b/.bazelrc @@ -50,3 +50,15 @@ build:maven --config=toolchain build:maven --stamp build:maven --define "maven_repo=https://oss.sonatype.org/service/local/staging/deploy/maven2" build:maven --java_runtime_version=local_jdk_8 + +# Generic coverage configuration taken from https://github.com/fmeum/rules_jni +coverage --combined_report=lcov +coverage --experimental_use_llvm_covmap +coverage --experimental_generate_llvm_lcov +coverage --repo_env=CC=clang +coverage --repo_env=BAZEL_USE_LLVM_NATIVE_COVERAGE=1 +coverage --repo_env=GCOV=llvm-profdata + +# Instrument all source files of non-test targets matching at least one of these regexes. +coverage --instrumentation_filter=^//agent/src/main[:/],^//driver:,^//sanitizers/src/main[:/] +coverage --test_tag_filters=-no-coverage diff --git a/.gitignore b/.gitignore index e992903a..b0ac4aff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /bazel-* .ijwb .clwb +/coverage diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index 1f55a2e8..f900cd9f 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -1,6 +1,6 @@ workspace(name = "jazzer") -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_jar") +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file", "http_jar") load("//:repositories.bzl", "jazzer_dependencies") jazzer_dependencies() @@ -116,3 +116,11 @@ maven_install( load("@maven//:defs.bzl", "pinned_maven_install") pinned_maven_install() + +http_file( + name = "genhtml", + downloaded_file_path = "genhtml", + executable = True, + sha256 = "4260f8d032743968d208afccfdb77fb6fda85b0ea4063a1f0b6a0c5602d22347", + urls = ["https://raw.githubusercontent.com/linux-test-project/lcov/master/bin/genhtml"], +) diff --git a/agent/BUILD.bazel b/agent/BUILD.bazel index 7e10b5a3..1737adcd 100644 --- a/agent/BUILD.bazel +++ b/agent/BUILD.bazel @@ -43,6 +43,11 @@ sh_test( ":jazzer_agent_deploy", "@local_jdk//:bin/jar", ], + tags = [ + # Coverage instrumentation necessarily adds files to the jar that we + # wouldn't want to release and thus causes this test to fail. + "no-coverage", + ], target_compatible_with = SKIP_ON_WINDOWS, ) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt b/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt index 763f230d..44249c81 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt @@ -14,12 +14,20 @@ package com.code_intelligence.jazzer.utils -import java.lang.IllegalArgumentException - private val BASE_INCLUDED_CLASS_NAME_GLOBS = listOf( "**", // everything ) +// We use both a strong indicator for running as a Bazel test together with an indicator for a +// Bazel coverage run to rule out false positives. +private val IS_BAZEL_COVERAGE_RUN = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR") != null && + System.getenv("COVERAGE_DIR") != null + +private val ADDITIONAL_EXCLUDED_NAME_GLOBS_FOR_BAZEL_COVERAGE = listOf( + "com.google.testing.coverage.**", + "org.jacoco.**", +) + private val BASE_EXCLUDED_CLASS_NAME_GLOBS = listOf( // JDK internals "\\[**", // array types @@ -36,7 +44,7 @@ private val BASE_EXCLUDED_CLASS_NAME_GLOBS = listOf( "com.code_intelligence.jazzer.**", "jaz.Ter", // safe companion of the honeypot class used by sanitizers "jaz.Zer", // honeypot class used by sanitizers -) +) + if (IS_BAZEL_COVERAGE_RUN) ADDITIONAL_EXCLUDED_NAME_GLOBS_FOR_BAZEL_COVERAGE else listOf() class ClassNameGlobber(includes: List, excludes: List) { // If no include globs are provided, start with all classes. diff --git a/bazel/coverage/BUILD.bazel b/bazel/coverage/BUILD.bazel new file mode 100644 index 00000000..b3dc9861 --- /dev/null +++ b/bazel/coverage/BUILD.bazel @@ -0,0 +1,10 @@ +# Run this target to generate and open an HTML coverage report. +# Takes the same arguments as `bazel coverage`, but after a double dash (`--`). +# The default is to run `bazel coverage //...`, which accumulates the coverage of all tests. +sh_binary( + name = "coverage", + srcs = ["coverage.sh"], + data = [ + "@genhtml//file:genhtml", + ], +) diff --git a/bazel/coverage/coverage.sh b/bazel/coverage/coverage.sh new file mode 100755 index 00000000..626fdc70 --- /dev/null +++ b/bazel/coverage/coverage.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env sh +# Copyright 2022 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. + + +# Use just like `bazel test` to generate and open an HTML coverage report. +# Requires a local installation of Perl. + +RUNFILES_ROOT=$PWD +cd "$BUILD_WORKSPACE_DIRECTORY" || exit 1 +if ! bazel coverage "${@:-//...}"; +then + exit $? +fi +"$RUNFILES_ROOT"/../genhtml/file/genhtml -o coverage \ + --prefix "$PWD" \ + --title "bazel coverage ${*:-//...}" \ + bazel-out/_coverage/_coverage_report.dat +xdg-open coverage/index.html > /dev/null 2>&1 diff --git a/driver/libfuzzer_fuzz_target.cpp b/driver/libfuzzer_fuzz_target.cpp index 9c2c6ecb..1f3a5c31 100644 --- a/driver/libfuzzer_fuzz_target.cpp +++ b/driver/libfuzzer_fuzz_target.cpp @@ -112,7 +112,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, const size_t size) { // Exit directly without invoking libFuzzer's atexit hook. driver_cleanup(); // When running with LLVM coverage instrumentation, write out the profile as - // the exit hook that write it won't run. + // the exit hook that writes it won't run. + // TODO: Remove once https://github.com/bazelbuild/bazel/pull/15166 has been + // fixed and use continuous mode instead. __llvm_profile_write_file(); _Exit(Driver::kErrorExitCode); } -- cgit v1.2.3 From a363ec3785790501ea0676863b4500cfcdb06f29 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 3 Jun 2022 13:16:56 +0200 Subject: Pin a particular commit of genhtml --- WORKSPACE.bazel | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index f900cd9f..1aeb41ee 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -121,6 +121,6 @@ http_file( name = "genhtml", downloaded_file_path = "genhtml", executable = True, - sha256 = "4260f8d032743968d208afccfdb77fb6fda85b0ea4063a1f0b6a0c5602d22347", - urls = ["https://raw.githubusercontent.com/linux-test-project/lcov/master/bin/genhtml"], + sha256 = "4120cc9186a0687db218520a2d0dc9bae75d15faf41d87448b6b6c5140c19156", + urls = ["https://raw.githubusercontent.com/linux-test-project/lcov/6da8399c7a7a3370de2c69b16b092e945442ffcd/bin/genhtml"], ) -- cgit v1.2.3 From 6e61ef2e76213097b36b14507a905e5dcd5a78bd Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 3 Jun 2022 13:01:27 +0200 Subject: Remove redundant -ldl linkopt We no longer use dlsym in the driver and thus don't have to link with -ldl. --- driver/BUILD.bazel | 4 ---- 1 file changed, 4 deletions(-) diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index 79358612..744d09e1 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -125,10 +125,6 @@ cc_library( "jvm_tooling.h", "libfuzzer_driver.h", ], - linkopts = select({ - "@platforms//os:windows": [], - "//conditions:default": ["-ldl"], - }), # Needs to be linked statically for JNI_OnLoad_jazzer_initialize to be found # by the JVM. linkstatic = True, -- cgit v1.2.3 From 9187660732a99ffc982795bd121637a0339a0b23 Mon Sep 17 00:00:00 2001 From: vargen Date: Fri, 17 Jun 2022 07:45:51 +0200 Subject: Update README with new OSS-Fuzz projects --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2fd7542b..20ee3111 100644 --- a/README.md +++ b/README.md @@ -366,7 +366,7 @@ Jazzer has so far uncovered the following vulnerabilities and bugs: | [google/re2j](https://github.com/google/re2j) | `NullPointerException` in `Pattern.compile` | [reported](https://github.com/google/re2j/issues/148) | | [@schirrmacher](https://github.com/schirrmacher) | | [google/gson](https://github.com/google/gson) | `ArrayIndexOutOfBounds` in `ParseString` | [fixed](https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=40838) | | [@DavidKorczynski](https://twitter.com/Davkorcz) | -As Jazzer is used to fuzz JVM projects in OSS-Fuzz, an additional list of bugs can be found [on the OSS-Fuzz issue tracker](https://bugs.chromium.org/p/oss-fuzz/issues/list?q=proj%3A%22json-sanitizer%22%20OR%20proj%3A%22fastjson2%22%20OR%20proj%3A%22jackson-core%22%20OR%20proj%3A%22jackson-dataformats-binary%22%20OR%20proj%3A%22jackson-dataformats-xml%22%20OR%20proj%3A%22apache-commons%22%20OR%20proj%3A%22jsoup%22&can=1). +As Jazzer is used to fuzz JVM projects in OSS-Fuzz, an additional list of bugs can be found [on the OSS-Fuzz issue tracker](https://bugs.chromium.org/p/oss-fuzz/issues/list?q=proj%3A%22json-sanitizer%22%20OR%20proj%3A%22fastjson2%22%20OR%20proj%3A%22jackson-core%22%20OR%20proj%3A%22jackson-dataformats-binary%22%20OR%20proj%3A%22jackson-dataformats-xml%22%20OR%20proj%3A%22apache-commons%22%20OR%20proj%3A%22jsoup%22%20OR%20proj%3A%22apache-commons-codec%22%20OR%20proj%3A%22apache-commons-io%22%20OR%20proj%3A%22apache-commons-jxpath%22%20OR%20proj%3A%22apache-commons-lang%22%20OR%20proj%3A%22httpcomponents-client%22%20OR%20proj%3A%22httpcomponents-core%22%20OR%20proj%3A%22tomcat%22%20OR%20proj%3A%22archaius-core%22%20OR%20proj%3A%22bc-java%22%20OR%20proj%3A%22gson%22%20OR%20proj%3A%22guava%22%20OR%20proj%3A%22guice%22%20OR%20proj%3A%22hdrhistogram%22%20OR%20proj%3A%22jackson-databind%22%20OR%20proj%3A%22javassist%22%20OR%20proj%3A%22jersey%22%20OR%20proj%3A%22jettison%22%20OR%20proj%3A%22joda-time%22%20OR%20proj%3A%22jul-to-slf4j%22%20OR%20proj%3A%22logback%22%20OR%20proj%3A%22servo-core%22%20OR%20proj%3A%22slf4j-api%22%20OR%20proj%3A%22snakeyaml%22%20OR%20proj%3A%22spring-boot-actuator%22%20OR%20proj%3A%22spring-boot%22%20OR%20proj%3A%22spring-framework%22%20OR%20proj%3A%22spring-security%22%20OR%20proj%3A%22stringtemplate4%22%20OR%20proj%3A%22woodstox%22%20OR%20proj%3A%22xmlpulll%22%20OR%20proj%3A%22xstream%22&can=1). If you find bugs with Jazzer, we would like to hear from you! Feel free to [open an issue](https://github.com/CodeIntelligenceTesting/jazzer/issues/new) or submit a pull request. -- cgit v1.2.3 From 3e8dc4d5dac4a0cd0fee891d05d9e3a656cfccdf Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 23 Jun 2022 21:03:36 +0200 Subject: Fix infinite recursion in ProbeInserter patch Speculative fix for the following externally reported issue: ``` INFO: Instrumented com.... java.lang.StackOverflowError at org.jacoco.core.internal.instr.ProbeInserter.visitLocalVariableAnnotation(ProbeInserter.java:126) at org.jacoco.core.internal.instr.ProbeInserter.visitLocalVariableAnnotation(ProbeInserter.java:126) ... ... ... at org.jacoco.core.internal.instr.ProbeInserter.visitLocalVariableAnnotation(ProbeInserter.java:126) INFO: Instrumented com.... ``` --- third_party/jacoco-make-probe-inserter-subclassable.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/jacoco-make-probe-inserter-subclassable.patch b/third_party/jacoco-make-probe-inserter-subclassable.patch index d867da7e..03cfe5e6 100644 --- a/third_party/jacoco-make-probe-inserter-subclassable.patch +++ b/third_party/jacoco-make-probe-inserter-subclassable.patch @@ -110,7 +110,7 @@ index 0f5b99ff..80965dfe 100644 final TypePath typePath, final Label[] start, final Label[] end, final int[] index, final String descriptor, final boolean visible) { + if (getLocalVariableType() == null) { -+ return visitLocalVariableAnnotation(typeRef, typePath, start, end, index, descriptor, visible); ++ return mv.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, descriptor, visible); + } + final int[] newIndex = new int[index.length]; -- cgit v1.2.3 From 762367781a8177939eb26ad8951f78755d6a0bcd Mon Sep 17 00:00:00 2001 From: Simon Resch Date: Fri, 17 Jun 2022 12:11:29 +0200 Subject: Split potentially long manifest line --- agent/BUILD.bazel | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agent/BUILD.bazel b/agent/BUILD.bazel index 1737adcd..e2cd5e3b 100644 --- a/agent/BUILD.bazel +++ b/agent/BUILD.bazel @@ -9,8 +9,8 @@ java_binary( deploy_manifest_lines = [ "Premain-Class: com.code_intelligence.jazzer.agent.Agent", "Can-Retransform-Classes: true", - "Jazzer-Hook-Classes: {}".format(":".join(SANITIZER_CLASSES)), - ], + "Jazzer-Hook-Classes: ", + ] + [" {}:".format(c) for c in SANITIZER_CLASSES], runtime_deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/agent:agent_lib", "//sanitizers", -- cgit v1.2.3 From 3819edaa7530da0fbca690d1c2031710829720a7 Mon Sep 17 00:00:00 2001 From: Simon Resch Date: Fri, 17 Jun 2022 12:14:23 +0200 Subject: Remove unused variable --- bazel/fuzz_target.bzl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bazel/fuzz_target.bzl b/bazel/fuzz_target.bzl index 8760d4eb..c70543bd 100644 --- a/bazel/fuzz_target.bzl +++ b/bazel/fuzz_target.bzl @@ -53,8 +53,6 @@ def java_fuzz_target_test( **kwargs ) - additional_args = [] - if sanitizer == None: driver = "//driver:jazzer_driver" elif sanitizer == "address": @@ -90,7 +88,7 @@ def java_fuzz_target_test( # args are shell tokenized and thus quotes are required in the case where # expected_findings is empty. "'" + ",".join(expected_findings) + "'", - ] + additional_args + fuzzer_args, + ] + fuzzer_args, data = [ ":%s_deploy.jar" % target_name, "//agent:jazzer_agent_deploy", -- cgit v1.2.3 From 10cf2aa7101a8c6439804601ff43f1fa1fbf4044 Mon Sep 17 00:00:00 2001 From: Simon Resch Date: Fri, 17 Jun 2022 12:23:20 +0200 Subject: Add hook to detect SQL injections --- agent/agent_shade_rules | 1 + maven.bzl | 2 + maven_install.json | 26 +++- sanitizers/sanitizers.bzl | 1 + .../jazzer/sanitizers/BUILD.bazel | 2 + .../jazzer/sanitizers/SqlInjection.kt | 169 +++++++++++++++++++++ sanitizers/src/test/java/com/example/BUILD.bazel | 15 ++ .../src/test/java/com/example/SqlInjection.java | 41 +++++ 8 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/SqlInjection.kt create mode 100644 sanitizers/src/test/java/com/example/SqlInjection.java diff --git a/agent/agent_shade_rules b/agent/agent_shade_rules index 0677cb1d..3f0aff07 100644 --- a/agent/agent_shade_rules +++ b/agent/agent_shade_rules @@ -2,5 +2,6 @@ rule com.github.** com.code_intelligence.jazzer.third_party.@0 rule io.** com.code_intelligence.jazzer.third_party.@0 rule kotlin.** com.code_intelligence.jazzer.third_party.@0 rule net.jodah.** com.code_intelligence.jazzer.third_party.@0 +rule net.sf.** com.code_intelligence.jazzer.third_party.@0 rule nonapi.** com.code_intelligence.jazzer.third_party.@0 rule org.objectweb.** com.code_intelligence.jazzer.third_party.@0 diff --git a/maven.bzl b/maven.bzl index 284706d2..a13f1759 100644 --- a/maven.bzl +++ b/maven.bzl @@ -25,6 +25,7 @@ MAVEN_ARTIFACTS = [ "com.fasterxml.jackson.core:jackson-core:2.12.1", "com.fasterxml.jackson.core:jackson-databind:2.12.1", "com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.12.1", + "com.github.jsqlparser:jsqlparser:4.4", # for SQL validation "com.google.code.gson:gson:2.8.6", "com.mikesamuel:json-sanitizer:1.2.1", "com.unboundid:unboundid-ldapsdk:6.0.3", @@ -39,4 +40,5 @@ MAVEN_ARTIFACTS = [ "org.openjdk.jmh:jmh-generator-annprocess:1.34", maven.artifact("org.apache.logging.log4j", "log4j-api", "2.14.1", testonly = True), maven.artifact("org.apache.logging.log4j", "log4j-core", "2.14.1", testonly = True), + maven.artifact("com.h2database", "h2", "2.1.212", testonly = True), ] diff --git a/maven_install.json b/maven_install.json index ef0243ae..cda4c940 100644 --- a/maven_install.json +++ b/maven_install.json @@ -1,8 +1,8 @@ { "dependency_tree": { "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL", - "__INPUT_ARTIFACTS_HASH": -1602439973, - "__RESOLVED_ARTIFACTS_HASH": -214698824, + "__INPUT_ARTIFACTS_HASH": -921920920, + "__RESOLVED_ARTIFACTS_HASH": 43450148, "conflict_resolution": {}, "dependencies": [ { @@ -103,6 +103,17 @@ "sha256": "610d23db8ece7268e93930562d89b91546c79fc80f3966baf433e5e93110b118", "url": "https://repo1.maven.org/maven2/com/fasterxml/classmate/1.1.0/classmate-1.1.0.jar" }, + { + "coord": "com.github.jsqlparser:jsqlparser:4.4", + "dependencies": [], + "directDependencies": [], + "file": "v1/https/repo1.maven.org/maven2/com/github/jsqlparser/jsqlparser/4.4/jsqlparser-4.4.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/github/jsqlparser/jsqlparser/4.4/jsqlparser-4.4.jar" + ], + "sha256": "101e22917b22a339787fc85447ea057ea57b572e2a777a4628b6562354da117d", + "url": "https://repo1.maven.org/maven2/com/github/jsqlparser/jsqlparser/4.4/jsqlparser-4.4.jar" + }, { "coord": "com.google.code.gson:gson:2.8.6", "dependencies": [], @@ -114,6 +125,17 @@ "sha256": "c8fb4839054d280b3033f800d1f5a97de2f028eb8ba2eb458ad287e536f3f25f", "url": "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar" }, + { + "coord": "com.h2database:h2:2.1.212", + "dependencies": [], + "directDependencies": [], + "file": "v1/https/repo1.maven.org/maven2/com/h2database/h2/2.1.212/h2-2.1.212.jar", + "mirror_urls": [ + "https://repo1.maven.org/maven2/com/h2database/h2/2.1.212/h2-2.1.212.jar" + ], + "sha256": "db9284c6ff9bf3bc0087851edbd34563f1180df3ae87c67c5fe2203c0e67a536", + "url": "https://repo1.maven.org/maven2/com/h2database/h2/2.1.212/h2-2.1.212.jar" + }, { "coord": "com.mikesamuel:json-sanitizer:1.2.1", "dependencies": [], diff --git a/sanitizers/sanitizers.bzl b/sanitizers/sanitizers.bzl index 17d6b272..cef4cf47 100644 --- a/sanitizers/sanitizers.bzl +++ b/sanitizers/sanitizers.bzl @@ -23,6 +23,7 @@ _sanitizer_class_names = [ "ReflectiveCall", "RegexInjection", "RegexRoadblocks", + "SqlInjection", ] SANITIZER_CLASSES = [_sanitizer_package_prefix + class_name for class_name in _sanitizer_class_names] diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel index f066b35f..1b156f9e 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel @@ -20,6 +20,7 @@ kt_jvm_library( "OsCommandInjection.kt", "ReflectiveCall.kt", "RegexInjection.kt", + "SqlInjection.kt", "Utils.kt", ], visibility = ["//sanitizers:__pkg__"], @@ -28,5 +29,6 @@ kt_jvm_library( ], deps = [ "//agent:jazzer_api_compile_only", + "@maven//:com_github_jsqlparser_jsqlparser", ], ) diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/SqlInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/SqlInjection.kt new file mode 100644 index 00000000..64b65b31 --- /dev/null +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/SqlInjection.kt @@ -0,0 +1,169 @@ +// Copyright 2022 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.sanitizers + +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh +import com.code_intelligence.jazzer.api.HookType +import com.code_intelligence.jazzer.api.Jazzer +import com.code_intelligence.jazzer.api.MethodHook +import com.code_intelligence.jazzer.api.MethodHooks +import net.sf.jsqlparser.JSQLParserException +import net.sf.jsqlparser.parser.CCJSqlParserUtil +import java.lang.invoke.MethodHandle + +/** + * Detects SQL injections. + * + * Untrusted input has to be escaped in such a way that queries remain valid otherwise an injection + * could be possible. This sanitizer guides the fuzzer to inject insecure characters. If an exception + * is raised during execution the fuzzer was able to inject an invalid pattern, otherwise all input + * was escaped correctly. + * + * Two types of methods are hooked: + * 1. Methods that take an SQL query as the first argument (e.g. [java.sql.Statement.execute]). + * 2. Methods that don't take any arguments and execute an already prepared statement + * (e.g. [java.sql.PreparedStatement.execute]). + * For 1. we validate the syntax of the query using jsqlparser + * and if both the syntax is invalid and the query execution throws an exception we report an SQL injection. + * Since we can't reliably validate SQL queries in arbitrary dialects this hook is expected to produce some + * amount of false positives. + * For 2. we can't validate the query syntax and therefore only rethrow any exceptions. + */ +@Suppress("unused_parameter", "unused") +object SqlInjection { + + // Characters that should be escaped in user input. + // See https://dev.mysql.com/doc/refman/8.0/en/string-literals.html + private const val CHARACTERS_TO_ESCAPE = "'\"\b\n\r\t\\%_" + + private val SQL_SYNTAX_ERROR_EXCEPTIONS = listOf( + "java.sql.SQLException", + "java.sql.SQLNonTransientException", + "java.sql.SQLSyntaxErrorException", + "org.h2.jdbc.JdbcSQLSyntaxErrorException", + ) + + @MethodHooks( + MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.Statement", targetMethod = "execute"), + MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.Statement", targetMethod = "executeBatch"), + MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.Statement", targetMethod = "executeLargeBatch"), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.sql.Statement", + targetMethod = "executeLargeUpdate" + ), + MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.Statement", targetMethod = "executeQuery"), + MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.Statement", targetMethod = "executeUpdate"), + MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.PreparedStatement", targetMethod = "execute"), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.sql.PreparedStatement", + targetMethod = "executeBatch" + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.sql.PreparedStatement", + targetMethod = "executeLargeBatch" + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.sql.PreparedStatement", + targetMethod = "executeLargeUpdate" + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.sql.PreparedStatement", + targetMethod = "executeQuery" + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.sql.PreparedStatement", + targetMethod = "executeUpdate" + ), + MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.CallableStatement", targetMethod = "execute"), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.sql.CallableStatement", + targetMethod = "executeBatch" + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.sql.CallableStatement", + targetMethod = "executeLargeBatch" + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.sql.CallableStatement", + targetMethod = "executeLargeUpdate" + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.sql.CallableStatement", + targetMethod = "executeQuery" + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "java.sql.CallableStatement", + targetMethod = "executeUpdate" + ), + MethodHook( + type = HookType.REPLACE, + targetClassName = "javax.persistence.EntityManager", + targetMethod = "createNativeQuery" + ) + ) + @JvmStatic + fun checkSqlExecute(method: MethodHandle, thisObject: Any?, arguments: Array, hookId: Int): Any { + var hasValidSqlQuery = false + + if (arguments.isNotEmpty() && arguments[0] is String) { + val query = arguments[0] as String + hasValidSqlQuery = isValidSql(query) + Jazzer.guideTowardsContainment(query, CHARACTERS_TO_ESCAPE, hookId) + } + return try { + method.invokeWithArguments(thisObject, *arguments) + } catch (throwable: Throwable) { + // If we already validated the query string and know it's correct, + // The exception is likely thrown by a non-existent table or something + // that we don't want to report. + if (!hasValidSqlQuery && SQL_SYNTAX_ERROR_EXCEPTIONS.contains(throwable.javaClass.name)) { + Jazzer.reportFindingFromHook( + FuzzerSecurityIssueHigh( + """ + SQL Injection + Injected query: ${arguments[0]} + """.trimIndent(), + throwable + ) + ) + } + throw throwable + } + } + + private fun isValidSql(sql: String): Boolean = + try { + CCJSqlParserUtil.parseStatements(sql) + true + } catch (e: JSQLParserException) { + false + } catch (t: Throwable) { + // Catch any unexpected exceptions so that we don't disturb the + // instrumented application. + t.printStackTrace() + true + } +} diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index 8498ab32..5d2e1ca5 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -123,3 +123,18 @@ java_fuzz_target_test( ], target_class = "com.example.RegexRoadblocks", ) + +java_fuzz_target_test( + name = "SqlInjection", + srcs = [ + "SqlInjection.java", + ], + expected_findings = [ + "com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh", + "org.h2.jdbc.JdbcSQLSyntaxErrorException", + ], + target_class = "com.example.SqlInjection", + deps = [ + "@maven//:com_h2database_h2", + ], +) diff --git a/sanitizers/src/test/java/com/example/SqlInjection.java b/sanitizers/src/test/java/com/example/SqlInjection.java new file mode 100644 index 00000000..8a16b5c8 --- /dev/null +++ b/sanitizers/src/test/java/com/example/SqlInjection.java @@ -0,0 +1,41 @@ +// Copyright 2022 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.example; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import java.sql.Connection; +import java.sql.SQLException; +import org.h2.jdbcx.JdbcDataSource; + +public class SqlInjection { + static Connection conn = null; + + public static void fuzzerInitialize() throws Exception { + JdbcDataSource ds = new JdbcDataSource(); + ds.setURL("jdbc:h2:./test.db"); + conn = ds.getConnection(); + conn.createStatement().execute( + "CREATE TABLE IF NOT EXISTS pet (id IDENTITY PRIMARY KEY, name VARCHAR(50))"); + } + + static void insecureInsertUser(String userName) throws SQLException { + // Never use String.format instead of java.sql.Connection.prepareStatement ... + conn.createStatement().execute(String.format("INSERT INTO pet (name) VALUES ('%s')", userName)); + } + + public static void fuzzerTestOneInput(FuzzedDataProvider data) throws Exception { + insecureInsertUser(data.consumeRemainingAsString()); + } +} -- cgit v1.2.3 From fb10df82c413246ee113abbc1991381849a5fcf4 Mon Sep 17 00:00:00 2001 From: Norbert Schneider Date: Fri, 8 Jul 2022 08:26:10 +0200 Subject: Remove duplicated SQL statement hooks PreparedStatement and CallableStatement implement Statement and don't need explicit hooks. --- .../jazzer/sanitizers/SqlInjection.kt | 58 +--------------------- 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/SqlInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/SqlInjection.kt index 64b65b31..f317bcc8 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/SqlInjection.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/SqlInjection.kt @@ -59,65 +59,9 @@ object SqlInjection { MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.Statement", targetMethod = "execute"), MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.Statement", targetMethod = "executeBatch"), MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.Statement", targetMethod = "executeLargeBatch"), - MethodHook( - type = HookType.REPLACE, - targetClassName = "java.sql.Statement", - targetMethod = "executeLargeUpdate" - ), + MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.Statement", targetMethod = "executeLargeUpdate"), MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.Statement", targetMethod = "executeQuery"), MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.Statement", targetMethod = "executeUpdate"), - MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.PreparedStatement", targetMethod = "execute"), - MethodHook( - type = HookType.REPLACE, - targetClassName = "java.sql.PreparedStatement", - targetMethod = "executeBatch" - ), - MethodHook( - type = HookType.REPLACE, - targetClassName = "java.sql.PreparedStatement", - targetMethod = "executeLargeBatch" - ), - MethodHook( - type = HookType.REPLACE, - targetClassName = "java.sql.PreparedStatement", - targetMethod = "executeLargeUpdate" - ), - MethodHook( - type = HookType.REPLACE, - targetClassName = "java.sql.PreparedStatement", - targetMethod = "executeQuery" - ), - MethodHook( - type = HookType.REPLACE, - targetClassName = "java.sql.PreparedStatement", - targetMethod = "executeUpdate" - ), - MethodHook(type = HookType.REPLACE, targetClassName = "java.sql.CallableStatement", targetMethod = "execute"), - MethodHook( - type = HookType.REPLACE, - targetClassName = "java.sql.CallableStatement", - targetMethod = "executeBatch" - ), - MethodHook( - type = HookType.REPLACE, - targetClassName = "java.sql.CallableStatement", - targetMethod = "executeLargeBatch" - ), - MethodHook( - type = HookType.REPLACE, - targetClassName = "java.sql.CallableStatement", - targetMethod = "executeLargeUpdate" - ), - MethodHook( - type = HookType.REPLACE, - targetClassName = "java.sql.CallableStatement", - targetMethod = "executeQuery" - ), - MethodHook( - type = HookType.REPLACE, - targetClassName = "java.sql.CallableStatement", - targetMethod = "executeUpdate" - ), MethodHook( type = HookType.REPLACE, targetClassName = "javax.persistence.EntityManager", -- cgit v1.2.3 From 85775c48c697f2d63c4bb261a06e829b123e5e6c Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sun, 6 Feb 2022 15:29:02 +0100 Subject: Fix TurbojpegFuzzer native library path The native library path for this example got lost during the migration to rules_jni and is required for the fuzz target to be functional. Also updates rules_foreign_cc and cleans up the attributes passed to the cmake rule. This includes adding -fPIC as a speculative fix for relocation issues reported by the linker. --- examples/BUILD.bazel | 1 + third_party/libjpeg_turbo.BUILD | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index a9cd04e4..6163fc58 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -325,6 +325,7 @@ java_fuzz_target_test( ], fuzzer_args = [ "-rss_limit_mb=8196", + "--jvm_args=-Djava.library.path=../libjpeg_turbo", ], sanitizer = "address", tags = ["manual"], diff --git a/third_party/libjpeg_turbo.BUILD b/third_party/libjpeg_turbo.BUILD index 4621f862..e140bc07 100644 --- a/third_party/libjpeg_turbo.BUILD +++ b/third_party/libjpeg_turbo.BUILD @@ -23,13 +23,16 @@ cc_import( cmake( name = "libjpeg_turbo", cache_entries = { - "CMAKE_BUILD_TYPE": "Release", - "CMAKE_C_COMPILER": "clang", - "CMAKE_C_FLAGS": "-fsanitize=address,fuzzer-no-link", - "CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address,fuzzer-no-link", "WITH_JAVA": "1", }, + copts = [ + "-fsanitize=address,fuzzer-no-link", + "-fPIC", + ], lib_source = ":all_files", + linkopts = [ + "-fsanitize=address,fuzzer-no-link", + ], out_shared_libs = [ "libjpeg.so", "libturbojpeg.so", -- cgit v1.2.3 From d4b389f02f1f70c915e5ff5fcb95a214eaf53097 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 25 Jul 2022 14:04:55 +0200 Subject: tests: Suppress new ErrorProne check --- .../jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java | 1 + 1 file changed, 1 insertion(+) diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java index 48f16e60..d8e28881 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java @@ -37,6 +37,7 @@ public class TraceDataFlowInstrumentationTarget implements DynamicTestContract { volatile int switchValue = 1200; + @SuppressWarnings("ReturnValueIgnored") @Override public Map selfCheck() { Map results = new HashMap<>(); -- cgit v1.2.3 From e0b980e5e16be4bdd54b898cf6c50ca9feec8020 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 25 Jul 2022 14:09:32 +0200 Subject: deps: Update everything to latest --- .bazelversion | 2 +- WORKSPACE.bazel | 18 +++++++++--------- repositories.bzl | 40 ++++++++++++++++++++-------------------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.bazelversion b/.bazelversion index 64ba3a1a..91ff5727 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -5.2.0rc1 +5.2.0 diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index 1aeb41ee..5589d57c 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -37,23 +37,23 @@ http_archive( http_archive( name = "googletest", - sha256 = "9dc9157a9a1551ec7a7e43daea9a694a0bb5fb8bec81235d8a1e6ef64c716dcb", - strip_prefix = "googletest-release-1.10.0", - url = "https://github.com/google/googletest/archive/refs/tags/release-1.10.0.tar.gz", + sha256 = "81964fe578e9bd7c94dfdb09c8e4d6e6759e19967e397dbea48d1c10e45d0df2", + strip_prefix = "googletest-release-1.12.1", + url = "https://github.com/google/googletest/archive/refs/tags/release-1.12.1.tar.gz", ) http_archive( name = "rules_foreign_cc", - sha256 = "bcd0c5f46a49b85b384906daae41d277b3dc0ff27c7c752cc51e43048a58ec83", - strip_prefix = "rules_foreign_cc-0.7.1", - url = "https://github.com/bazelbuild/rules_foreign_cc/archive/refs/tags/0.7.1.tar.gz", + sha256 = "6041f1374ff32ba711564374ad8e007aef77f71561a7ce784123b9b4b88614fc", + strip_prefix = "rules_foreign_cc-0.8.0", + url = "https://github.com/bazelbuild/rules_foreign_cc/archive/refs/tags/0.8.0.tar.gz", ) http_archive( name = "rules_jvm_external", - sha256 = "f36441aa876c4f6427bfb2d1f2d723b48e9d930b62662bf723ddfb8fc80f0140", - strip_prefix = "rules_jvm_external-4.1", - url = "https://github.com/bazelbuild/rules_jvm_external/archive/refs/tags/4.1.zip", + sha256 = "cd1a77b7b02e8e008439ca76fd34f5b07aecb8c752961f9640dea15e9e5ba1ca", + strip_prefix = "rules_jvm_external-4.2", + url = "https://github.com/bazelbuild/rules_jvm_external/archive/refs/tags/4.2.zip", ) http_archive( diff --git a/repositories.bzl b/repositories.bzl index bd9739b6..51114480 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -31,35 +31,35 @@ def jazzer_dependencies(): maybe( http_archive, name = "bazel_skylib", - sha256 = "c6966ec828da198c5d9adbaa94c05e3a1c7f21bd012a0b29ba8ddbccb2c93b0d", + sha256 = "f7be3474d42aae265405a592bb7da8e171919d74c16f082a5457840f06054728", urls = [ - "https://github.com/bazelbuild/bazel-skylib/releases/download/1.1.1/bazel-skylib-1.1.1.tar.gz", - "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.1.1/bazel-skylib-1.1.1.tar.gz", + "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.2.1/bazel-skylib-1.2.1.tar.gz", + "https://github.com/bazelbuild/bazel-skylib/releases/download/1.2.1/bazel-skylib-1.2.1.tar.gz", ], ) maybe( http_archive, name = "io_bazel_rules_kotlin", - sha256 = "12d22a3d9cbcf00f2e2d8f0683ba87d3823cb8c7f6837568dd7e48846e023307", - url = "https://github.com/bazelbuild/rules_kotlin/releases/download/v1.5.0/rules_kotlin_release.tgz", + sha256 = "a57591404423a52bd6b18ebba7979e8cd2243534736c5c94d35c89718ea38f94", + url = "https://github.com/bazelbuild/rules_kotlin/releases/download/v1.6.0/rules_kotlin_release.tgz", ) maybe( http_archive, name = "com_google_glog", repo_mapping = {"@com_github_gflags_gflags": "@jazzer_com_github_gflags_gflags"}, - sha256 = "eede71f28371bf39aa69b45de23b329d37214016e2055269b3b5e7cfd40b59f5", - strip_prefix = "glog-0.5.0", - url = "https://github.com/google/glog/archive/refs/tags/v0.5.0.tar.gz", + sha256 = "8a83bf982f37bb70825df71a9709fa90ea9f4447fb3c099e1d720a439d88bad6", + strip_prefix = "glog-0.6.0", + url = "https://github.com/google/glog/archive/refs/tags/v0.6.0.tar.gz", ) maybe( http_archive, name = "com_google_absl", - sha256 = "dcf71b9cba8dc0ca9940c4b316a0c796be8fab42b070bb6b7cab62b48f0e66c4", - strip_prefix = "abseil-cpp-20211102.0", - url = "https://github.com/abseil/abseil-cpp/archive/refs/tags/20211102.0.tar.gz", + sha256 = "4208129b49006089ba1d6710845a45e31c59b0ab6bff9e5788a87f55c5abd602", + strip_prefix = "abseil-cpp-20220623.0", + url = "https://github.com/abseil/abseil-cpp/archive/refs/tags/20220623.0.tar.gz", ) maybe( @@ -91,30 +91,30 @@ def jazzer_dependencies(): maybe( http_archive, name = "fmeum_rules_jni", - sha256 = "45acc80812e0ecafc3f1b2f17efa6141d014aa2cf49c94a83252f570124d161a", - strip_prefix = "rules_jni-0.5.1", - url = "https://github.com/fmeum/rules_jni/archive/refs/tags/v0.5.1.tar.gz", + sha256 = "47f0c566ef93fbca2fe94ae8b964d9bf2cb5b31be0efa66e9684b096e54042c1", + strip_prefix = "rules_jni-0.5.2", + url = "https://github.com/fmeum/rules_jni/archive/refs/tags/v0.5.2.tar.gz", ) maybe( http_jar, name = "org_ow2_asm_asm", - sha256 = "b9d4fe4d71938df38839f0eca42aaaa64cf8b313d678da036f0cb3ca199b47f5", - url = "https://repo1.maven.org/maven2/org/ow2/asm/asm/9.2/asm-9.2.jar", + sha256 = "1263369b59e29c943918de11d6d6152e2ec6085ce63e5710516f8c67d368e4bc", + url = "https://repo1.maven.org/maven2/org/ow2/asm/asm/9.3/asm-9.3.jar", ) maybe( http_jar, name = "org_ow2_asm_asm_commons", - sha256 = "be4ce53138a238bb522cd781cf91f3ba5ce2f6ca93ec62d46a162a127225e0a6", - url = "https://repo1.maven.org/maven2/org/ow2/asm/asm-commons/9.2/asm-commons-9.2.jar", + sha256 = "a347c24732db2aead106b6e5996a015b06a3ef86e790a4f75b61761f0d2f7f39", + url = "https://repo1.maven.org/maven2/org/ow2/asm/asm-commons/9.3/asm-commons-9.3.jar", ) maybe( http_jar, name = "org_ow2_asm_asm_tree", - sha256 = "aabf9bd23091a4ebfc109c1f3ee7cf3e4b89f6ba2d3f51c5243f16b3cffae011", - url = "https://repo1.maven.org/maven2/org/ow2/asm/asm-tree/9.2/asm-tree-9.2.jar", + sha256 = "ae629c2609f39681ef8d140a42a23800464a94f2d23e36d8f25cd10d5e4caff4", + url = "https://repo1.maven.org/maven2/org/ow2/asm/asm-tree/9.3/asm-tree-9.3.jar", ) maybe( -- cgit v1.2.3 From 3ad4b85b93dc5624006fd8d2bf9f0e44d59d53c9 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 1 Aug 2022 12:53:18 +0200 Subject: Add disabled_hooks CLI argument The new argument can be used to selectively disable both custom and and built-in hooks. Also mentions the OS-dependent separator in agent flag descriptions. --- .../com/code_intelligence/jazzer/agent/Agent.kt | 10 ++++- driver/jvm_tooling.cpp | 27 ++++++----- tests/BUILD.bazel | 15 +++++++ .../test/java/com/example/DisabledHooksFuzzer.java | 52 ++++++++++++++++++++++ 4 files changed, 93 insertions(+), 11 deletions(-) create mode 100644 tests/src/test/java/com/example/DisabledHooksFuzzer.java diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index e1d08d91..e7396828 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -42,6 +42,7 @@ private val KNOWN_ARGUMENTS = listOf( "custom_hook_excludes", "trace", "custom_hooks", + "disabled_hooks", "id_sync_file", "dump_classes_dir", ) @@ -103,7 +104,14 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { ManifestUtils.combineManifestValues(ManifestUtils.HOOK_CLASSES).flatMap { it.split(':') }.filter { it.isNotBlank() } - val customHookNames = manifestCustomHookNames + (argumentMap["custom_hooks"] ?: emptyList()) + val allCustomHookNames = manifestCustomHookNames + (argumentMap["custom_hooks"] ?: emptySet()) + val disabledCustomHookNames = argumentMap["disabled_hooks"]?.toSet() ?: emptySet() + val customHookNames = allCustomHookNames - disabledCustomHookNames + val disabledCustomHooksToPrint = allCustomHookNames - customHookNames.toSet() + if (disabledCustomHooksToPrint.isNotEmpty()) { + println("INFO: Not using the following disabled hooks: ${disabledCustomHooksToPrint.joinToString(", ")}") + } + val classNameGlobber = ClassNameGlobber( argumentMap["instrumentation_includes"] ?: emptyList(), (argumentMap["instrumentation_excludes"] ?: emptyList()) + customHookNames diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index 35bd8bad..96fe7f2b 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -51,20 +51,26 @@ DEFINE_string(agent_path, "", "location of the fuzzing instrumentation agent"); // combined during the initialization of the JVM. DEFINE_string(instrumentation_includes, "", "list of glob patterns for classes that will be instrumented for " - "fuzzing. Separated by colon \":\""); -DEFINE_string(instrumentation_excludes, "", - "list of glob patterns for classes that will not be instrumented " - "for fuzzing. Separated by colon \":\""); + "fuzzing (separator is ':' on Linux/macOS and ';' on Windows)"); +DEFINE_string( + instrumentation_excludes, "", + "list of glob patterns for classes that will not be instrumented " + "for fuzzing (separator is ':' on Linux/macOS and ';' on Windows)"); DEFINE_string(custom_hook_includes, "", "list of glob patterns for classes that will only be " - "instrumented using custom hooks. Separated by colon \":\""); -DEFINE_string(custom_hook_excludes, "", - "list of glob patterns for classes that will not be instrumented " - "using custom hooks. Separated by colon \":\""); + "instrumented using custom hooks (separator is ':' on " + "Linux/macOS and ';' on Windows)"); +DEFINE_string( + custom_hook_excludes, "", + "list of glob patterns for classes that will not be instrumented " + "using custom hooks (separator is ':' on Linux/macOS and ';' on Windows)"); DEFINE_string(custom_hooks, "", - "list of classes containing custom instrumentation hooks. " - "Separated by colon \":\""); + "list of classes containing custom instrumentation hooks " + "(separator is ':' on Linux/macOS and ';' on Windows)"); +DEFINE_string(disabled_hooks, "", + "list of hook classes (custom or built-in) that should not be " + "loaded (separator is ':' on Linux/macOS and ';' on Windows)"); DEFINE_string( trace, "", "list of instrumentation to perform separated by colon \":\". " @@ -191,6 +197,7 @@ std::string agentArgsFromFlags() { {"instrumentation_includes", FLAGS_instrumentation_includes}, {"instrumentation_excludes", FLAGS_instrumentation_excludes}, {"custom_hooks", FLAGS_custom_hooks}, + {"disabled_hooks", FLAGS_disabled_hooks}, {"custom_hook_includes", FLAGS_custom_hook_includes}, {"custom_hook_excludes", FLAGS_custom_hook_excludes}, {"trace", FLAGS_trace}, diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index fa5f8aea..0089dca8 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -162,3 +162,18 @@ JAZZER_API_TEST_CASES = { ) for case, args in JAZZER_API_TEST_CASES.items() ] + +java_fuzz_target_test( + name = "DisabledHooksFuzzer", + timeout = "short", + srcs = ["src/test/java/com/example/DisabledHooksFuzzer.java"], + expect_crash = False, + fuzzer_args = [ + "-runs=0", + "--custom_hooks=com.example.DisabledHook", + ] + select({ + "@platforms//os:windows": ["--disabled_hooks=com.example.DisabledHook;com.code_intelligence.jazzer.sanitizers.RegexInjection"], + "//conditions:default": ["--disabled_hooks=com.example.DisabledHook:com.code_intelligence.jazzer.sanitizers.RegexInjection"], + }), + target_class = "com.example.DisabledHooksFuzzer", +) diff --git a/tests/src/test/java/com/example/DisabledHooksFuzzer.java b/tests/src/test/java/com/example/DisabledHooksFuzzer.java new file mode 100644 index 00000000..430bfa40 --- /dev/null +++ b/tests/src/test/java/com/example/DisabledHooksFuzzer.java @@ -0,0 +1,52 @@ +/* + * Copyright 2022 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.example; + +import com.code_intelligence.jazzer.api.HookType; +import com.code_intelligence.jazzer.api.Jazzer; +import com.code_intelligence.jazzer.api.MethodHook; +import java.lang.invoke.MethodHandle; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +public class DisabledHooksFuzzer { + public static void fuzzerTestOneInput(byte[] data) { + triggerCustomHook(); + triggerBuiltinHook(); + } + + private static void triggerCustomHook() {} + + private static void triggerBuiltinHook() { + // Trigger the built-in regex injection detector if it is enabled, but catch the exception + // thrown if it isn't. + try { + Pattern.compile("["); + } catch (PatternSyntaxException ignored) { + } + } +} + +class DisabledHook { + @MethodHook(type = HookType.BEFORE, targetClassName = "com.example.DisabledHooksFuzzer", + targetMethod = "triggerCustomHook", targetMethodDescriptor = "()V") + public static void + triggerCustomHookHook(MethodHandle method, Object thisObject, Object[] arguments, int hookId) { + Jazzer.reportFindingFromHook( + new IllegalStateException("hook on triggerCustomHook should have been disabled")); + } +} -- cgit v1.2.3 From 785efb7238bc9f10778c96c2348e579d3d01525d Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 27 Jul 2022 12:44:14 +0200 Subject: driver: Remove unused FuzzTargetRunner constructor parameter --- driver/fuzz_target_runner.cpp | 6 +----- driver/fuzz_target_runner.h | 3 +-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index 4af0c1d0..8ca0ac3c 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -111,8 +111,7 @@ std::vector splitOnSpace(const std::string &s) { return tokens; } -FuzzTargetRunner::FuzzTargetRunner( - JVM &jvm, const std::vector &additional_target_args) +FuzzTargetRunner::FuzzTargetRunner(JVM &jvm) : ExceptionPrinter(jvm), jvm_(jvm), ignore_tokens_() { auto &env = jvm.GetEnv(); if (!FLAGS_target_class.empty() && !FLAGS_autofuzz.empty()) { @@ -220,9 +219,6 @@ FuzzTargetRunner::FuzzTargetRunner( jclass_, "fuzzerInitialize", "([Ljava/lang/String;)V", false); auto fuzz_target_args_tokens = splitOnSpace(FLAGS_target_args); - fuzz_target_args_tokens.insert(fuzz_target_args_tokens.end(), - additional_target_args.begin(), - additional_target_args.end()); if (fuzzer_initialize_with_args_) { // fuzzerInitialize with arguments gets priority diff --git a/driver/fuzz_target_runner.h b/driver/fuzz_target_runner.h index 98ac794c..0a0f1e11 100644 --- a/driver/fuzz_target_runner.h +++ b/driver/fuzz_target_runner.h @@ -60,8 +60,7 @@ class FuzzTargetRunner : public ExceptionPrinter { public: // Initializes the java fuzz target by calling `void fuzzerInitialize(...)`. - explicit FuzzTargetRunner( - JVM &jvm, const std::vector &additional_target_args = {}); + explicit FuzzTargetRunner(JVM &jvm); // Calls the fuzz target tear down function. This can be useful to join any // Threads so that the JVM shuts down correctly. -- cgit v1.2.3 From 3782163e81c8887ad1d9f77d0ddf0eade4d19aa5 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 2 Aug 2022 18:00:16 +0200 Subject: bazel: Make cc_17_library work with CLion aspect By calling the attribute of cc_17_library `exports` instead of `library`, the CLion aspect picks it up without additional effort. See https://github.com/fmeum/rules_meta/commit/8d57be3cba03d68abbebcf9b774d4167feae0e0e --- bazel/cc.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/cc.bzl b/bazel/cc.bzl index 1fab30cd..8cd1c8b2 100644 --- a/bazel/cc.bzl +++ b/bazel/cc.bzl @@ -29,7 +29,7 @@ _add_cxxopt_std_17 = transition( ) def _cc_17_library_impl(ctx): - library = ctx.attr.library[0] + library = ctx.attr.exports[0] return [ # Workaround for https://github.com/bazelbuild/bazel/issues/9442. DefaultInfo( @@ -48,7 +48,7 @@ _cc_17_library = rule( implementation = _cc_17_library_impl, attrs = { "is_windows": attr.bool(), - "library": attr.label( + "exports": attr.label( cfg = _add_cxxopt_std_17, mandatory = True, providers = [CcInfo], @@ -79,6 +79,6 @@ def cc_17_library(name, visibility = None, **kwargs): "@platforms//os:windows": True, "//conditions:default": False, }), - library = library_name, + exports = library_name, visibility = visibility, ) -- cgit v1.2.3 From db6dc563305c6ec2118bec2f7f7dabe690c547a3 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 3 Aug 2022 00:03:15 +0200 Subject: runtime: Fix signal handling with --nohooks --- .../main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java index ad4c291a..fede3a8a 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java @@ -18,6 +18,7 @@ import sun.misc.Signal; public final class SignalHandler { static { + System.loadLibrary("jazzer_initialize"); Signal.handle(new Signal("INT"), sig -> handleInterrupt()); } -- cgit v1.2.3 From 7e67350754bffa448a1cec0adf764b2ffebf33c0 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 2 Aug 2022 18:02:25 +0200 Subject: all: Simplify native initialization of FuzzedDataProviderImpl --- .../code_intelligence/jazzer/runtime/BUILD.bazel | 6 ++++++ .../jazzer/runtime/FuzzedDataProviderImpl.java | 11 +++++++++++ ...com_code_intelligence_jazzer_replay_Replayer.cpp | 4 +--- .../runtime/RecordingFuzzedDataProviderTest.java | 2 +- driver/BUILD.bazel | 10 ++++++++++ driver/fuzz_target_runner.cpp | 1 - driver/fuzzed_data_provider.cpp | 21 +++++---------------- driver/fuzzed_data_provider.h | 3 --- .../testdata/test/FuzzTargetWithDataProvider.java | 4 ++++ 9 files changed, 38 insertions(+), 24 deletions(-) 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 b5f2d317..c85aea96 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 @@ -15,6 +15,12 @@ java_library( ], ) +jni_headers( + name = "fuzzed_data_provider.hdrs", + lib = ":fuzzed_data_provider", + visibility = ["//driver:__pkg__"], +) + java_library( name = "coverage_map", srcs = ["CoverageMap.java"], 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 fe4d8ac7..2a6f68d1 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 @@ -19,6 +19,17 @@ import com.code_intelligence.jazzer.api.FuzzedDataProvider; public class FuzzedDataProviderImpl implements FuzzedDataProvider { public FuzzedDataProviderImpl() {} + private static native void nativeInit(); + + static { + try { + System.loadLibrary("jazzer_initialize"); + } catch (UnsatisfiedLinkError ignored) { + // Reached when loaded from the replayer. + } + nativeInit(); + } + @Override public native boolean consumeBoolean(); @Override public native boolean[] consumeBooleans(int maxLength); 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 index c4bdfcfb..e481e82f 100644 --- 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 @@ -24,9 +24,7 @@ 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 { + if (data != nullptr) { delete[] data; } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProviderTest.java b/agent/src/test/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProviderTest.java index 36b3ec61..d58a5ca9 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProviderTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProviderTest.java @@ -55,7 +55,7 @@ public class RecordingFuzzedDataProviderTest { return result.toString(); } - private static final class MockFuzzedDataProvider extends FuzzedDataProviderImpl { + private static final class MockFuzzedDataProvider implements FuzzedDataProvider { @Override public boolean consumeBoolean() { return true; diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index 744d09e1..b91662a1 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -35,6 +35,7 @@ cc_library( "//agent/src/main/native/com/code_intelligence/jazzer/replay:__pkg__", ], deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:fuzzed_data_provider.hdrs", "@com_google_absl//absl/strings:str_format", "@fmeum_rules_jni//jni", ], @@ -317,6 +318,15 @@ cc_test( "//driver/testdata:fuzz_target_mocks_deploy.jar", ], includes = ["."], + linkopts = select({ + "@platforms//os:windows": [], + "//conditions:default": [ + # Needs to export symbols dynamically for JNI_OnLoad_jazzer_initialize + # to be found by the JVM. + "-rdynamic", + ], + }), + linkstatic = True, deps = [ ":jvm_tooling_lib", ":sanitizer_symbols_for_tests", diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index 8ca0ac3c..e401551d 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -246,7 +246,6 @@ FuzzTargetRunner::FuzzTargetRunner(JVM &jvm) if (FLAGS_hooks) { CoverageTracker::RecordInitialCoverage(env); } - SetUpFuzzedDataProvider(jvm_.GetEnv()); // Parse a comma-separated list of hex dedup tokens. std::vector str_ignore_tokens = diff --git a/driver/fuzzed_data_provider.cpp b/driver/fuzzed_data_provider.cpp index e8cb971b..1daee741 100644 --- a/driver/fuzzed_data_provider.cpp +++ b/driver/fuzzed_data_provider.cpp @@ -52,6 +52,7 @@ #include #include "absl/strings/str_format.h" +#include "com_code_intelligence_jazzer_runtime_FuzzedDataProviderImpl.h" namespace { @@ -694,24 +695,12 @@ const jint kNumFuzzedDataMethods = sizeof(kFuzzedDataMethods) / sizeof(kFuzzedDataMethods[0]); } // namespace -namespace jazzer { - -void SetUpFuzzedDataProvider(JNIEnv &env) { - jclass fuzzed_data_provider_class = - env.FindClass(kFuzzedDataProviderImplClass); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - throw std::runtime_error("failed to find FuzzedDataProviderImpl class"); - } - env.RegisterNatives(fuzzed_data_provider_class, kFuzzedDataMethods, - kNumFuzzedDataMethods); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - throw std::runtime_error( - "could not register native callbacks for FuzzedDataProvider"); - } +void Java_com_code_1intelligence_jazzer_runtime_FuzzedDataProviderImpl_nativeInit( + JNIEnv *env, jclass clazz) { + env->RegisterNatives(clazz, kFuzzedDataMethods, kNumFuzzedDataMethods); } +namespace jazzer { void FeedFuzzedDataProvider(const uint8_t *data, std::size_t size) { gDataPtr = data; gRemainingBytes = size; diff --git a/driver/fuzzed_data_provider.h b/driver/fuzzed_data_provider.h index 9b8faf78..c9b72546 100644 --- a/driver/fuzzed_data_provider.h +++ b/driver/fuzzed_data_provider.h @@ -35,9 +35,6 @@ namespace jazzer { constexpr char kFuzzedDataProviderImplClass[] = "com/code_intelligence/jazzer/runtime/FuzzedDataProviderImpl"; -// Registers the native methods in FuzzedDataProvider. -void SetUpFuzzedDataProvider(JNIEnv &env); - // Feed the FuzzedDataProvider with a new data buffer. The buffer is accessed // by native code and not copied into the JVM, so this is cheap to call. void FeedFuzzedDataProvider(const uint8_t *data, std::size_t size); diff --git a/driver/testdata/test/FuzzTargetWithDataProvider.java b/driver/testdata/test/FuzzTargetWithDataProvider.java index fc5bc1b0..16b230ed 100644 --- a/driver/testdata/test/FuzzTargetWithDataProvider.java +++ b/driver/testdata/test/FuzzTargetWithDataProvider.java @@ -20,6 +20,10 @@ import java.util.Arrays; import java.util.stream.Collectors; class FuzzTargetWithDataProvider { + static { + System.loadLibrary("jazzer_initialize"); + } + public static > void assertEqual(T a, T b) { if (a.compareTo(b) != 0) { throw new IllegalArgumentException("Expected: " + a + ", got: " + b); -- cgit v1.2.3 From fde0f7f884e44cf218ad877616465ce8dacce21c Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 3 Aug 2022 21:21:26 +0200 Subject: examples: Reduce fork count for JpegImageParserFuzzer -fork=3 still caused frequent OOMs on Windows. --- examples/BUILD.bazel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index 6163fc58..8042cdd7 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -148,7 +148,7 @@ java_fuzz_target_test( ], expected_findings = ["java.lang.NegativeArraySizeException"], fuzzer_args = [ - "-fork=3", + "-fork=2", ], target_class = "com.example.JpegImageParserFuzzer", # The exit codes of the forked libFuzzer processes are not picked up correctly. -- cgit v1.2.3 From cae3bdfbd06fffc8449c145e694d18907db4243d Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 4 Aug 2022 07:13:06 +0200 Subject: tests: Disable AutofuzzHookDependencies test on Windows --- tests/BUILD.bazel | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 0089dca8..ea5a3b0b 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -1,4 +1,4 @@ -load("//bazel:compat.bzl", "SKIP_ON_MACOS") +load("//bazel:compat.bzl", "SKIP_ON_MACOS", "SKIP_ON_WINDOWS") load("//bazel:fuzz_target.bzl", "java_fuzz_target_test") java_fuzz_target_test( @@ -58,6 +58,8 @@ java_fuzz_target_test( "--autofuzz_ignore=java.lang.Exception", "--keep_going=1", ], + # FIXME(fabian): Regularly times out on Windows with 0 exec/s for minutes. + target_compatible_with = SKIP_ON_WINDOWS, ) java_fuzz_target_test( -- cgit v1.2.3 From 678ceb53e8c2f045b550f3ddbd80ed7a9818de41 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 28 Jul 2022 18:58:47 +0200 Subject: driver: Rewrite fuzz_target_runner.cpp in Java This is mostly a faithful Java rewrite of the original native code, with a few changes believed not to cause any serious backwards compatibility concerns: * Emit an empty line to stderr rather than stdout before the Java exception. DEDUP_TOKEN does not have to appear at the beginning of a line to be picked up by libFuzzer, so this seems more consistent. * Simplify the logic around dedup tokens to also emit them with hooks set to false, but not by default. * Do not emit the escape character when splitting a list-valued argument on a separator - this appeared to be a design bug. * Use the same "split on unescaped separator" function for all splitting. The native tests have been replaced by a single comprehensive Java test. --- agent/BUILD.bazel | 1 + .../jazzer/instrumentor/BUILD.bazel | 1 + .../code_intelligence/jazzer/runtime/BUILD.bazel | 4 + .../jazzer/runtime/CoverageMap.java | 4 + .../jazzer/runtime/FuzzedDataProviderImpl.java | 4 + .../jazzer/runtime/JazzerInternal.java | 4 +- .../runtime/RecordingFuzzedDataProvider.java | 7 +- .../java/com/code_intelligence/jazzer/BUILD.bazel | 1 + driver/BUILD.bazel | 24 +- driver/coverage_tracker.cpp | 17 + driver/fuzz_target_runner.cpp | 16 + driver/fuzz_target_runner.h | 1 + driver/fuzzed_data_provider.cpp | 14 + driver/jvm_tooling.cpp | 8 + driver/jvm_tooling_test.cpp | 65 ---- driver/libfuzzer_driver.cpp | 48 ++- driver/libfuzzer_driver.h | 8 +- driver/libfuzzer_fuzz_target.cpp | 43 +-- .../code_intelligence/jazzer/driver/BUILD.bazel | 34 ++ .../jazzer/driver/FuzzTargetRunner.java | 368 +++++++++++++++++++++ .../com/code_intelligence/jazzer/driver/Opt.java | 148 +++++++++ .../jazzer/driver/Reproducer.java.tmpl | 28 ++ .../jazzer/driver/ReproducerTemplates.java | 54 +++ .../code_intelligence/jazzer/driver/BUILD.bazel | 24 ++ .../jazzer/driver/FuzzTargetRunnerTest.java | 214 ++++++++++++ .../code_intelligence/jazzer/driver/OptTest.java | 44 +++ .../code_intelligence/jazzer/driver/BUILD.bazel | 11 + .../jazzer/driver/fuzz_target_runner_mock.cpp | 25 ++ driver/testdata/test/FuzzTargetWithCoverage.java | 24 -- driver/testdata/test/FuzzTargetWithInit.java | 30 -- driver/testdata/test/SimpleFuzzTarget.java | 25 -- 31 files changed, 1096 insertions(+), 203 deletions(-) create mode 100644 driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel create mode 100644 driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java create mode 100644 driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java create mode 100644 driver/src/main/java/com/code_intelligence/jazzer/driver/Reproducer.java.tmpl create mode 100644 driver/src/main/java/com/code_intelligence/jazzer/driver/ReproducerTemplates.java create mode 100644 driver/src/test/java/com/code_intelligence/jazzer/driver/BUILD.bazel create mode 100644 driver/src/test/java/com/code_intelligence/jazzer/driver/FuzzTargetRunnerTest.java create mode 100644 driver/src/test/java/com/code_intelligence/jazzer/driver/OptTest.java create mode 100644 driver/src/test/native/com/code_intelligence/jazzer/driver/BUILD.bazel create mode 100644 driver/src/test/native/com/code_intelligence/jazzer/driver/fuzz_target_runner_mock.cpp delete mode 100644 driver/testdata/test/FuzzTargetWithCoverage.java delete mode 100644 driver/testdata/test/FuzzTargetWithInit.java delete mode 100644 driver/testdata/test/SimpleFuzzTarget.java diff --git a/agent/BUILD.bazel b/agent/BUILD.bazel index e2cd5e3b..f3ce04f8 100644 --- a/agent/BUILD.bazel +++ b/agent/BUILD.bazel @@ -13,6 +13,7 @@ java_binary( ] + [" {}:".format(c) for c in SANITIZER_CLASSES], runtime_deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/agent:agent_lib", + "//driver/src/main/java/com/code_intelligence/jazzer/driver:fuzz_target_runner", "//sanitizers", ], ) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel index 4ebe9b79..db93dcae 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -21,6 +21,7 @@ kt_jvm_library( "//agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor:__pkg__", "//agent/src/main/java/com/code_intelligence/jazzer/agent:__pkg__", "//agent/src/test/java/com/code_intelligence/jazzer/instrumentor:__pkg__", + "//driver/src/main/java/com/code_intelligence/jazzer/driver:__pkg__", ], deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/runtime", 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 c85aea96..8f418326 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 @@ -9,6 +9,7 @@ java_library( visibility = [ "//agent/src/main/java/com/code_intelligence/jazzer/replay:__pkg__", "//agent/src/test/java/com/code_intelligence/jazzer/runtime:__pkg__", + "//driver/src/main/java/com/code_intelligence/jazzer/driver:__pkg__", ], deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/api", @@ -26,6 +27,8 @@ java_library( srcs = ["CoverageMap.java"], visibility = [ "//agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor:__pkg__", + "//driver/src/main/java/com/code_intelligence/jazzer/driver:__pkg__", + "//driver/src/test:__subpackages__", "//driver/testdata:__pkg__", ], deps = [ @@ -77,6 +80,7 @@ java_library( name = "unsafe_provider", srcs = ["UnsafeProvider.java"], visibility = [ + "//driver/src/test:__subpackages__", "//sanitizers/src/main/java:__subpackages__", ], ) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java index b2238799..fdc83791 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java @@ -114,6 +114,10 @@ final public class CoverageMap { } } + // Returns the IDs of all blocks that have been covered in at least one run (not just the current + // one). + public static native int[] getEverCoveredIds(); + private static native void initialize(long countersAddress); private static native void registerNewCounters(int oldNumCounters, int newNumCounters); 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 2a6f68d1..262e4596 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 @@ -30,6 +30,10 @@ public class FuzzedDataProviderImpl implements FuzzedDataProvider { nativeInit(); } + // Resets the FuzzedDataProvider state to read from the beginning to the end of the last fuzzer + // input. + public static native void reset(); + @Override public native boolean consumeBoolean(); @Override public native boolean[] consumeBooleans(int maxLength); diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java index 6802bd75..79c851ad 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java @@ -19,8 +19,7 @@ import java.util.ArrayList; final public class JazzerInternal { private static final ArrayList ON_FUZZ_TARGET_READY_CALLBACKS = new ArrayList<>(); - // Accessed from native code. - private static Throwable lastFinding; + public static Throwable lastFinding; // Accessed from api.Jazzer via reflection. public static void reportFindingFromHook(Throwable finding) { @@ -35,7 +34,6 @@ final public class JazzerInternal { ON_FUZZ_TARGET_READY_CALLBACKS.add(callback); } - // Accessed from native code. public static void onFuzzTargetReady(String fuzzTargetClass) { ON_FUZZ_TARGET_READY_CALLBACKS.forEach(Runnable::run); ON_FUZZ_TARGET_READY_CALLBACKS.clear(); diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider.java index f17da356..72d82814 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider.java @@ -18,15 +18,12 @@ import com.code_intelligence.jazzer.api.FuzzedDataProvider; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Base64; // Wraps the native FuzzedDataProviderImpl and serializes all its return values // into a Base64-encoded string. -final class RecordingFuzzedDataProvider implements FuzzedDataProvider { +public final class RecordingFuzzedDataProvider implements FuzzedDataProvider { private final FuzzedDataProvider target; private final ArrayList recordedReplies = new ArrayList<>(); @@ -34,7 +31,6 @@ final class RecordingFuzzedDataProvider implements FuzzedDataProvider { this.target = target; } - // Called from native code. public static FuzzedDataProvider makeFuzzedDataProviderProxy() { return makeFuzzedDataProviderProxy(new FuzzedDataProviderImpl()); } @@ -43,7 +39,6 @@ final class RecordingFuzzedDataProvider implements FuzzedDataProvider { return new RecordingFuzzedDataProvider(target); } - // Called from native code. public static String serializeFuzzedDataProviderProxy(FuzzedDataProvider proxy) throws IOException { return ((RecordingFuzzedDataProvider) proxy).serialize(); diff --git a/agent/src/test/java/com/code_intelligence/jazzer/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/BUILD.bazel index f2595077..52ddf679 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/BUILD.bazel +++ b/agent/src/test/java/com/code_intelligence/jazzer/BUILD.bazel @@ -7,5 +7,6 @@ java_jni_library( visibility = [ "//agent/src/jmh/java/com/code_intelligence/jazzer:__subpackages__", "//agent/src/test/java/com/code_intelligence/jazzer:__subpackages__", + "//driver/src/test:__subpackages__", ], ) diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index b91662a1..118e6598 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -9,6 +9,7 @@ cc_library( visibility = [ "//agent/src/jmh/native/com/code_intelligence/jazzer/runtime:__pkg__", "//agent/src/main/native/com/code_intelligence/jazzer/runtime:__pkg__", + "//driver/src/main/native/com/code_intelligence/jazzer/driver:__pkg__", ], ) @@ -33,18 +34,25 @@ cc_library( ], visibility = [ "//agent/src/main/native/com/code_intelligence/jazzer/replay:__pkg__", + "//driver/src/main/native/com/code_intelligence/jazzer/driver:__pkg__", + "//driver/src/test:__subpackages__", ], deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/runtime:fuzzed_data_provider.hdrs", "@com_google_absl//absl/strings:str_format", "@fmeum_rules_jni//jni", ], + # Symbols may only be referenced dynamically via JNI. + alwayslink = True, ) cc_library( name = "signal_handler", srcs = ["signal_handler.cpp"], linkstatic = True, + visibility = [ + "//driver/src/main/native/com/code_intelligence/jazzer/driver:__pkg__", + ], deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/runtime:signal_handler.hdrs", "@fmeum_rules_jni//jni", @@ -67,9 +75,12 @@ cc_library( srcs = ["coverage_tracker.cpp"], hdrs = ["coverage_tracker.h"], linkstatic = True, + visibility = [ + "//driver/src/main/native/com/code_intelligence/jazzer/driver:__pkg__", + "//driver/src/test:__subpackages__", + ], deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/runtime:coverage_map.hdrs", - "@fmeum_rules_jni//jni", ], # Symbols are only referenced dynamically via JNI. alwayslink = True, @@ -79,6 +90,9 @@ cc_library( name = "libfuzzer_callbacks", srcs = ["libfuzzer_callbacks.cpp"], linkstatic = True, + visibility = [ + "//driver/src/main/native/com/code_intelligence/jazzer/driver:__pkg__", + ], deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/runtime:trace_data_flow_native_callbacks.hdrs", "@com_google_absl//absl/strings", @@ -156,6 +170,7 @@ cc_17_library( linkstatic = True, deps = [ ":jvm_tooling_lib", + "//driver/src/main/java/com/code_intelligence/jazzer/driver:fuzz_target_runner.hdrs", "@jazzer_libfuzzer//:libFuzzer", ], alwayslink = True, @@ -260,6 +275,7 @@ cc_binary( cc_library( name = "sanitizer_symbols_for_tests", srcs = ["sanitizer_symbols_for_tests.cpp"], + visibility = ["//driver/src/test:__subpackages__"], alwayslink = True, ) @@ -272,7 +288,11 @@ cc_jni_library( "//conditions:default": ["rtld_global_hack.cpp"], }), visibility = ["//agent/src/test/java:__subpackages__"], - deps = [":sanitizer_symbols_for_tests"], + deps = [ + ":coverage_tracker", + ":fuzzed_data_provider", + ":sanitizer_symbols_for_tests", + ], ) cc_test( diff --git a/driver/coverage_tracker.cpp b/driver/coverage_tracker.cpp index 63aefd30..fffab506 100644 --- a/driver/coverage_tracker.cpp +++ b/driver/coverage_tracker.cpp @@ -175,3 +175,20 @@ Java_com_code_1intelligence_jazzer_runtime_CoverageMap_registerNewCounters( ::jazzer::CoverageTracker::RegisterNewCounters(*env, old_num_counters, new_num_counters); } + +[[maybe_unused]] jintArray +Java_com_code_1intelligence_jazzer_runtime_CoverageMap_getEverCoveredIds( + JNIEnv *env, jclass) { + uintptr_t *covered_pcs; + jint num_covered_pcs = __sanitizer_cov_get_observed_pcs(&covered_pcs); + std::vector covered_edge_ids(covered_pcs, + covered_pcs + num_covered_pcs); + delete[] covered_pcs; + + jintArray covered_edge_ids_jni = env->NewIntArray(num_covered_pcs); + AssertNoException(*env); + env->SetIntArrayRegion(covered_edge_ids_jni, 0, num_covered_pcs, + covered_edge_ids.data()); + AssertNoException(*env); + return covered_edge_ids_jni; +} diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index e401551d..a72884d2 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -90,6 +90,22 @@ constexpr auto kAutofuzzFuzzTargetClass = constexpr auto dataChunkMaxLength = std::numeric_limits::max() - 1; namespace jazzer { +std::vector fuzzTargetRunnerFlagsAsDefines() { + return { + absl::StrFormat("-Djazzer.target_class=%s", FLAGS_target_class), + absl::StrFormat("-Djazzer.target_args=%s", FLAGS_target_args), + absl::StrFormat("-Djazzer.keep_going=%d", FLAGS_keep_going), + absl::StrFormat("-Djazzer.dedup=%s", FLAGS_dedup ? "true" : "false"), + absl::StrFormat("-Djazzer.ignore=%s", FLAGS_ignore), + absl::StrFormat("-Djazzer.reproducer_path=%s", FLAGS_reproducer_path), + absl::StrFormat("-Djazzer.coverage_report=%s", FLAGS_coverage_report), + absl::StrFormat("-Djazzer.coverage_dump=%s", FLAGS_coverage_dump), + absl::StrFormat("-Djazzer.autofuzz=%s", FLAGS_autofuzz), + absl::StrFormat("-Djazzer.autofuzz_ignore=%s", FLAGS_autofuzz_ignore), + absl::StrFormat("-Djazzer.hooks=%s", FLAGS_hooks ? "true" : "false"), + }; +} + // split a string on unescaped spaces std::vector splitOnSpace(const std::string &s) { if (s.empty()) { diff --git a/driver/fuzz_target_runner.h b/driver/fuzz_target_runner.h index 0a0f1e11..23d18fb0 100644 --- a/driver/fuzz_target_runner.h +++ b/driver/fuzz_target_runner.h @@ -72,4 +72,5 @@ class FuzzTargetRunner : public ExceptionPrinter { void DumpReproducer(const uint8_t *data, std::size_t size); }; +std::vector fuzzTargetRunnerFlagsAsDefines(); } // namespace jazzer diff --git a/driver/fuzzed_data_provider.cpp b/driver/fuzzed_data_provider.cpp index 1daee741..8b60fd62 100644 --- a/driver/fuzzed_data_provider.cpp +++ b/driver/fuzzed_data_provider.cpp @@ -56,9 +56,14 @@ namespace { +// The current position in the fuzzer input. const uint8_t *gDataPtr = nullptr; +// The remaining unconsumed bytes at the current position in the fuzzer input. std::size_t gRemainingBytes = 0; +const uint8_t *gFuzzerInputStart = nullptr; +std::size_t gFuzzerInputSize = 0; + // Advance by `bytes` bytes in the buffer or stay at the end if it has been // consumed. void Advance(const std::size_t bytes) { @@ -700,9 +705,18 @@ void Java_com_code_1intelligence_jazzer_runtime_FuzzedDataProviderImpl_nativeIni env->RegisterNatives(clazz, kFuzzedDataMethods, kNumFuzzedDataMethods); } +void Java_com_code_1intelligence_jazzer_runtime_FuzzedDataProviderImpl_reset( + JNIEnv *env, jclass clazz) { + gDataPtr = gFuzzerInputStart; + gRemainingBytes = gFuzzerInputSize; +} + namespace jazzer { void FeedFuzzedDataProvider(const uint8_t *data, std::size_t size) { gDataPtr = data; gRemainingBytes = size; + + gFuzzerInputStart = data; + gFuzzerInputSize = size; } } // namespace jazzer diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index 96fe7f2b..c69c044f 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -25,6 +25,7 @@ #include "absl/strings/str_join.h" #include "absl/strings/str_replace.h" #include "absl/strings/str_split.h" +#include "fuzz_target_runner.h" #include "gflags/gflags.h" #include "glog/logging.h" #include "tools/cpp/runfiles/runfiles.h" @@ -281,6 +282,13 @@ JVM::JVM(std::string_view executable_path, std::string_view seed) { options.push_back(JavaVMOption{ .optionString = const_cast(fake_pcs_property.c_str())}); + std::vector fuzz_target_runner_defines = + ::jazzer::fuzzTargetRunnerFlagsAsDefines(); + for (const auto &define : fuzz_target_runner_defines) { + options.push_back( + JavaVMOption{.optionString = const_cast(define.c_str())}); + } + // Add additional JVM options set through JAVA_OPTS. std::vector java_opts_args; const char *java_opts = std::getenv("JAVA_OPTS"); diff --git a/driver/jvm_tooling_test.cpp b/driver/jvm_tooling_test.cpp index 5a6f99d3..88d32f7f 100644 --- a/driver/jvm_tooling_test.cpp +++ b/driver/jvm_tooling_test.cpp @@ -103,25 +103,6 @@ TEST_F(JvmToolingTest, JniProperties) { } } -TEST_F(JvmToolingTest, SimpleFuzzTarget) { - // see testdata/test/SimpleFuzzTarget.java for the implementation of the fuzz - // target - FLAGS_target_class = "test.SimpleFuzzTarget"; - FLAGS_target_args = ""; - FuzzTargetRunner fuzz_target_runner(*jvm_); - - // normal case: fuzzerTestOneInput returns false - std::string input("random"); - ASSERT_EQ(RunResult::kOk, fuzz_target_runner.Run( - (const uint8_t *)input.c_str(), input.size())); - - // exception is thrown in fuzzerTestOneInput - input = "crash"; - ASSERT_EQ( - RunResult::kException, - fuzz_target_runner.Run((const uint8_t *)input.c_str(), input.size())); -} - class ExceptionPrinterTest : public ExceptionPrinter { public: ExceptionPrinterTest(JVM &jvm) : ExceptionPrinter(jvm), jvm_(jvm) {} @@ -145,50 +126,4 @@ TEST_F(JvmToolingTest, ExceptionPrinter) { ASSERT_TRUE(exception_printer.TriggerJvmException().rfind( "java.lang.IllegalArgumentException", 0) == 0); } - -TEST_F(JvmToolingTest, FuzzTargetWithInit) { - // see testdata/test/FuzzTargetWithInit.java for the implementation of the - // fuzz target. All string arguments provided in fuzzerInitialize(String[]) - // will cause a crash if input in fuzzerTestOneInput(byte[]). - FLAGS_target_class = "test.FuzzTargetWithInit"; - FLAGS_target_args = "crash_now crash_harder"; - FuzzTargetRunner fuzz_target_runner(*jvm_); - - // normal case: fuzzerTestOneInput returns false - std::string input("random"); - ASSERT_EQ(RunResult::kOk, fuzz_target_runner.Run( - (const uint8_t *)input.c_str(), input.size())); - - input = "crash_now"; - ASSERT_EQ( - RunResult::kException, - fuzz_target_runner.Run((const uint8_t *)input.c_str(), input.size())); - - input = "this is harmless"; - ASSERT_EQ(RunResult::kOk, fuzz_target_runner.Run( - (const uint8_t *)input.c_str(), input.size())); - - input = "crash_harder"; - ASSERT_EQ( - RunResult::kException, - fuzz_target_runner.Run((const uint8_t *)input.c_str(), input.size())); -} - -TEST_F(JvmToolingTest, TestCoverageMap) { - auto coverage_counters_array = CoverageTracker::GetCoverageCounters(); - ASSERT_EQ(0, coverage_counters_array[0]); - - FLAGS_target_class = "test.FuzzTargetWithCoverage"; - FLAGS_target_args = ""; - FuzzTargetRunner fuzz_target_runner(*jvm_); - // run a fuzz target input which will cause the first coverage counter to - // increase - fuzz_target_runner.Run(nullptr, 0); - ASSERT_EQ(1, coverage_counters_array[0]); - - // calling the fuzz target twice more - fuzz_target_runner.Run(nullptr, 0); - fuzz_target_runner.Run(nullptr, 0); - ASSERT_EQ(3, coverage_counters_array[0]); -} } // namespace jazzer diff --git a/driver/libfuzzer_driver.cpp b/driver/libfuzzer_driver.cpp index d808e2c4..bbcd94dd 100644 --- a/driver/libfuzzer_driver.cpp +++ b/driver/libfuzzer_driver.cpp @@ -49,6 +49,13 @@ DECLARE_string(coverage_report); DECLARE_string(coverage_dump); namespace { +constexpr auto kFuzzTargetRunnerClassName = + "com/code_intelligence/jazzer/driver/FuzzTargetRunner"; + +bool gUseFuzzedDataProvider; +jclass gRunner; +jmethodID gRunOneId; + std::vector modified_argv; std::string GetNewTempFilePath() { @@ -196,9 +203,18 @@ void AbstractLibfuzzerDriver::initJvm(std::string_view executable_path, LibfuzzerDriver::LibfuzzerDriver(int *argc, char ***argv) : AbstractLibfuzzerDriver(argc, argv, getUsageString()) { - // the FuzzTargetRunner can only be initialized after the fuzzer callbacks - // have been registered otherwise link errors would occur - runner_ = std::make_unique(*jvm_); + JNIEnv &env = jvm_->GetEnv(); + jclass runner = env.FindClass(kFuzzTargetRunnerClassName); + if (env.ExceptionCheck()) { + env.ExceptionDescribe(); + _Exit(1); + } + gRunner = reinterpret_cast(env.NewGlobalRef(runner)); + gRunOneId = env.GetStaticMethodID(runner, "runOne", "([B)I"); + jfieldID use_fuzzed_data_provider_id = + env.GetStaticFieldID(runner, "useFuzzedDataProvider", "Z"); + gUseFuzzedDataProvider = + env.GetStaticBooleanField(runner, use_fuzzed_data_provider_id); } std::string LibfuzzerDriver::getUsageString() { @@ -206,14 +222,24 @@ std::string LibfuzzerDriver::getUsageString() { jazzer --cp= --target_class= )"; } -RunResult LibfuzzerDriver::TestOneInput(const uint8_t *data, - const std::size_t size) { - // pass the fuzzer input to the java fuzz target - return runner_->Run(data, size); -} - -void LibfuzzerDriver::DumpReproducer(const uint8_t *data, std::size_t size) { - return runner_->DumpReproducer(data, size); +int LibfuzzerDriver::TestOneInput(const uint8_t *data, const std::size_t size) { + JNIEnv &env = jvm_->GetEnv(); + jbyteArray input = nullptr; + jint jsize = + std::min(size, static_cast(std::numeric_limits::max())); + if (gUseFuzzedDataProvider) { + ::jazzer::FeedFuzzedDataProvider(data, size); + } else { + input = env.NewByteArray(jsize); + env.SetByteArrayRegion(input, 0, jsize, + reinterpret_cast(data)); + } + int res = env.CallStaticIntMethod(gRunner, gRunOneId, input); + if (env.ExceptionCheck()) { + env.ExceptionDescribe(); + _Exit(1); + } + return res; } } // namespace jazzer diff --git a/driver/libfuzzer_driver.h b/driver/libfuzzer_driver.h index 20fd0280..53713092 100644 --- a/driver/libfuzzer_driver.h +++ b/driver/libfuzzer_driver.h @@ -20,7 +20,6 @@ #include #include "absl/strings/match.h" -#include "fuzz_target_runner.h" #include "fuzzed_data_provider.h" #include "jvm_tooling.h" @@ -33,7 +32,7 @@ class AbstractLibfuzzerDriver { virtual ~AbstractLibfuzzerDriver() = default; - virtual RunResult TestOneInput(const uint8_t *data, std::size_t size) = 0; + virtual int TestOneInput(const uint8_t *data, std::size_t size) = 0; // Default value of the libFuzzer -error_exitcode flag. static constexpr int kErrorExitCode = 77; @@ -54,16 +53,13 @@ class LibfuzzerDriver : public AbstractLibfuzzerDriver { public: LibfuzzerDriver(int *argc, char ***argv); - RunResult TestOneInput(const uint8_t *data, std::size_t size) override; + int TestOneInput(const uint8_t *data, std::size_t size) override; ~LibfuzzerDriver() override = default; void DumpReproducer(const uint8_t *data, std::size_t size); private: - // initializes the fuzz target and invokes the TestOneInput function - std::unique_ptr runner_; - static std::string getUsageString(); }; diff --git a/driver/libfuzzer_fuzz_target.cpp b/driver/libfuzzer_fuzz_target.cpp index 1f3a5c31..15c7d358 100644 --- a/driver/libfuzzer_fuzz_target.cpp +++ b/driver/libfuzzer_fuzz_target.cpp @@ -14,6 +14,7 @@ #include +#include "com_code_intelligence_jazzer_driver_FuzzTargetRunner.h" #include "libfuzzer_driver.h" namespace { @@ -73,6 +74,16 @@ extern "C" [[maybe_unused]] void __jazzer_set_death_callback( }); } +void Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner_printCrashingInput( + JNIEnv *, jclass) { + jazzer::AbstractLibfuzzerDriver::libfuzzer_print_crashing_input_(); +} + +void Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner__1Exit( + JNIEnv *, jclass, jint exit_code) { + _Exit(exit_code); +} + // Entry point called by libfuzzer before any LLVMFuzzerTestOneInput(...) // invocations. extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) { @@ -86,37 +97,7 @@ extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) { return 0; } -#ifndef _WIN32 -__attribute__((weak)) -#endif -extern "C" int -__llvm_profile_write_file(void) { - return 0; -} - // Called by the fuzzer for every fuzzing input. extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, const size_t size) { - auto result = gLibfuzzerDriver->TestOneInput(data, size); - if (result != jazzer::RunResult::kOk) { - // Fuzzer triggered an exception or assertion in Java code. Skip the - // uninformative libFuzzer stack trace. - std::cerr << "== libFuzzer crashing input ==\n"; - Driver::libfuzzer_print_crashing_input_(); - // DumpReproducer needs to be called after libFuzzer printed its final - // stats as otherwise it would report incorrect coverage. - gLibfuzzerDriver->DumpReproducer(data, size); - if (result == jazzer::RunResult::kDumpAndContinue) { - // Continue fuzzing after printing the crashing input. - return 0; - } - // Exit directly without invoking libFuzzer's atexit hook. - driver_cleanup(); - // When running with LLVM coverage instrumentation, write out the profile as - // the exit hook that writes it won't run. - // TODO: Remove once https://github.com/bazelbuild/bazel/pull/15166 has been - // fixed and use continuous mode instead. - __llvm_profile_write_file(); - _Exit(Driver::kErrorExitCode); - } - return 0; + return gLibfuzzerDriver->TestOneInput(data, size); } diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel b/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel new file mode 100644 index 00000000..3eb39aee --- /dev/null +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -0,0 +1,34 @@ +load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library") + +java_jni_library( + name = "fuzz_target_runner", + srcs = ["FuzzTargetRunner.java"], + visibility = [ + "//agent:__pkg__", + "//driver:__pkg__", + "//driver/src/test:__subpackages__", + ], + deps = [ + ":opt", + ":reproducer_templates", + "//agent/src/main/java/com/code_intelligence/jazzer/api", + "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz", + "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:coverage_map", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:fuzzed_data_provider", + ], +) + +java_library( + name = "reproducer_templates", + srcs = ["ReproducerTemplates.java"], + resources = ["Reproducer.java.tmpl"], + deps = [":opt"], +) + +java_library( + name = "opt", + srcs = ["Opt.java"], + visibility = ["//driver/src/test/java/com/code_intelligence/jazzer/driver:__pkg__"], +) diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java new file mode 100644 index 00000000..c4c7e6b1 --- /dev/null +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -0,0 +1,368 @@ +/* + * Copyright 2022 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.driver; + +import static java.lang.System.err; +import static java.lang.System.exit; +import static java.lang.System.out; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import com.code_intelligence.jazzer.autofuzz.FuzzTarget; +import com.code_intelligence.jazzer.instrumentor.CoverageRecorder; +import com.code_intelligence.jazzer.runtime.CoverageMap; +import com.code_intelligence.jazzer.runtime.ExceptionUtils; +import com.code_intelligence.jazzer.runtime.FuzzedDataProviderImpl; +import com.code_intelligence.jazzer.runtime.JazzerInternal; +import com.code_intelligence.jazzer.runtime.ManifestUtils; +import com.code_intelligence.jazzer.runtime.RecordingFuzzedDataProvider; +import java.io.IOException; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +/** + * Executes a fuzz target and reports findings. + * + *

    This class maintains global state (both native and non-native) and thus cannot be used + * concurrently. + */ +public final class FuzzTargetRunner { + // Default value of the libFuzzer -error_exitcode flag. + private static final int LIBFUZZER_ERROR_EXIT_CODE = 77; + private static final String AUTOFUZZ_FUZZ_TARGET = + "com.code_intelligence.jazzer.autofuzz.FuzzTarget"; + private static final String FUZZER_TEST_ONE_INPUT = "fuzzerTestOneInput"; + private static final String FUZZER_INITIALIZE = "fuzzerInitialize"; + private static final String FUZZER_TEARDOWN = "fuzzerTearDown"; + // A constant pool CONSTANT_Utf8_info entry should be able to hold data of size + // uint16, but somehow this does not seem to be the case and leads to invalid + // code crash reproducer code. Reducing the size by one resolves the problem. + private static final int DATA_CHUNK_MAX_LENGTH = Short.MAX_VALUE - 1; + + private static final Set ignoredTokens = new HashSet<>(Opt.ignore); + private static final FuzzedDataProvider fuzzedDataProvider = new FuzzedDataProviderImpl(); + private static final Class fuzzTargetClass; + private static final MethodHandle fuzzTarget; + public static final boolean useFuzzedDataProvider; + private static long runCount = 0; + + static { + String targetClassName = determineFuzzTargetClassName(); + try { + // When running with the agent, the JAR containing the agent and the driver has been added to + // the bootstrap class loader path at the time the native driver can use FindClass to load the + // Java fuzz target runner. As a result, FuzzTargetRunner's class loader will be the bootstrap + // class loader, which doesn't have the fuzz target on its classpath. We thus have to + // explicitly use the system class loader in this case. + ClassLoader notBootstrapLoader = FuzzTargetRunner.class.getClassLoader(); + if (notBootstrapLoader == null) { + notBootstrapLoader = ClassLoader.getSystemClassLoader(); + } + fuzzTargetClass = Class.forName(targetClassName, false, notBootstrapLoader); + } catch (ClassNotFoundException e) { + err.print("ERROR: "); + e.printStackTrace(err); + exit(1); + throw new IllegalStateException("Not reached"); + } + // Inform the agent about the fuzz target class. Important note: This has to be done *before* + // the class is initialized so that hooks can enable themselves in time for the fuzz target's + // static initializer. + JazzerInternal.onFuzzTargetReady(targetClassName); + + Method bytesFuzzTarget = targetPublicStaticMethodOrNull(FUZZER_TEST_ONE_INPUT, byte[].class); + Method dataFuzzTarget = + targetPublicStaticMethodOrNull(FUZZER_TEST_ONE_INPUT, FuzzedDataProvider.class); + if ((bytesFuzzTarget != null) == (dataFuzzTarget != null)) { + err.printf( + "ERROR: %s must define exactly one of the following two functions:%n", targetClassName); + err.println("public static void fuzzerTestOneInput(byte[] ...)"); + err.println("public static void fuzzerTestOneInput(FuzzedDataProvider ...)"); + err.println( + "Note: Fuzz targets returning boolean are no longer supported; exceptions should be thrown instead of returning true."); + exit(1); + } + try { + if (bytesFuzzTarget != null) { + useFuzzedDataProvider = false; + fuzzTarget = MethodHandles.publicLookup().unreflect(bytesFuzzTarget); + } else { + useFuzzedDataProvider = true; + fuzzTarget = MethodHandles.publicLookup().unreflect(dataFuzzTarget); + } + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + + Method initializeNoArgs = targetPublicStaticMethodOrNull(FUZZER_INITIALIZE); + Method initializeWithArgs = targetPublicStaticMethodOrNull(FUZZER_INITIALIZE, String[].class); + try { + if (initializeWithArgs != null) { + initializeWithArgs.invoke(null, (Object) Opt.targetArgs.toArray(new String[] {})); + } else if (initializeNoArgs != null) { + initializeNoArgs.invoke(null); + } + } catch (IllegalAccessException | InvocationTargetException e) { + err.print("== Java Exception in fuzzerInitialize: "); + e.printStackTrace(err); + exit(1); + } + + if (Opt.hooks) { + CoverageRecorder.updateCoveredIdsWithCoverageMap(); + } + + Runtime.getRuntime().addShutdownHook(new Thread(FuzzTargetRunner::shutdown)); + } + + /** + * Executes the user-provided fuzz target once. + * + * @param data the raw fuzzer input if using a {@code byte[]}-based fuzz target and {@code null} + * when using a {@link FuzzedDataProvider}-based fuzz target. + * @return the value that the native LLVMFuzzerTestOneInput function should return. Currently, + * this is always 0. The function may exit the process instead of returning. + */ + public static int runOne(byte[] data) { + if (Opt.hooks && runCount < 2) { + runCount++; + // For the first two runs only, replay the coverage recorded from static initializers. + // libFuzzer cleared the coverage map after they ran and could fail to see any coverage, + // triggering an early exit, if we don't replay it here. + // https://github.com/llvm/llvm-project/blob/957a5e987444d3193575d6ad8afe6c75da00d794/compiler-rt/lib/fuzzer/FuzzerLoop.cpp#L804-L809 + CoverageRecorder.replayCoveredIds(); + } + + Throwable finding = null; + try { + if (useFuzzedDataProvider) { + // The FuzzedDataProvider has already been fed with the fuzzer input in + // LLVMFuzzerTestOneInput. + fuzzTarget.invokeExact(fuzzedDataProvider); + } else { + fuzzTarget.invokeExact(data); + } + } catch (Throwable uncaughtFinding) { + finding = uncaughtFinding; + } + // Explicitly reported findings take precedence over uncaught exceptions. + if (JazzerInternal.lastFinding != null) { + finding = JazzerInternal.lastFinding; + JazzerInternal.lastFinding = null; + } + if (finding == null) { + return 0; + } + if (Opt.hooks) { + finding = ExceptionUtils.preprocessThrowable(finding); + } + + long dedupToken = Opt.dedup ? ExceptionUtils.computeDedupToken(finding) : 0; + // Opt.keepGoing implies Opt.dedup. + if (Opt.keepGoing > 1 && !ignoredTokens.add(dedupToken)) { + return 0; + } + + err.println(); + err.print("== Java Exception: "); + finding.printStackTrace(err); + if (Opt.dedup) { + // Has to be printed to stdout as it is parsed by libFuzzer when minimizing a crash. It does + // not necessarily have to appear at the beginning of a line. + // https://github.com/llvm/llvm-project/blob/4c106c93eb68f8f9f201202677cd31e326c16823/compiler-rt/lib/fuzzer/FuzzerDriver.cpp#L342 + out.printf(Locale.ROOT, "DEDUP_TOKEN: %016x%n", dedupToken); + } + err.println("== libFuzzer crashing input =="); + printCrashingInput(); + // dumpReproducer needs to be called after libFuzzer printed its final stats as otherwise it + // would report incorrect coverage - the reproducer generation involved rerunning the fuzz + // target. + dumpReproducer(data); + + if (Opt.keepGoing == 1 || ignoredTokens.size() >= Opt.keepGoing) { + // Reached the maximum amount of findings to keep going for, crash after shutdown. We use + // _Exit rather than System.exit to not trigger libFuzzer's exit handlers. + shutdown(); + _Exit(LIBFUZZER_ERROR_EXIT_CODE); + throw new IllegalStateException("Not reached"); + } + return 0; + } + + private static void shutdown() { + if (!Opt.coverageDump.isEmpty() || !Opt.coverageReport.isEmpty()) { + int[] everCoveredIds = CoverageMap.getEverCoveredIds(); + if (!Opt.coverageDump.isEmpty()) { + CoverageRecorder.dumpJacocoCoverage(everCoveredIds, Opt.coverageDump); + } + if (!Opt.coverageReport.isEmpty()) { + CoverageRecorder.dumpCoverageReport(everCoveredIds, Opt.coverageReport); + } + } + + Method teardown = targetPublicStaticMethodOrNull(FUZZER_TEARDOWN); + if (teardown == null) { + return; + } + err.println("calling fuzzerTearDown function"); + try { + teardown.invoke(null); + } catch (InvocationTargetException e) { + // An exception in fuzzerTearDown is a regular finding. + err.print("== Java Exception in fuzzerTearDown: "); + e.getCause().printStackTrace(err); + _Exit(LIBFUZZER_ERROR_EXIT_CODE); + } catch (Throwable t) { + // Any other exception is an error. + t.printStackTrace(err); + _Exit(1); + } + } + + private static String determineFuzzTargetClassName() { + if (!Opt.autofuzz.isEmpty()) { + return AUTOFUZZ_FUZZ_TARGET; + } + if (!Opt.targetClass.isEmpty()) { + return Opt.targetClass; + } + String manifestTargetClass = ManifestUtils.detectFuzzTargetClass(); + if (manifestTargetClass != null) { + return manifestTargetClass; + } + err.println("Missing argument --target_class="); + exit(1); + throw new IllegalStateException("Not reached"); + } + + private static void dumpReproducer(byte[] data) { + if (data == null) { + assert useFuzzedDataProvider; + FuzzedDataProviderImpl.reset(); + data = fuzzedDataProvider.consumeRemainingAsBytes(); + } + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("SHA-1 not available", e); + } + String dataSha1 = toHexString(digest.digest(data)); + + if (!Opt.autofuzz.isEmpty()) { + FuzzedDataProviderImpl.reset(); + FuzzTarget.dumpReproducer(fuzzedDataProvider, Opt.reproducerPath, dataSha1); + return; + } + + String base64Data; + if (useFuzzedDataProvider) { + FuzzedDataProviderImpl.reset(); + FuzzedDataProvider recordingFuzzedDataProvider = + RecordingFuzzedDataProvider.makeFuzzedDataProviderProxy(); + try { + fuzzTarget.invokeExact(recordingFuzzedDataProvider); + if (JazzerInternal.lastFinding == null) { + err.println("Failed to reproduce crash when rerunning with recorder"); + } + } catch (Throwable ignored) { + // Expected. + } + try { + base64Data = RecordingFuzzedDataProvider.serializeFuzzedDataProviderProxy( + recordingFuzzedDataProvider); + } catch (IOException e) { + err.print("ERROR: Failed to create reproducer: "); + e.printStackTrace(err); + // Don't let libFuzzer print a native stack trace. + _Exit(1); + throw new IllegalStateException("Not reached"); + } + } else { + base64Data = Base64.getEncoder().encodeToString(data); + } + + // The serialization of recorded FuzzedDataProvider invocations can get too long to be emitted + // into the template as a single String literal. This is mitigated by chunking the data and + // concatenating it again in the generated code. + ArrayList chunks = new ArrayList<>(); + for (int i = 0; i <= base64Data.length() / DATA_CHUNK_MAX_LENGTH; i++) { + chunks.add(base64Data.substring(i * DATA_CHUNK_MAX_LENGTH, + Math.min((i + 1) * DATA_CHUNK_MAX_LENGTH, base64Data.length()))); + } + String chunkedBase64Data = String.join("\", \"", chunks); + ReproducerTemplates.dumpReproducer( + chunkedBase64Data, dataSha1, fuzzTargetClass.getName(), useFuzzedDataProvider); + } + + private static Method targetPublicStaticMethodOrNull(String name, Class... parameterTypes) { + try { + Method method = fuzzTargetClass.getMethod(name, parameterTypes); + if (!Modifier.isStatic(method.getModifiers()) || !Modifier.isPublic(method.getModifiers())) { + return null; + } + return method; + } catch (NoSuchMethodException e) { + return null; + } + } + + /** + * Convert a byte array to a lower-case hex string. + * + *

    The returned hex string always has {@code 2 * bytes.length} characters. + * + * @param bytes the bytes to convert + * @return a lower-case hex string representing the bytes + */ + private static String toHexString(byte[] bytes) { + String unpadded = new BigInteger(1, bytes).toString(16); + int numLeadingZeroes = 2 * bytes.length - unpadded.length(); + return String.join("", Collections.nCopies(numLeadingZeroes, "0")) + unpadded; + } + + /** + * Causes libFuzzer to write the current input to disk as a crashing input and emit some + * information about it to stderr. + */ + private static native void printCrashingInput(); + + /** + * Immediately terminates the process without performing any cleanup. + * + *

    Neither JVM shutdown hooks nor native exit handlers are called. This method does not return. + * + *

    This method provides a way to exit Jazzer without triggering libFuzzer's exit hook that + * prints the "fuzz target exited" error message. It should thus be preferred over + * {@link System#exit} in any situation where Jazzer encounters an error after the fuzz target has + * started running. + * + * @param exitCode the exit code + */ + private static native void _Exit(int exitCode); +} diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java new file mode 100644 index 00000000..201b8695 --- /dev/null +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java @@ -0,0 +1,148 @@ +/* + * Copyright 2022 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.driver; + +import static java.lang.System.err; +import static java.lang.System.exit; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Static options that determine the runtime behavior of the fuzzer, set via Java properties. + * + *

    Each option corresponds to a command-line argument of the driver of the same name. + * + *

    Every public field should be deeply immutable. + */ +final class Opt { + public static final String autofuzz = stringSetting("autofuzz", ""); + public static final List autofuzzIgnore = stringListSetting("autofuzz_ignore", ','); + public static final String coverageDump = stringSetting("coverage_dump", ""); + public static final String coverageReport = stringSetting("coverage_report", ""); + public static final boolean hooks = boolSetting("hooks", true); + // Default to false if hooks is false to mimic the original behavior of the native fuzz target + // runner, but still support hooks = false && dedup = true. + public static boolean dedup = boolSetting("dedup", hooks); + public static final Set ignore = + Collections.unmodifiableSet(stringListSetting("ignore", ',') + .stream() + .map(Long::parseUnsignedLong) + .collect(Collectors.toSet())); + public static final String targetClass = stringSetting("target_class", ""); + public static final String reproducerPath = stringSetting("reproducer_path", "."); + + // The values of these settings depend on autofuzz. + public static final List targetArgs = autofuzz.isEmpty() + ? stringListSetting("target_args", ' ') + : Collections.unmodifiableList( + Stream.concat(Stream.of(autofuzz), autofuzzIgnore.stream()).collect(Collectors.toList())); + public static final long keepGoing = + uint32Setting("keep_going", autofuzz.isEmpty() ? 1 : Integer.MIN_VALUE); + + static { + if (!targetClass.isEmpty() && !autofuzz.isEmpty()) { + err.println("--target_class and --autofuzz cannot be specified together"); + exit(1); + } + if (!stringListSetting("target_args", ' ').isEmpty() && !autofuzz.isEmpty()) { + err.println("--target_args and --autofuzz cannot be specified together"); + exit(1); + } + if (autofuzz.isEmpty() && !autofuzzIgnore.isEmpty()) { + err.println("--autofuzz_ignore requires --autofuzz"); + exit(1); + } + if ((!ignore.isEmpty() || keepGoing > 1) && !dedup) { + // --autofuzz implicitly sets keepGoing to Integer.MAX_VALUE. + err.println("--nodedup is not supported with --ignore, --keep_going, or --autofuzz"); + exit(1); + } + } + + private static final String optionsPrefix = "jazzer."; + + private static String stringSetting(String name, String defaultValue) { + return System.getProperty(optionsPrefix + name, defaultValue); + } + + private static List stringListSetting(String name, char separator) { + String value = System.getProperty(optionsPrefix + name); + if (value == null || value.isEmpty()) { + return Collections.emptyList(); + } + return splitOnUnescapedSeparator(value, separator); + } + + private static boolean boolSetting(String name, boolean defaultValue) { + String value = System.getProperty(optionsPrefix + name); + if (value == null) { + return defaultValue; + } + return Boolean.parseBoolean(value); + } + + private static long uint32Setting(String name, int defaultValue) { + String value = System.getProperty(optionsPrefix + name); + if (value == null) { + return defaultValue; + } + return Integer.parseUnsignedInt(value, 10); + } + + /** + * Split value into non-empty takens separated by separator. Backslashes can be used to escape + * separators (or backslashes). + * + * @param value the string to split + * @param separator a single character to split on (backslash is not allowed) + * @return an immutable list of tokens obtained by splitting value on separator + */ + static List splitOnUnescapedSeparator(String value, char separator) { + if (separator == '\\') { + throw new IllegalArgumentException("separator '\\' is not supported"); + } + ArrayList tokens = new ArrayList<>(); + StringBuilder currentToken = new StringBuilder(); + boolean inEscapeState = false; + for (int pos = 0; pos < value.length(); pos++) { + char c = value.charAt(pos); + if (inEscapeState) { + currentToken.append(c); + inEscapeState = false; + } else if (c == '\\') { + inEscapeState = true; + } else if (c == separator) { + // Do not emit empty tokens between consecutive separators. + if (currentToken.length() > 0) { + tokens.add(currentToken.toString()); + } + currentToken.setLength(0); + } else { + currentToken.append(c); + } + } + if (currentToken.length() > 0) { + tokens.add(currentToken.toString()); + } + return Collections.unmodifiableList(tokens); + } +} diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/Reproducer.java.tmpl b/driver/src/main/java/com/code_intelligence/jazzer/driver/Reproducer.java.tmpl new file mode 100644 index 00000000..d9cb1e9e --- /dev/null +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/Reproducer.java.tmpl @@ -0,0 +1,28 @@ +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class Crash_%1$s { + static final String base64Bytes = String.join("", "%2$s"); + + public static void main(String[] args) throws Throwable { + ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true); + try { + Method fuzzerInitialize = %3$s.class.getMethod("fuzzerInitialize"); + fuzzerInitialize.invoke(null); + } catch (NoSuchMethodException ignored) { + try { + Method fuzzerInitialize = %3$s.class.getMethod("fuzzerInitialize", String[].class); + fuzzerInitialize.invoke(null, (Object) args); + } catch (NoSuchMethodException ignored1) { + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + System.exit(1); + } + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + System.exit(1); + } + %4$s + %3$s.fuzzerTestOneInput(input); + } +} diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/ReproducerTemplates.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/ReproducerTemplates.java new file mode 100644 index 00000000..68042079 --- /dev/null +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/ReproducerTemplates.java @@ -0,0 +1,54 @@ +/* + * Copyright 2022 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.driver; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.stream.Collectors; + +final class ReproducerTemplates { + private static final String rawBytesInput = + "byte[] input = java.util.Base64.getDecoder().decode(base64Bytes);"; + private static final String fuzzedDataProviderInput = + "com.code_intelligence.jazzer.api.CannedFuzzedDataProvider input = new com.code_intelligence.jazzer.api.CannedFuzzedDataProvider(base64Bytes);"; + + public static void dumpReproducer( + String base64, String dataSha, String targetClass, boolean useFuzzedDataProvider) { + String targetArg = useFuzzedDataProvider ? fuzzedDataProviderInput : rawBytesInput; + String template = new BufferedReader( + new InputStreamReader(ReproducerTemplates.class.getResourceAsStream("Reproducer.java.tmpl"), + StandardCharsets.UTF_8)) + .lines() + .collect(Collectors.joining("\n")); + String javaSource = String.format(template, dataSha, base64, targetClass, targetArg); + Path javaPath = Paths.get(Opt.reproducerPath, String.format("Crash_%s.java", dataSha)); + try { + Files.write(javaPath, javaSource.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); + } catch (IOException e) { + System.err.printf("ERROR: Failed to write Java reproducer to %s%n", javaPath); + e.printStackTrace(); + } + System.out.printf( + "reproducer_path='%s'; Java reproducer written to %s%n", Opt.reproducerPath, javaPath); + } +} diff --git a/driver/src/test/java/com/code_intelligence/jazzer/driver/BUILD.bazel b/driver/src/test/java/com/code_intelligence/jazzer/driver/BUILD.bazel new file mode 100644 index 00000000..9da20d54 --- /dev/null +++ b/driver/src/test/java/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -0,0 +1,24 @@ +java_test( + name = "FuzzTargetRunnerTest", + srcs = ["FuzzTargetRunnerTest.java"], + jvm_flags = ["-ea"], + use_testrunner = False, + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/api", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:coverage_map", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:unsafe_provider", + "//agent/src/test/java/com/code_intelligence/jazzer:MockDriver", + "//driver/src/main/java/com/code_intelligence/jazzer/driver:fuzz_target_runner", + "//driver/src/test/native/com/code_intelligence/jazzer/driver:fuzz_target_runner_mock", + "@fmeum_rules_jni//jni/tools/native_loader", + ], +) + +java_test( + name = "OptTest", + srcs = ["OptTest.java"], + deps = [ + "//driver/src/main/java/com/code_intelligence/jazzer/driver:opt", + "@maven//:junit_junit", + ], +) diff --git a/driver/src/test/java/com/code_intelligence/jazzer/driver/FuzzTargetRunnerTest.java b/driver/src/test/java/com/code_intelligence/jazzer/driver/FuzzTargetRunnerTest.java new file mode 100644 index 00000000..7f6894c5 --- /dev/null +++ b/driver/src/test/java/com/code_intelligence/jazzer/driver/FuzzTargetRunnerTest.java @@ -0,0 +1,214 @@ +/* + * Copyright 2022 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.driver; + +import com.code_intelligence.jazzer.MockDriver; +import com.code_intelligence.jazzer.api.Jazzer; +import com.code_intelligence.jazzer.runtime.CoverageMap; +import com.code_intelligence.jazzer.runtime.UnsafeProvider; +import com.github.fmeum.rules_jni.RulesJni; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import sun.misc.Unsafe; + +public class FuzzTargetRunnerTest { + static { + MockDriver.load(); + RulesJni.loadLibrary("fuzz_target_runner_mock", FuzzTargetRunnerTest.class); + } + + private static final Pattern DEDUP_TOKEN_PATTERN = + Pattern.compile("(?m)^DEDUP_TOKEN: ([0-9a-f]{16})$"); + private static final Unsafe UNSAFE = UnsafeProvider.getUnsafe(); + private static final ByteArrayOutputStream recordedErr = new ByteArrayOutputStream(); + private static final ByteArrayOutputStream recordedOut = new ByteArrayOutputStream(); + private static boolean fuzzerInitializeRan = false; + private static boolean finishedAllNonCrashingRuns = false; + + public static void fuzzerInitialize() { + fuzzerInitializeRan = true; + } + + public static void fuzzerTestOneInput(byte[] data) { + switch (new String(data, StandardCharsets.UTF_8)) { + case "no crash": + CoverageMap.recordCoverage(0); + return; + case "first finding": + CoverageMap.recordCoverage(1); + throw new IllegalArgumentException("first finding"); + case "second finding": + CoverageMap.recordCoverage(2); + Jazzer.reportFindingFromHook(new StackOverflowError("second finding")); + throw new IllegalArgumentException("not reported"); + case "crash": + CoverageMap.recordCoverage(3); + throw new IllegalArgumentException("crash"); + } + } + + public static void fuzzerTearDown() { + String errOutput = new String(recordedErr.toByteArray(), StandardCharsets.UTF_8); + assert errOutput.contains("== Java Exception: java.lang.IllegalArgumentException: crash"); + String outOutput = new String(recordedOut.toByteArray(), StandardCharsets.UTF_8); + assert DEDUP_TOKEN_PATTERN.matcher(outOutput).find(); + + assert finishedAllNonCrashingRuns : "Did not finish all expected runs before crashing"; + assert CoverageMap.getCoveredIds().equals(Stream.of(0, 1, 2, 3).collect(Collectors.toSet())); + assert UNSAFE.getByte(CoverageMap.countersAddress) == 2; + assert UNSAFE.getByte(CoverageMap.countersAddress + 1) == 2; + assert UNSAFE.getByte(CoverageMap.countersAddress + 2) == 2; + assert UNSAFE.getByte(CoverageMap.countersAddress + 3) == 1; + // FuzzTargetRunner calls _Exit after this function, so the test would fail unless this line is + // executed. Use halt rather than exit to get around FuzzTargetRunner's shutdown hook calling + // fuzzerTearDown, which would otherwise result in a shutdown hook loop. + Runtime.getRuntime().halt(0); + } + + public static void main(String[] args) { + PrintStream recordingErr = new TeeOutputStream(new PrintStream(recordedErr, true), System.err); + System.setErr(recordingErr); + PrintStream recordingOut = new TeeOutputStream(new PrintStream(recordedOut, true), System.out); + System.setOut(recordingOut); + + System.setProperty("jazzer.target_class", FuzzTargetRunnerTest.class.getName()); + // Keep going past all "no crash", "first finding" and "second finding" runs, then crash. + System.setProperty("jazzer.keep_going", "3"); + + // Use a loop to simulate two findings with the same stack trace and thus verify that keep_going + // works as advertised. + for (int i = 1; i < 3; i++) { + int result = FuzzTargetRunner.runOne("no crash".getBytes(StandardCharsets.UTF_8)); + + assert result == 0; + assert !FuzzTargetRunner.useFuzzedDataProvider; + assert fuzzerInitializeRan; + assert CoverageMap.getCoveredIds().equals(Stream.of(0).collect(Collectors.toSet())); + assert UNSAFE.getByte(CoverageMap.countersAddress) == i; + assert UNSAFE.getByte(CoverageMap.countersAddress + 1) == 0; + assert UNSAFE.getByte(CoverageMap.countersAddress + 2) == 0; + assert UNSAFE.getByte(CoverageMap.countersAddress + 3) == 0; + + String errOutput = new String(recordedErr.toByteArray(), StandardCharsets.UTF_8); + assert errOutput.isEmpty(); + String outOutput = new String(recordedOut.toByteArray(), StandardCharsets.UTF_8); + assert outOutput.isEmpty(); + } + + String firstDedupToken = null; + for (int i = 1; i < 3; i++) { + int result = FuzzTargetRunner.runOne("first finding".getBytes(StandardCharsets.UTF_8)); + + assert result == 0; + assert CoverageMap.getCoveredIds().equals(Stream.of(0, 1).collect(Collectors.toSet())); + assert UNSAFE.getByte(CoverageMap.countersAddress) == 2; + assert UNSAFE.getByte(CoverageMap.countersAddress + 1) == i; + assert UNSAFE.getByte(CoverageMap.countersAddress + 2) == 0; + assert UNSAFE.getByte(CoverageMap.countersAddress + 3) == 0; + + String errOutput = new String(recordedErr.toByteArray(), StandardCharsets.UTF_8); + String outOutput = new String(recordedOut.toByteArray(), StandardCharsets.UTF_8); + if (i == 1) { + assert errOutput.contains( + "== Java Exception: java.lang.IllegalArgumentException: first finding"); + Matcher dedupTokenMatcher = DEDUP_TOKEN_PATTERN.matcher(outOutput); + assert dedupTokenMatcher.find(); + firstDedupToken = dedupTokenMatcher.group(); + recordedErr.reset(); + recordedOut.reset(); + } else { + assert errOutput.isEmpty(); + assert outOutput.isEmpty(); + } + } + + for (int i = 1; i < 3; i++) { + int result = FuzzTargetRunner.runOne("second finding".getBytes(StandardCharsets.UTF_8)); + + assert result == 0; + assert CoverageMap.getCoveredIds().equals(Stream.of(0, 1, 2).collect(Collectors.toSet())); + assert UNSAFE.getByte(CoverageMap.countersAddress) == 2; + assert UNSAFE.getByte(CoverageMap.countersAddress + 1) == 2; + assert UNSAFE.getByte(CoverageMap.countersAddress + 2) == i; + assert UNSAFE.getByte(CoverageMap.countersAddress + 3) == 0; + + String errOutput = new String(recordedErr.toByteArray(), StandardCharsets.UTF_8); + String outOutput = new String(recordedOut.toByteArray(), StandardCharsets.UTF_8); + if (i == 1) { + // Verify that the StackOverflowError is wrapped in security issue and contains reproducer + // information. + assert errOutput.contains( + "== Java Exception: com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow: Stack overflow (use "); + assert !errOutput.contains("not reported"); + Matcher dedupTokenMatcher = DEDUP_TOKEN_PATTERN.matcher(outOutput); + assert dedupTokenMatcher.find(); + assert !firstDedupToken.equals(dedupTokenMatcher.group()); + recordedErr.reset(); + recordedOut.reset(); + } else { + assert errOutput.isEmpty(); + assert outOutput.isEmpty(); + } + } + + finishedAllNonCrashingRuns = true; + + FuzzTargetRunner.runOne("crash".getBytes(StandardCharsets.UTF_8)); + + throw new IllegalStateException("Expected FuzzTargetRunner to call fuzzerTearDown"); + } + + /** + * An OutputStream that prints to two OutputStreams simultaneously. + */ + private static class TeeOutputStream extends PrintStream { + private final PrintStream otherOut; + public TeeOutputStream(PrintStream out1, PrintStream out2) { + super(out1, true); + this.otherOut = out2; + } + + @Override + public void flush() { + super.flush(); + otherOut.flush(); + } + + @Override + public void close() { + super.close(); + otherOut.close(); + } + + @Override + public void write(int b) { + super.write(b); + otherOut.write(b); + } + + @Override + public void write(byte[] buf, int off, int len) { + super.write(buf, off, len); + otherOut.write(buf, off, len); + } + } +} diff --git a/driver/src/test/java/com/code_intelligence/jazzer/driver/OptTest.java b/driver/src/test/java/com/code_intelligence/jazzer/driver/OptTest.java new file mode 100644 index 00000000..87cda2b1 --- /dev/null +++ b/driver/src/test/java/com/code_intelligence/jazzer/driver/OptTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2022 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.driver; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.stream.Collectors; +import org.junit.Test; + +public class OptTest { + @Test + public void splitString() { + assertStringSplit("", ','); + assertStringSplit(",,,,,", ','); + assertStringSplit("fir\\\\st se\\ cond third", ' ', "fir\\st", "se cond", "third"); + assertStringSplit("first ", ' ', "first"); + assertStringSplit("first\\", ' ', "first"); + } + + @Test(expected = IllegalArgumentException.class) + public void splitString_noBackslashAsSeparator() { + assertStringSplit("foo", '\\'); + } + + public void assertStringSplit(String str, char sep, String... tokens) { + assertEquals(Arrays.stream(tokens).collect(Collectors.toList()), + Opt.splitOnUnescapedSeparator(str, sep)); + } +} diff --git a/driver/src/test/native/com/code_intelligence/jazzer/driver/BUILD.bazel b/driver/src/test/native/com/code_intelligence/jazzer/driver/BUILD.bazel new file mode 100644 index 00000000..30d64fe7 --- /dev/null +++ b/driver/src/test/native/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -0,0 +1,11 @@ +load("@fmeum_rules_jni//jni:defs.bzl", "cc_jni_library") + +cc_jni_library( + name = "fuzz_target_runner_mock", + testonly = True, + srcs = ["fuzz_target_runner_mock.cpp"], + visibility = ["//driver/src/test:__subpackages__"], + deps = [ + "//driver/src/main/java/com/code_intelligence/jazzer/driver:fuzz_target_runner.hdrs", + ], +) diff --git a/driver/src/test/native/com/code_intelligence/jazzer/driver/fuzz_target_runner_mock.cpp b/driver/src/test/native/com/code_intelligence/jazzer/driver/fuzz_target_runner_mock.cpp new file mode 100644 index 00000000..d5178bf0 --- /dev/null +++ b/driver/src/test/native/com/code_intelligence/jazzer/driver/fuzz_target_runner_mock.cpp @@ -0,0 +1,25 @@ +// Copyright 2022 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 + +#include "com_code_intelligence_jazzer_driver_FuzzTargetRunner.h" + +void Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner_printCrashingInput( + JNIEnv *, jclass) {} + +void Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner__1Exit( + JNIEnv *, jclass, jint exit_code) { + _Exit(exit_code); +} diff --git a/driver/testdata/test/FuzzTargetWithCoverage.java b/driver/testdata/test/FuzzTargetWithCoverage.java deleted file mode 100644 index e85c674b..00000000 --- a/driver/testdata/test/FuzzTargetWithCoverage.java +++ /dev/null @@ -1,24 +0,0 @@ -// 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 test; - -import com.code_intelligence.jazzer.runtime.CoverageMap; - -public class FuzzTargetWithCoverage { - public static void fuzzerTestOneInput(byte[] input) { - // manually increase the first coverage counter - CoverageMap.recordCoverage(0); - } -} diff --git a/driver/testdata/test/FuzzTargetWithInit.java b/driver/testdata/test/FuzzTargetWithInit.java deleted file mode 100644 index 86aed82b..00000000 --- a/driver/testdata/test/FuzzTargetWithInit.java +++ /dev/null @@ -1,30 +0,0 @@ -// 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 test; - -class FuzzTargetWithInit { - static String[] crashOnString; - public static void fuzzerInitialize(String[] args) { - crashOnString = args; - } - public static void fuzzerTestOneInput(byte[] input) { - String inputString = new String(input); - for (String crashString : crashOnString) { - if (inputString.equals(crashString)) { - throw new RuntimeException("triggered the exception"); - } - } - } -} diff --git a/driver/testdata/test/SimpleFuzzTarget.java b/driver/testdata/test/SimpleFuzzTarget.java deleted file mode 100644 index 5657e416..00000000 --- a/driver/testdata/test/SimpleFuzzTarget.java +++ /dev/null @@ -1,25 +0,0 @@ -// 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 test; - -class SimpleFuzzTarget { - public static void fuzzerTestOneInput(byte[] input) { - String inputString = new String(input); - System.err.println("got input " + inputString); - if (inputString.startsWith("crash")) { - throw new RuntimeException("exception triggered in fuzz target"); - } - } -} -- cgit v1.2.3 From 5585339ca6a009d2e2507513b35f05682358f4ba Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 4 Aug 2022 09:57:03 +0200 Subject: driver: Extract Java feed method out of FuzzedDataProvider Simplifies the replayer and allows us to Javaify the FuzzedDataProvider test. --- .../code_intelligence/jazzer/replay/BUILD.bazel | 3 +- .../code_intelligence/jazzer/replay/Replayer.java | 7 +- .../jazzer/runtime/FuzzedDataProviderImpl.java | 5 + .../code_intelligence/jazzer/replay/BUILD.bazel | 13 -- ...om_code_intelligence_jazzer_replay_Replayer.cpp | 46 ---- .../code_intelligence/jazzer/runtime/BUILD.bazel | 12 ++ .../jazzer/runtime/FuzzedDataProviderImplTest.java | 235 +++++++++++++++++++++ driver/BUILD.bazel | 11 - driver/fuzzed_data_provider.cpp | 23 ++ driver/fuzzed_data_provider_test.cpp | 120 ----------- .../code_intelligence/jazzer/driver/BUILD.bazel | 12 ++ .../testdata/test/FuzzTargetWithDataProvider.java | 118 ----------- 12 files changed, 291 insertions(+), 314 deletions(-) delete mode 100644 agent/src/main/native/com/code_intelligence/jazzer/replay/BUILD.bazel delete mode 100644 agent/src/main/native/com/code_intelligence/jazzer/replay/com_code_intelligence_jazzer_replay_Replayer.cpp create mode 100644 agent/src/test/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImplTest.java create mode 100644 driver/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel delete mode 100644 driver/testdata/test/FuzzTargetWithDataProvider.java 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 index df28adb4..08bd7653 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/replay/BUILD.bazel +++ b/agent/src/main/java/com/code_intelligence/jazzer/replay/BUILD.bazel @@ -3,8 +3,7 @@ load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library") java_jni_library( 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__"], + native_libs = ["//driver/src/main/native/com/code_intelligence/jazzer/driver:fuzzed_data_provider_standalone"], deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/api", "//agent/src/main/java/com/code_intelligence/jazzer/runtime:fuzzed_data_provider", 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 index fc6bfc4f..ae509dad 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer.java @@ -30,7 +30,8 @@ public class Replayer { static { try { - RulesJni.loadLibrary("replay", Replayer.class); + RulesJni.loadLibrary( + "fuzzed_data_provider_standalone", "/com/code_intelligence/jazzer/driver"); } catch (Throwable t) { t.printStackTrace(); System.exit(STATUS_OTHER_ERROR); @@ -151,9 +152,7 @@ public class Replayer { } private static FuzzedDataProvider makeFuzzedDataProvider(byte[] input) { - feedFuzzedDataProvider(input); + FuzzedDataProviderImpl.feed(input); return new FuzzedDataProviderImpl(); } - - private static native void feedFuzzedDataProvider(byte[] input); } 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 262e4596..4b8ec7f2 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 @@ -34,6 +34,11 @@ public class FuzzedDataProviderImpl implements FuzzedDataProvider { // input. public static native void reset(); + // Feeds new raw fuzzer input into the provider. + // Note: Clients *must not* use this method if they also use the native FeedFuzzedDataProvider + // method. + public static native void feed(byte[] input); + @Override public native boolean consumeBoolean(); @Override public native boolean[] consumeBooleans(int maxLength); 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 deleted file mode 100644 index 6b75fb8b..00000000 --- a/agent/src/main/native/com/code_intelligence/jazzer/replay/BUILD.bazel +++ /dev/null @@ -1,13 +0,0 @@ -load("@fmeum_rules_jni//jni:defs.bzl", "cc_jni_library") - -cc_jni_library( - name = "replay", - srcs = [ - "com_code_intelligence_jazzer_replay_Replayer.cpp", - ], - visibility = ["//agent/src/main/java/com/code_intelligence/jazzer/replay:__pkg__"], - deps = [ - "//agent/src/main/java/com/code_intelligence/jazzer/replay:replay.hdrs", - "//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 deleted file mode 100644 index e481e82f..00000000 --- a/agent/src/main/native/com/code_intelligence/jazzer/replay/com_code_intelligence_jazzer_replay_Replayer.cpp +++ /dev/null @@ -1,46 +0,0 @@ -// 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 - -#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) { - 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(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(data)); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->FatalError("Failed to copy input"); - } - jazzer::FeedFuzzedDataProvider(data, size); -} diff --git a/agent/src/test/java/com/code_intelligence/jazzer/runtime/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/runtime/BUILD.bazel index ad7ddb01..4fa0df37 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/runtime/BUILD.bazel +++ b/agent/src/test/java/com/code_intelligence/jazzer/runtime/BUILD.bazel @@ -1,5 +1,17 @@ load("//bazel:compat.bzl", "SKIP_ON_WINDOWS") +java_test( + name = "FuzzedDataProviderImplTest", + srcs = ["FuzzedDataProviderImplTest.java"], + use_testrunner = False, + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/api", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:fuzzed_data_provider", + "//driver/src/main/native/com/code_intelligence/jazzer/driver:fuzzed_data_provider_standalone", + "@fmeum_rules_jni//jni/tools/native_loader", + ], +) + java_test( name = "RecordingFuzzedDataProviderTest", srcs = [ diff --git a/agent/src/test/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImplTest.java b/agent/src/test/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImplTest.java new file mode 100644 index 00000000..53f28d8b --- /dev/null +++ b/agent/src/test/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImplTest.java @@ -0,0 +1,235 @@ +// 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.runtime; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import com.github.fmeum.rules_jni.RulesJni; +import java.util.Arrays; +import java.util.stream.Collectors; + +public class FuzzedDataProviderImplTest { + static { + try { + RulesJni.loadLibrary( + "fuzzed_data_provider_standalone", "/com/code_intelligence/jazzer/driver"); + } catch (Throwable t) { + t.printStackTrace(); + System.exit(1); + } + } + + public static void main(String[] args) { + FuzzedDataProviderImpl fuzzedDataProvider = new FuzzedDataProviderImpl(); + FuzzedDataProviderImpl.feed(INPUT_BYTES); + verifyFuzzedDataProvider(fuzzedDataProvider); + } + + private strictfp static void verifyFuzzedDataProvider(FuzzedDataProvider data) { + assertEqual(true, data.consumeBoolean()); + + assertEqual((byte) 0x7F, data.consumeByte()); + assertEqual((byte) 0x14, data.consumeByte((byte) 0x12, (byte) 0x22)); + + assertEqual(0x12345678, data.consumeInt()); + assertEqual(-0x12345600, data.consumeInt(-0x12345678, -0x12345600)); + assertEqual(0x12345679, data.consumeInt(0x12345678, 0x12345679)); + + assertEqual(true, Arrays.equals(new byte[] {0x01, 0x02}, data.consumeBytes(2))); + + assertEqual("jazzer", data.consumeString(6)); + assertEqual("ja\u0000zer", data.consumeString(6)); + assertEqual("€ß", data.consumeString(2)); + + assertEqual("jazzer", data.consumeAsciiString(6)); + assertEqual("ja\u0000zer", data.consumeAsciiString(6)); + assertEqual("\u0062\u0002\u002C\u0043\u001F", data.consumeAsciiString(5)); + + assertEqual(true, + Arrays.equals(new boolean[] {false, false, true, false, true}, data.consumeBooleans(5))); + assertEqual(true, + Arrays.equals(new long[] {0x0123456789abdcefL, 0xfedcba9876543210L}, data.consumeLongs(2))); + + assertEqual((float) 0.28969181, data.consumeProbabilityFloat()); + assertEqual(0.086814121166605432, data.consumeProbabilityDouble()); + assertEqual((float) 0.30104411, data.consumeProbabilityFloat()); + assertEqual(0.96218831486039413, data.consumeProbabilityDouble()); + + assertEqual((float) -2.8546307e+38, data.consumeRegularFloat()); + assertEqual(8.0940194040236032e+307, data.consumeRegularDouble()); + assertEqual((float) 271.49084, data.consumeRegularFloat((float) 123.0, (float) 777.0)); + assertEqual(30.859126145478349, data.consumeRegularDouble(13.37, 31.337)); + + assertEqual((float) 0.0, data.consumeFloat()); + assertEqual((float) -0.0, data.consumeFloat()); + assertEqual(Float.POSITIVE_INFINITY, data.consumeFloat()); + assertEqual(Float.NEGATIVE_INFINITY, data.consumeFloat()); + assertEqual(true, Float.isNaN(data.consumeFloat())); + assertEqual(Float.MIN_VALUE, data.consumeFloat()); + assertEqual(-Float.MIN_VALUE, data.consumeFloat()); + assertEqual(Float.MIN_NORMAL, data.consumeFloat()); + assertEqual(-Float.MIN_NORMAL, data.consumeFloat()); + assertEqual(Float.MAX_VALUE, data.consumeFloat()); + assertEqual(-Float.MAX_VALUE, data.consumeFloat()); + + assertEqual(0.0, data.consumeDouble()); + assertEqual(-0.0, data.consumeDouble()); + assertEqual(Double.POSITIVE_INFINITY, data.consumeDouble()); + assertEqual(Double.NEGATIVE_INFINITY, data.consumeDouble()); + assertEqual(true, Double.isNaN(data.consumeDouble())); + assertEqual(Double.MIN_VALUE, data.consumeDouble()); + assertEqual(-Double.MIN_VALUE, data.consumeDouble()); + assertEqual(Double.MIN_NORMAL, data.consumeDouble()); + assertEqual(-Double.MIN_NORMAL, data.consumeDouble()); + assertEqual(Double.MAX_VALUE, data.consumeDouble()); + assertEqual(-Double.MAX_VALUE, data.consumeDouble()); + + int[] array = {0, 1, 2, 3, 4}; + assertEqual(4, data.pickValue(array)); + assertEqual(2, (int) data.pickValue(Arrays.stream(array).boxed().toArray())); + assertEqual(3, data.pickValue(Arrays.stream(array).boxed().collect(Collectors.toList()))); + assertEqual(2, data.pickValue(Arrays.stream(array).boxed().collect(Collectors.toSet()))); + + // Buffer is almost depleted at this point. + assertEqual(7, data.remainingBytes()); + assertEqual(true, Arrays.equals(new long[0], data.consumeLongs(3))); + assertEqual(7, data.remainingBytes()); + assertEqual(true, Arrays.equals(new int[] {0x12345678}, data.consumeInts(3))); + assertEqual(3, data.remainingBytes()); + assertEqual(0x123456L, data.consumeLong()); + + // Buffer has been fully consumed at this point + assertEqual(0, data.remainingBytes()); + assertEqual(0, data.consumeInt()); + assertEqual(0.0, data.consumeDouble()); + assertEqual(-13.37, data.consumeRegularDouble(-13.37, 31.337)); + assertEqual(true, Arrays.equals(new byte[0], data.consumeBytes(4))); + assertEqual(true, Arrays.equals(new long[0], data.consumeLongs(4))); + assertEqual("", data.consumeRemainingAsAsciiString()); + assertEqual("", data.consumeRemainingAsString()); + assertEqual("", data.consumeAsciiString(100)); + assertEqual("", data.consumeString(100)); + } + + private static > void assertEqual(T a, T b) { + if (a.compareTo(b) != 0) { + throw new IllegalArgumentException("Expected: " + a + ", got: " + b); + } + } + + private static final byte[] INPUT_BYTES = new byte[] { + // Bytes read from the start + 0x01, 0x02, // consumeBytes(2): {0x01, 0x02} + + 'j', 'a', 'z', 'z', 'e', 'r', // consumeString(6): "jazzer" + 'j', 'a', 0x00, 'z', 'e', 'r', // consumeString(6): "ja\u0000zer" + (byte) 0xE2, (byte) 0x82, (byte) 0xAC, (byte) 0xC3, (byte) 0x9F, // consumeString(2): "€ẞ" + + 'j', 'a', 'z', 'z', 'e', 'r', // consumeAsciiString(6): "jazzer" + 'j', 'a', 0x00, 'z', 'e', 'r', // consumeAsciiString(6): "ja\u0000zer" + (byte) 0xE2, (byte) 0x82, (byte) 0xAC, (byte) 0xC3, + (byte) 0x9F, // consumeAsciiString(5): "\u0062\u0002\u002C\u0043\u001F" + + 0, 0, 1, 0, 1, // consumeBooleans(5): { false, false, true, false, true } + (byte) 0xEF, (byte) 0xDC, (byte) 0xAB, (byte) 0x89, 0x67, 0x45, 0x23, 0x01, 0x10, 0x32, 0x54, + 0x76, (byte) 0x98, (byte) 0xBA, (byte) 0xDC, (byte) 0xFE, + // consumeLongs(2): { 0x0123456789ABCDEF, 0xFEDCBA9876543210 } + + 0x78, 0x56, 0x34, 0x12, // consumeInts(3): { 0x12345678 } + 0x56, 0x34, 0x12, // consumeLong(): + + // Bytes read from the end + 0x02, 0x03, 0x02, 0x04, // 4x pickValue in array with five elements + + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 10, // -max for next consumeDouble + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 9, // max for next consumeDouble + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 8, // -min for next consumeDouble + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 7, // min for next consumeDouble + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 6, // -denorm_min for next consumeDouble + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 5, // denorm_min for next consumeDouble + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 4, // NaN for next consumeDouble + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 3, // -infinity for next consumeDouble + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 2, // infinity for next consumeDouble + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 1, // -0.0 for next consumeDouble + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, 0x12, 0x34, 0x56, + 0x78, // consumed but unused by consumeDouble() + 0, // 0.0 for next consumeDouble + + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 10, // -max for next consumeFloat + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 9, // max for next consumeFloat + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 8, // -min for next consumeFloat + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 7, // min for next consumeFloat + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 6, // -denorm_min for next consumeFloat + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 5, // denorm_min for next consumeFloat + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 4, // NaN for next consumeFloat + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 3, // -infinity for next consumeFloat + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 2, // infinity for next consumeFloat + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 1, // -0.0 for next consumeFloat + 0x12, 0x34, 0x56, 0x78, (byte) 0x90, // consumed but unused by consumeFloat() + 0, // 0.0 for next consumeFloat + + (byte) 0x88, (byte) 0xAB, 0x61, (byte) 0xCB, 0x32, (byte) 0xEB, 0x30, (byte) 0xF9, + // consumeDouble(13.37, 31.337): 30.859126145478349 (small range) + 0x51, (byte) 0xF6, 0x1F, 0x3A, // consumeFloat(123.0, 777.0): 271.49084 (small range) + 0x11, 0x4D, (byte) 0xFD, 0x54, (byte) 0xD6, 0x3D, 0x43, 0x73, 0x39, + // consumeRegularDouble(): 8.0940194040236032e+307 + 0x16, (byte) 0xCF, 0x3D, 0x29, 0x4A, // consumeRegularFloat(): -2.8546307e+38 + + 0x61, (byte) 0xCB, 0x32, (byte) 0xEB, 0x30, (byte) 0xF9, 0x51, (byte) 0xF6, + // consumeProbabilityDouble(): 0.96218831486039413 + 0x1F, 0x3A, 0x11, 0x4D, // consumeProbabilityFloat(): 0.30104411 + (byte) 0xFD, 0x54, (byte) 0xD6, 0x3D, 0x43, 0x73, 0x39, 0x16, + // consumeProbabilityDouble(): 0.086814121166605432 + (byte) 0xCF, 0x3D, 0x29, 0x4A, // consumeProbabilityFloat(): 0.28969181 + + 0x01, // consumeInt(0x12345678, 0x12345679): 0x12345679 + 0x78, // consumeInt(-0x12345678, -0x12345600): -0x12345600 + 0x78, 0x56, 0x34, 0x12, // consumeInt(): 0x12345678 + + 0x02, // consumeByte(0x12, 0x22): 0x14 + 0x7F, // consumeByte(): 0x7F + + 0x01, // consumeBool(): true + }; +} diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index 118e6598..f8be51cb 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -33,7 +33,6 @@ cc_library( "fuzzed_data_provider.h", ], visibility = [ - "//agent/src/main/native/com/code_intelligence/jazzer/replay:__pkg__", "//driver/src/main/native/com/code_intelligence/jazzer/driver:__pkg__", "//driver/src/test:__subpackages__", ], @@ -338,18 +337,8 @@ cc_test( "//driver/testdata:fuzz_target_mocks_deploy.jar", ], includes = ["."], - linkopts = select({ - "@platforms//os:windows": [], - "//conditions:default": [ - # Needs to export symbols dynamically for JNI_OnLoad_jazzer_initialize - # to be found by the JVM. - "-rdynamic", - ], - }), - linkstatic = True, deps = [ ":jvm_tooling_lib", - ":sanitizer_symbols_for_tests", ":test_main", "@bazel_tools//tools/cpp/runfiles", "@googletest//:gtest", diff --git a/driver/fuzzed_data_provider.cpp b/driver/fuzzed_data_provider.cpp index 8b60fd62..ef595d2d 100644 --- a/driver/fuzzed_data_provider.cpp +++ b/driver/fuzzed_data_provider.cpp @@ -711,6 +711,29 @@ void Java_com_code_1intelligence_jazzer_runtime_FuzzedDataProviderImpl_reset( gRemainingBytes = gFuzzerInputSize; } +void Java_com_code_1intelligence_jazzer_runtime_FuzzedDataProviderImpl_feed( + JNIEnv *env, jclass, jbyteArray input) { + // This line is why this function must not be used if FeedFuzzedDataProvider + // is also called from native code. + delete[] gFuzzerInputStart; + + std::size_t size = env->GetArrayLength(input); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->FatalError("Failed to get length of input"); + } + auto *data = static_cast(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(data)); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->FatalError("Failed to copy input"); + } + jazzer::FeedFuzzedDataProvider(data, size); +} + namespace jazzer { void FeedFuzzedDataProvider(const uint8_t *data, std::size_t size) { gDataPtr = data; diff --git a/driver/fuzzed_data_provider_test.cpp b/driver/fuzzed_data_provider_test.cpp index 05656584..a402d8f1 100644 --- a/driver/fuzzed_data_provider_test.cpp +++ b/driver/fuzzed_data_provider_test.cpp @@ -17,11 +17,9 @@ #include #include #include -#include #include #include -#include "fuzz_target_runner.h" #include "gflags/gflags.h" #include "gtest/gtest.h" #include "jvm_tooling.h" @@ -131,124 +129,6 @@ class FuzzedDataProviderTest : public ::testing::Test { std::unique_ptr FuzzedDataProviderTest::jvm_ = nullptr; -// see testdata/test/FuzzTargetWithDataProvider.java for the implementation -// of the fuzz target that asserts that the correct values are received from -// the data provider. -const uint8_t kInput[] = { - // Bytes read from the start - 0x01, 0x02, // consumeBytes(2): {0x01, 0x02} - - 'j', 'a', 'z', 'z', 'e', 'r', // consumeString(6): "jazzer" - 'j', 'a', 0x00, 'z', 'e', 'r', // consumeString(6): "ja\u0000zer" - 0xE2, 0x82, 0xAC, 0xC3, 0x9F, // consumeString(2): "€ẞ" - - 'j', 'a', 'z', 'z', 'e', 'r', // consumeAsciiString(6): "jazzer" - 'j', 'a', 0x00, 'z', 'e', 'r', // consumeAsciiString(6): "ja\u0000zer" - 0xE2, 0x82, 0xAC, 0xC3, - 0x9F, // consumeAsciiString(5): "\u0062\u0002\u002C\u0043\u001F" - - false, false, true, false, - true, // consumeBooleans(5): { false, false, true, false, true } - 0xEF, 0xDC, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01, 0x10, 0x32, 0x54, 0x76, - 0x98, 0xBA, 0xDC, - 0xFE, // consumeLongs(2): { 0x0123456789ABCDEF, 0xFEDCBA9876543210 } - - 0x78, 0x56, 0x34, 0x12, // consumeInts(3): { 0x12345678 } - 0x56, 0x34, 0x12, // consumeLong(): - - // Bytes read from the end - 0x02, 0x03, 0x02, 0x04, // 4x pickValue in array with five elements - - 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, - 0x78, // consumed but unused by consumeDouble() - 10, // -max for next consumeDouble - 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, - 0x78, // consumed but unused by consumeDouble() - 9, // max for next consumeDouble - 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, - 0x78, // consumed but unused by consumeDouble() - 8, // -min for next consumeDouble - 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, - 0x78, // consumed but unused by consumeDouble() - 7, // min for next consumeDouble - 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, - 0x78, // consumed but unused by consumeDouble() - 6, // -denorm_min for next consumeDouble - 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, - 0x78, // consumed but unused by consumeDouble() - 5, // denorm_min for next consumeDouble - 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, - 0x78, // consumed but unused by consumeDouble() - 4, // NaN for next consumeDouble - 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, - 0x78, // consumed but unused by consumeDouble() - 3, // -infinity for next consumeDouble - 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, - 0x78, // consumed but unused by consumeDouble() - 2, // infinity for next consumeDouble - 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, - 0x78, // consumed but unused by consumeDouble() - 1, // -0.0 for next consumeDouble - 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, - 0x78, // consumed but unused by consumeDouble() - 0, // 0.0 for next consumeDouble - - 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat() - 10, // -max for next consumeFloat - 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat() - 9, // max for next consumeFloat - 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat() - 8, // -min for next consumeFloat - 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat() - 7, // min for next consumeFloat - 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat() - 6, // -denorm_min for next consumeFloat - 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat() - 5, // denorm_min for next consumeFloat - 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat() - 4, // NaN for next consumeFloat - 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat() - 3, // -infinity for next consumeFloat - 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat() - 2, // infinity for next consumeFloat - 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat() - 1, // -0.0 for next consumeFloat - 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat() - 0, // 0.0 for next consumeFloat - - 0x88, 0xAB, 0x61, 0xCB, 0x32, 0xEB, 0x30, - 0xF9, // consumeDouble(13.37, 31.337): 30.859126145478349 (small range) - 0x51, 0xF6, 0x1F, - 0x3A, // consumeFloat(123.0, 777.0): 271.49084 (small range) - 0x11, 0x4D, 0xFD, 0x54, 0xD6, 0x3D, 0x43, 0x73, - 0x39, // consumeRegularDouble(): 8.0940194040236032e+307 - 0x16, 0xCF, 0x3D, 0x29, 0x4A, // consumeRegularFloat(): -2.8546307e+38 - - 0x61, 0xCB, 0x32, 0xEB, 0x30, 0xF9, 0x51, - 0xF6, // consumeProbabilityDouble(): 0.96218831486039413 - 0x1F, 0x3A, 0x11, 0x4D, // consumeProbabilityFloat(): 0.30104411 - 0xFD, 0x54, 0xD6, 0x3D, 0x43, 0x73, 0x39, - 0x16, // consumeProbabilityDouble(): 0.086814121166605432 - 0xCF, 0x3D, 0x29, 0x4A, // consumeProbabilityFloat(): 0.28969181 - - 0x01, // consumeInt(0x12345678, 0x12345679): 0x12345679 - 0x78, // consumeInt(-0x12345678, -0x12345600): -0x12345600 - 0x78, 0x56, 0x34, 0x12, // consumeInt(): 0x12345678 - - 0x02, // consumeByte(0x12, 0x22): 0x14 - 0x7F, // consumeByte(): 0x7F - - 0x01, // consumeBool(): true -}; - -TEST_F(FuzzedDataProviderTest, FuzzTargetWithDataProvider) { - FLAGS_target_class = "test.FuzzTargetWithDataProvider"; - FLAGS_target_args = ""; - FuzzTargetRunner fuzz_target_runner(*jvm_); - - ASSERT_EQ(RunResult::kOk, fuzz_target_runner.Run(kInput, sizeof(kInput))); -} - constexpr std::size_t kValidModifiedUtf8NumRuns = 10000; constexpr std::size_t kValidModifiedUtf8NumBytes = 100000; constexpr uint32_t kValidModifiedUtf8Seed = 0x12345678; diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel b/driver/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel new file mode 100644 index 00000000..edb0b4c7 --- /dev/null +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -0,0 +1,12 @@ +load("@fmeum_rules_jni//jni:defs.bzl", "cc_jni_library") + +cc_jni_library( + name = "fuzzed_data_provider_standalone", + visibility = [ + "//agent/src/main/java/com/code_intelligence/jazzer/replay:__pkg__", + "//agent/src/test/java/com/code_intelligence/jazzer/runtime:__pkg__", + ], + deps = [ + "//driver:fuzzed_data_provider", + ], +) diff --git a/driver/testdata/test/FuzzTargetWithDataProvider.java b/driver/testdata/test/FuzzTargetWithDataProvider.java deleted file mode 100644 index 16b230ed..00000000 --- a/driver/testdata/test/FuzzTargetWithDataProvider.java +++ /dev/null @@ -1,118 +0,0 @@ -// 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 test; - -import com.code_intelligence.jazzer.api.FuzzedDataProvider; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.stream.Collectors; - -class FuzzTargetWithDataProvider { - static { - System.loadLibrary("jazzer_initialize"); - } - - public static > void assertEqual(T a, T b) { - if (a.compareTo(b) != 0) { - throw new IllegalArgumentException("Expected: " + a + ", got: " + b); - } - } - - public strictfp static void fuzzerTestOneInput(FuzzedDataProvider data) { - assertEqual(true, data.consumeBoolean()); - - assertEqual((byte) 0x7F, data.consumeByte()); - assertEqual((byte) 0x14, data.consumeByte((byte) 0x12, (byte) 0x22)); - - assertEqual(0x12345678, data.consumeInt()); - assertEqual(-0x12345600, data.consumeInt(-0x12345678, -0x12345600)); - assertEqual(0x12345679, data.consumeInt(0x12345678, 0x12345679)); - - assertEqual(true, Arrays.equals(new byte[] {0x01, 0x02}, data.consumeBytes(2))); - - assertEqual("jazzer", data.consumeString(6)); - assertEqual("ja\u0000zer", data.consumeString(6)); - assertEqual("€ß", data.consumeString(2)); - - assertEqual("jazzer", data.consumeAsciiString(6)); - assertEqual("ja\u0000zer", data.consumeAsciiString(6)); - assertEqual("\u0062\u0002\u002C\u0043\u001F", data.consumeAsciiString(5)); - - assertEqual(true, - Arrays.equals(new boolean[] {false, false, true, false, true}, data.consumeBooleans(5))); - assertEqual(true, - Arrays.equals(new long[] {0x0123456789abdcefL, 0xfedcba9876543210L}, data.consumeLongs(2))); - - assertEqual((float) 0.28969181, data.consumeProbabilityFloat()); - assertEqual(0.086814121166605432, data.consumeProbabilityDouble()); - assertEqual((float) 0.30104411, data.consumeProbabilityFloat()); - assertEqual(0.96218831486039413, data.consumeProbabilityDouble()); - - assertEqual((float) -2.8546307e+38, data.consumeRegularFloat()); - assertEqual(8.0940194040236032e+307, data.consumeRegularDouble()); - assertEqual((float) 271.49084, data.consumeRegularFloat((float) 123.0, (float) 777.0)); - assertEqual(30.859126145478349, data.consumeRegularDouble(13.37, 31.337)); - - assertEqual((float) 0.0, data.consumeFloat()); - assertEqual((float) -0.0, data.consumeFloat()); - assertEqual(Float.POSITIVE_INFINITY, data.consumeFloat()); - assertEqual(Float.NEGATIVE_INFINITY, data.consumeFloat()); - assertEqual(true, Float.isNaN(data.consumeFloat())); - assertEqual(Float.MIN_VALUE, data.consumeFloat()); - assertEqual(-Float.MIN_VALUE, data.consumeFloat()); - assertEqual(Float.MIN_NORMAL, data.consumeFloat()); - assertEqual(-Float.MIN_NORMAL, data.consumeFloat()); - assertEqual(Float.MAX_VALUE, data.consumeFloat()); - assertEqual(-Float.MAX_VALUE, data.consumeFloat()); - - assertEqual(0.0, data.consumeDouble()); - assertEqual(-0.0, data.consumeDouble()); - assertEqual(Double.POSITIVE_INFINITY, data.consumeDouble()); - assertEqual(Double.NEGATIVE_INFINITY, data.consumeDouble()); - assertEqual(true, Double.isNaN(data.consumeDouble())); - assertEqual(Double.MIN_VALUE, data.consumeDouble()); - assertEqual(-Double.MIN_VALUE, data.consumeDouble()); - assertEqual(Double.MIN_NORMAL, data.consumeDouble()); - assertEqual(-Double.MIN_NORMAL, data.consumeDouble()); - assertEqual(Double.MAX_VALUE, data.consumeDouble()); - assertEqual(-Double.MAX_VALUE, data.consumeDouble()); - - int[] array = {0, 1, 2, 3, 4}; - assertEqual(4, data.pickValue(array)); - assertEqual(2, (int) data.pickValue(Arrays.stream(array).boxed().toArray())); - assertEqual(3, data.pickValue(Arrays.stream(array).boxed().collect(Collectors.toList()))); - assertEqual(2, data.pickValue(Arrays.stream(array).boxed().collect(Collectors.toSet()))); - - // Buffer is almost depleted at this point. - assertEqual(7, data.remainingBytes()); - assertEqual(true, Arrays.equals(new long[0], data.consumeLongs(3))); - assertEqual(7, data.remainingBytes()); - assertEqual(true, Arrays.equals(new int[] {0x12345678}, data.consumeInts(3))); - assertEqual(3, data.remainingBytes()); - assertEqual(0x123456L, data.consumeLong()); - - // Buffer has been fully consumed at this point - assertEqual(0, data.remainingBytes()); - assertEqual(0, data.consumeInt()); - assertEqual(0.0, data.consumeDouble()); - assertEqual(-13.37, data.consumeRegularDouble(-13.37, 31.337)); - assertEqual(true, Arrays.equals(new byte[0], data.consumeBytes(4))); - assertEqual(true, Arrays.equals(new long[0], data.consumeLongs(4))); - assertEqual("", data.consumeRemainingAsAsciiString()); - assertEqual("", data.consumeRemainingAsString()); - assertEqual("", data.consumeAsciiString(100)); - assertEqual("", data.consumeString(100)); - } -} -- cgit v1.2.3 From 33bfc7db10bb7adbf3dfc944cbb3711f19bf181b Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 4 Aug 2022 11:28:06 +0200 Subject: all: Mark JNI and fuzz target functions as [[maybe_unused]] --- .../code_intelligence/jazzer/runtime/fuzzer_callbacks.cpp | 5 ++++- driver/fuzzed_data_provider.cpp | 9 ++++++--- driver/libfuzzer_fuzz_target.cpp | 15 +++++++++------ driver/sanitizer_symbols.cpp | 4 ++-- driver/signal_handler.cpp | 3 ++- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/fuzzer_callbacks.cpp b/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/fuzzer_callbacks.cpp index 689a53d6..718a3924 100644 --- a/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/fuzzer_callbacks.cpp +++ b/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/fuzzer_callbacks.cpp @@ -43,7 +43,10 @@ void __sanitizer_cov_trace_div8(uint64_t val); void __sanitizer_cov_trace_gep(uintptr_t idx); // Not called but required to link against libFuzzer. -int LLVMFuzzerTestOneInput(const uint8_t *data, std::size_t size) { return 0; } +[[maybe_unused]] int LLVMFuzzerTestOneInput(const uint8_t *data, + std::size_t size) { + return 0; +} } inline __attribute__((always_inline)) void *idToPc(jint id) { diff --git a/driver/fuzzed_data_provider.cpp b/driver/fuzzed_data_provider.cpp index ef595d2d..f4956ac7 100644 --- a/driver/fuzzed_data_provider.cpp +++ b/driver/fuzzed_data_provider.cpp @@ -700,18 +700,21 @@ const jint kNumFuzzedDataMethods = sizeof(kFuzzedDataMethods) / sizeof(kFuzzedDataMethods[0]); } // namespace -void Java_com_code_1intelligence_jazzer_runtime_FuzzedDataProviderImpl_nativeInit( +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_FuzzedDataProviderImpl_nativeInit( JNIEnv *env, jclass clazz) { env->RegisterNatives(clazz, kFuzzedDataMethods, kNumFuzzedDataMethods); } -void Java_com_code_1intelligence_jazzer_runtime_FuzzedDataProviderImpl_reset( +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_FuzzedDataProviderImpl_reset( JNIEnv *env, jclass clazz) { gDataPtr = gFuzzerInputStart; gRemainingBytes = gFuzzerInputSize; } -void Java_com_code_1intelligence_jazzer_runtime_FuzzedDataProviderImpl_feed( +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_FuzzedDataProviderImpl_feed( JNIEnv *env, jclass, jbyteArray input) { // This line is why this function must not be used if FeedFuzzedDataProvider // is also called from native code. diff --git a/driver/libfuzzer_fuzz_target.cpp b/driver/libfuzzer_fuzz_target.cpp index 15c7d358..58380a24 100644 --- a/driver/libfuzzer_fuzz_target.cpp +++ b/driver/libfuzzer_fuzz_target.cpp @@ -22,7 +22,7 @@ bool is_asan_active = false; } extern "C" { -const char *__asan_default_options() { +[[maybe_unused]] const char *__asan_default_options() { is_asan_active = true; // LeakSanitizer is not yet supported as it reports too many false positives // due to how the JVM GC works. @@ -32,7 +32,7 @@ const char *__asan_default_options() { return "abort_on_error=0,detect_leaks=0,exitcode=76"; } -const char *__ubsan_default_options() { +[[maybe_unused]] const char *__ubsan_default_options() { // We use a distinguished exit code to recognize UBSan crashes in tests. // Also specify abort_on_error=0 explicitly since UBSan aborts rather than // exits on macOS by default, which would cause our exit code to be ignored. @@ -74,19 +74,21 @@ extern "C" [[maybe_unused]] void __jazzer_set_death_callback( }); } -void Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner_printCrashingInput( +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner_printCrashingInput( JNIEnv *, jclass) { jazzer::AbstractLibfuzzerDriver::libfuzzer_print_crashing_input_(); } -void Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner__1Exit( +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner__1Exit( JNIEnv *, jclass, jint exit_code) { _Exit(exit_code); } // Entry point called by libfuzzer before any LLVMFuzzerTestOneInput(...) // invocations. -extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) { +extern "C" [[maybe_unused]] int LLVMFuzzerInitialize(int *argc, char ***argv) { if (is_asan_active) { std::cerr << "WARN: Jazzer is not compatible with LeakSanitizer yet. Leaks " "are not reported." @@ -98,6 +100,7 @@ extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) { } // Called by the fuzzer for every fuzzing input. -extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, const size_t size) { +extern "C" [[maybe_unused]] int LLVMFuzzerTestOneInput(const uint8_t *data, + const size_t size) { return gLibfuzzerDriver->TestOneInput(data, size); } diff --git a/driver/sanitizer_symbols.cpp b/driver/sanitizer_symbols.cpp index 10255ef1..c009fcbe 100644 --- a/driver/sanitizer_symbols.cpp +++ b/driver/sanitizer_symbols.cpp @@ -17,13 +17,13 @@ extern "C" void __sanitizer_set_death_callback(void (*)()) {} // Suppress libFuzzer warnings about missing sanitizer methods in non-sanitizer // builds. -extern "C" int __sanitizer_acquire_crash_state() { return 1; } +extern "C" [[maybe_unused]] int __sanitizer_acquire_crash_state() { return 1; } namespace jazzer { void DumpJvmStackTraces(); } // Dump a JVM stack trace on timeouts. -extern "C" void __sanitizer_print_stack_trace() { +extern "C" [[maybe_unused]] void __sanitizer_print_stack_trace() { jazzer::DumpJvmStackTraces(); } diff --git a/driver/signal_handler.cpp b/driver/signal_handler.cpp index 0b984978..2600a53a 100644 --- a/driver/signal_handler.cpp +++ b/driver/signal_handler.cpp @@ -26,7 +26,8 @@ #endif // Handles SIGINT raised while running Java code. -void Java_com_code_1intelligence_jazzer_runtime_SignalHandler_handleInterrupt( +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_SignalHandler_handleInterrupt( JNIEnv *, jclass) { static std::atomic already_exiting{false}; if (!already_exiting.exchange(true)) { -- cgit v1.2.3 From fb7cb0c484f2158754c95c320b91c30d1b70352d Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 4 Aug 2022 11:24:37 +0200 Subject: driver: Remove now unused code Test uses of JVM methods have been replaced with the equivalent standard JNI function. --- driver/BUILD.bazel | 22 +- driver/coverage_tracker.cpp | 78 ------- driver/coverage_tracker.h | 8 - driver/fuzz_target_runner.cpp | 401 ----------------------------------- driver/fuzz_target_runner.h | 52 ----- driver/fuzzed_data_provider_test.cpp | 10 +- driver/java_reproducer.cpp | 79 ------- driver/java_reproducer.h | 32 --- driver/java_reproducer_templates.h | 58 ----- driver/jvm_tooling.cpp | 167 +-------------- driver/jvm_tooling.h | 41 ---- driver/jvm_tooling_test.cpp | 61 +----- driver/libfuzzer_driver.cpp | 2 +- driver/libfuzzer_driver.h | 7 - driver/testdata/BUILD.bazel | 5 - 15 files changed, 17 insertions(+), 1006 deletions(-) delete mode 100644 driver/java_reproducer.cpp delete mode 100644 driver/java_reproducer.h delete mode 100644 driver/java_reproducer_templates.h diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index f8be51cb..cec70d25 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -125,17 +125,13 @@ cc_library( name = "jvm_tooling_lib", srcs = [ "fuzz_target_runner.cpp", - "java_reproducer.cpp", - "java_reproducer.h", - "java_reproducer_templates.h", + "fuzz_target_runner.h", "jvm_tooling.cpp", "libfuzzer_driver.cpp", "utils.cpp", "utils.h", ], hdrs = [ - "fuzz_target_runner.h", - "fuzzed_data_provider.h", "jvm_tooling.h", "libfuzzer_driver.h", ], @@ -146,12 +142,9 @@ cc_library( # Should be built through the cc_17_library driver_lib. "manual", ], - visibility = ["//visibility:public"], deps = [ - ":coverage_tracker", ":fuzzed_data_provider", ":libfuzzer_callbacks", - ":signal_handler", "@bazel_tools//tools/cpp/runfiles", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", @@ -168,7 +161,9 @@ cc_17_library( ], linkstatic = True, deps = [ + ":coverage_tracker", ":jvm_tooling_lib", + ":libfuzzer_callbacks", "//driver/src/main/java/com/code_intelligence/jazzer/driver:fuzz_target_runner.hdrs", "@jazzer_libfuzzer//:libFuzzer", ], @@ -306,18 +301,8 @@ cc_test( "//driver/testdata:fuzz_target_mocks_deploy.jar", ], includes = ["."], - linkopts = select({ - "@platforms//os:windows": [], - "//conditions:default": [ - # Needs to export symbols dynamically for JNI_OnLoad_jazzer_initialize - # to be found by the JVM. - "-rdynamic", - ], - }), deps = [ - ":coverage_tracker", ":jvm_tooling_lib", - ":sanitizer_symbols_for_tests", ":test_main", "@bazel_tools//tools/cpp/runfiles", "@googletest//:gtest", @@ -338,6 +323,7 @@ cc_test( ], includes = ["."], deps = [ + ":fuzzed_data_provider", ":jvm_tooling_lib", ":test_main", "@bazel_tools//tools/cpp/runfiles", diff --git a/driver/coverage_tracker.cpp b/driver/coverage_tracker.cpp index fffab506..47872fa9 100644 --- a/driver/coverage_tracker.cpp +++ b/driver/coverage_tracker.cpp @@ -29,9 +29,6 @@ extern "C" void __sanitizer_cov_pcs_init(const uintptr_t *pcs_beg, const uintptr_t *pcs_end); extern "C" size_t __sanitizer_cov_get_observed_pcs(uintptr_t **pc_entries); -constexpr auto kCoverageRecorderClass = - "com/code_intelligence/jazzer/instrumentor/CoverageRecorder"; - namespace { void AssertNoException(JNIEnv &env) { if (env.ExceptionCheck()) { @@ -86,81 +83,6 @@ void CoverageTracker::RegisterNewCounters(JNIEnv &env, jint old_num_counters, __sanitizer_cov_pcs_init((uintptr_t *)(pc_entries_), (uintptr_t *)(pc_entries_ + diff_num_counters)); } - -uint8_t *CoverageTracker::GetCoverageCounters() { return counters_; } - -void CoverageTracker::RecordInitialCoverage(JNIEnv &env) { - jclass coverage_recorder = env.FindClass(kCoverageRecorderClass); - AssertNoException(env); - jmethodID coverage_recorder_update_covered_ids_with_coverage_map = - env.GetStaticMethodID(coverage_recorder, - "updateCoveredIdsWithCoverageMap", "()V"); - AssertNoException(env); - env.CallStaticVoidMethod( - coverage_recorder, - coverage_recorder_update_covered_ids_with_coverage_map); - AssertNoException(env); -} - -void CoverageTracker::ReplayInitialCoverage(JNIEnv &env) { - jclass coverage_recorder = env.FindClass(kCoverageRecorderClass); - AssertNoException(env); - jmethodID coverage_recorder_update_covered_ids_with_coverage_map = - env.GetStaticMethodID(coverage_recorder, "replayCoveredIds", "()V"); - AssertNoException(env); - env.CallStaticVoidMethod( - coverage_recorder, - coverage_recorder_update_covered_ids_with_coverage_map); - AssertNoException(env); -} - -void CoverageTracker::ReportCoverage(JNIEnv &env, std::string report_file) { - uintptr_t *covered_pcs; - size_t num_covered_pcs = __sanitizer_cov_get_observed_pcs(&covered_pcs); - std::vector covered_edge_ids(covered_pcs, - covered_pcs + num_covered_pcs); - delete[] covered_pcs; - - jclass coverage_recorder = env.FindClass(kCoverageRecorderClass); - AssertNoException(env); - jmethodID coverage_recorder_dump_coverage_report = env.GetStaticMethodID( - coverage_recorder, "dumpCoverageReport", "([ILjava/lang/String;)V"); - AssertNoException(env); - jintArray covered_edge_ids_jni = env.NewIntArray(num_covered_pcs); - AssertNoException(env); - env.SetIntArrayRegion(covered_edge_ids_jni, 0, num_covered_pcs, - covered_edge_ids.data()); - AssertNoException(env); - jstring report_file_str = env.NewStringUTF(report_file.c_str()); - env.CallStaticVoidMethod(coverage_recorder, - coverage_recorder_dump_coverage_report, - covered_edge_ids_jni, report_file_str); - AssertNoException(env); -} - -void CoverageTracker::DumpCoverage(JNIEnv &env, std::string dump_file) { - uintptr_t *covered_pcs; - size_t num_covered_pcs = __sanitizer_cov_get_observed_pcs(&covered_pcs); - std::vector covered_edge_ids(covered_pcs, - covered_pcs + num_covered_pcs); - delete[] covered_pcs; - - jclass coverage_recorder = env.FindClass(kCoverageRecorderClass); - AssertNoException(env); - jmethodID coverage_recorder_dump_jacoco_coverage = env.GetStaticMethodID( - coverage_recorder, "dumpJacocoCoverage", "([ILjava/lang/String;)V"); - AssertNoException(env); - jintArray covered_edge_ids_jni = env.NewIntArray(num_covered_pcs); - AssertNoException(env); - env.SetIntArrayRegion(covered_edge_ids_jni, 0, num_covered_pcs, - covered_edge_ids.data()); - AssertNoException(env); - jstring dump_file_str = env.NewStringUTF(dump_file.c_str()); - env.CallStaticVoidMethod(coverage_recorder, - coverage_recorder_dump_jacoco_coverage, - covered_edge_ids_jni, dump_file_str); - AssertNoException(env); -} } // namespace jazzer [[maybe_unused]] void diff --git a/driver/coverage_tracker.h b/driver/coverage_tracker.h index 455fe15e..8cceceed 100644 --- a/driver/coverage_tracker.h +++ b/driver/coverage_tracker.h @@ -38,13 +38,5 @@ class CoverageTracker { static void Initialize(JNIEnv &env, jlong counters); static void RegisterNewCounters(JNIEnv &env, jint old_num_counters, jint new_num_counters); - - // Returns the address of the coverage counters array. - static uint8_t *GetCoverageCounters(); - - static void RecordInitialCoverage(JNIEnv &env); - static void ReplayInitialCoverage(JNIEnv &env); - static void ReportCoverage(JNIEnv &env, std::string); - static void DumpCoverage(JNIEnv &env, std::string); }; } // namespace jazzer diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index a72884d2..adf15fee 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -14,28 +14,11 @@ #include "fuzz_target_runner.h" -#include - -#include -#include -#include -#include #include #include -#include "absl/strings/escaping.h" -#include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" -#include "absl/strings/str_replace.h" -#include "absl/strings/str_split.h" -#include "absl/strings/substitute.h" -#include "coverage_tracker.h" -#include "fuzzed_data_provider.h" #include "gflags/gflags.h" -#include "glog/logging.h" -#include "java_reproducer.h" -#include "java_reproducer_templates.h" -#include "utils.h" DEFINE_string( target_class, "", @@ -77,18 +60,6 @@ DEFINE_string(autofuzz_ignore, "", DECLARE_bool(hooks); -constexpr auto kManifestUtilsClass = - "com/code_intelligence/jazzer/runtime/ManifestUtils"; -constexpr auto kJazzerClass = - "com/code_intelligence/jazzer/runtime/JazzerInternal"; -constexpr auto kAutofuzzFuzzTargetClass = - "com/code_intelligence/jazzer/autofuzz/FuzzTarget"; - -// A constant pool CONSTANT_Utf8_info entry should be able to hold data of size -// uint16, but somehow this does not seem to be the case and leads to invalid -// code crash reproducer code. Reducing the size by one resolves the problem. -constexpr auto dataChunkMaxLength = std::numeric_limits::max() - 1; - namespace jazzer { std::vector fuzzTargetRunnerFlagsAsDefines() { return { @@ -105,376 +76,4 @@ std::vector fuzzTargetRunnerFlagsAsDefines() { absl::StrFormat("-Djazzer.hooks=%s", FLAGS_hooks ? "true" : "false"), }; } - -// split a string on unescaped spaces -std::vector splitOnSpace(const std::string &s) { - if (s.empty()) { - return {}; - } - - std::vector tokens; - std::size_t token_begin = 0; - for (std::size_t i = 1; i < s.size() - 1; i++) { - // only split if the space is not escaped by a backslash "\" - if (s[i] == ' ' && s[i - 1] != '\\') { - // don't split on multiple spaces - if (i > token_begin + 1) - tokens.push_back(s.substr(token_begin, i - token_begin)); - token_begin = i + 1; - } - } - tokens.push_back(s.substr(token_begin)); - return tokens; -} - -FuzzTargetRunner::FuzzTargetRunner(JVM &jvm) - : ExceptionPrinter(jvm), jvm_(jvm), ignore_tokens_() { - auto &env = jvm.GetEnv(); - if (!FLAGS_target_class.empty() && !FLAGS_autofuzz.empty()) { - std::cerr << "--target_class and --autofuzz cannot be specified together" - << std::endl; - exit(1); - } - if (!FLAGS_target_args.empty() && !FLAGS_autofuzz.empty()) { - std::cerr << "--target_args and --autofuzz cannot be specified together" - << std::endl; - exit(1); - } - if (FLAGS_autofuzz.empty() && !FLAGS_autofuzz_ignore.empty()) { - std::cerr << "--autofuzz_ignore requires --autofuzz" << std::endl; - exit(1); - } - if (FLAGS_target_class.empty() && FLAGS_autofuzz.empty()) { - FLAGS_target_class = DetectFuzzTargetClass(); - } - // If automatically detecting the fuzz target class failed, we expect it as - // the value of the --target_class argument. - if (FLAGS_target_class.empty() && FLAGS_autofuzz.empty()) { - std::cerr << "Missing argument --target_class=" - << std::endl; - exit(1); - } - if (!FLAGS_autofuzz.empty()) { - FLAGS_target_class = kAutofuzzFuzzTargetClass; - if (FLAGS_keep_going == 0) { - FLAGS_keep_going = std::numeric_limits::max(); - } - // Pass the method reference string as the first argument to the generic - // autofuzz fuzz target. Subseqeuent arguments are interpreted as exception - // class names that should be ignored. - FLAGS_target_args = FLAGS_autofuzz; - if (!FLAGS_autofuzz_ignore.empty()) { - FLAGS_target_args = absl::StrCat( - FLAGS_target_args, " ", - absl::StrReplaceAll(FLAGS_autofuzz_ignore, {{",", " "}})); - } - } - // Set --keep_going to its real default. - if (FLAGS_keep_going == 0) { - FLAGS_keep_going = 1; - } - if ((!FLAGS_ignore.empty() || FLAGS_keep_going > 1) && !FLAGS_dedup) { - std::cerr << "--nodedup is not supported with --ignore or --keep_going" - << std::endl; - exit(1); - } - jazzer_ = jvm.FindClass(kJazzerClass); - last_finding_ = - env.GetStaticFieldID(jazzer_, "lastFinding", "Ljava/lang/Throwable;"); - - // Inform the agent about the fuzz target class. - // Important note: This has to be done *before* - // jvm.FindClass(FLAGS_target_class) so that hooks can enable themselves in - // time for the fuzz target's static initializer. - auto on_fuzz_target_ready = jvm.GetStaticMethodID( - jazzer_, "onFuzzTargetReady", "(Ljava/lang/String;)V", true); - jstring fuzz_target_class = env.NewStringUTF(FLAGS_target_class.c_str()); - env.CallStaticVoidMethod(jazzer_, on_fuzz_target_ready, fuzz_target_class); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - return; - } - env.DeleteLocalRef(fuzz_target_class); - - try { - jclass_ = jvm.FindClass(FLAGS_target_class); - } catch (const std::runtime_error &error) { - std::cerr << "ERROR: " << error.what() << std::endl; - exit(1); - } - // one of the following functions is required: - // public static void fuzzerTestOneInput(byte[] input) - // public static void fuzzerTestOneInput(FuzzedDataProvider data) - fuzzer_test_one_input_bytes_ = - jvm.GetStaticMethodID(jclass_, "fuzzerTestOneInput", "([B)V", false); - fuzzer_test_one_input_data_ = jvm.GetStaticMethodID( - jclass_, "fuzzerTestOneInput", - "(Lcom/code_intelligence/jazzer/api/FuzzedDataProvider;)V", false); - bool using_bytes = fuzzer_test_one_input_bytes_ != nullptr; - bool using_data = fuzzer_test_one_input_data_ != nullptr; - // Fail if none ore both of the two possible fuzzerTestOneInput versions is - // defined in the class. - if (using_bytes == using_data) { - LOG(ERROR) << FLAGS_target_class - << " must define exactly one of the following two functions:"; - LOG(ERROR) << "public static void fuzzerTestOneInput(byte[] ...)"; - LOG(ERROR) - << "public static void fuzzerTestOneInput(FuzzedDataProvider ...)"; - LOG(ERROR) << "Note: Fuzz targets returning boolean are no longer " - "supported; exceptions should be thrown instead of " - "returning true."; - exit(1); - } - - // check existence of optional methods for initialization and destruction - fuzzer_initialize_ = - jvm.GetStaticMethodID(jclass_, "fuzzerInitialize", "()V", false); - fuzzer_tear_down_ = - jvm.GetStaticMethodID(jclass_, "fuzzerTearDown", "()V", false); - fuzzer_initialize_with_args_ = jvm.GetStaticMethodID( - jclass_, "fuzzerInitialize", "([Ljava/lang/String;)V", false); - - auto fuzz_target_args_tokens = splitOnSpace(FLAGS_target_args); - - if (fuzzer_initialize_with_args_) { - // fuzzerInitialize with arguments gets priority - jclass string_class = jvm.FindClass("java/lang/String"); - jobjectArray arg_array = jvm.GetEnv().NewObjectArray( - fuzz_target_args_tokens.size(), string_class, nullptr); - for (jint i = 0; i < fuzz_target_args_tokens.size(); i++) { - jstring str = env.NewStringUTF(fuzz_target_args_tokens[i].c_str()); - env.SetObjectArrayElement(arg_array, i, str); - } - env.CallStaticVoidMethod(jclass_, fuzzer_initialize_with_args_, arg_array); - } else if (fuzzer_initialize_) { - env.CallStaticVoidMethod(jclass_, fuzzer_initialize_); - } else { - LOG(INFO) << "did not call any fuzz target initialize functions"; - } - - if (jthrowable exception = env.ExceptionOccurred()) { - env.ExceptionClear(); - LOG(ERROR) << "== Java Exception in fuzzerInitialize: "; - LOG(ERROR) << getStackTrace(exception); - std::exit(1); - } - - if (FLAGS_hooks) { - CoverageTracker::RecordInitialCoverage(env); - } - - // Parse a comma-separated list of hex dedup tokens. - std::vector str_ignore_tokens = - absl::StrSplit(FLAGS_ignore, ','); - for (const std::string &str_token : str_ignore_tokens) { - if (str_token.empty()) continue; - try { - ignore_tokens_.push_back(std::stoull(str_token, nullptr, 16)); - } catch (...) { - LOG(ERROR) << "Invalid dedup token (expected up to 16 hex digits): '" - << str_token << "'"; - // Don't let libFuzzer print a crash stack trace. - _Exit(1); - } - } -} - -FuzzTargetRunner::~FuzzTargetRunner() { - auto &env = jvm_.GetEnv(); - if (FLAGS_hooks && !FLAGS_coverage_report.empty()) { - CoverageTracker::ReportCoverage(env, FLAGS_coverage_report); - } - if (FLAGS_hooks && !FLAGS_coverage_dump.empty()) { - CoverageTracker::DumpCoverage(env, FLAGS_coverage_dump); - } - if (fuzzer_tear_down_ != nullptr) { - std::cerr << "calling fuzzer teardown function" << std::endl; - env.CallStaticVoidMethod(jclass_, fuzzer_tear_down_); - if (jthrowable exception = env.ExceptionOccurred()) { - env.ExceptionClear(); - std::cerr << getStackTrace(exception) << std::endl; - _Exit(1); - } - } -} - -RunResult FuzzTargetRunner::Run(const uint8_t *data, const std::size_t size) { - auto &env = jvm_.GetEnv(); - static std::size_t run_count = 0; - if (run_count < 2 && FLAGS_hooks) { - run_count++; - // For the first two runs only, replay the coverage recorded from static - // initializers. libFuzzer cleared the coverage map after they ran and could - // fail to see any coverage, triggering an early exit, if we don't replay it - // here. - // https://github.com/llvm/llvm-project/blob/957a5e987444d3193575d6ad8afe6c75da00d794/compiler-rt/lib/fuzzer/FuzzerLoop.cpp#L804-L809 - CoverageTracker::ReplayInitialCoverage(env); - } - if (fuzzer_test_one_input_data_ != nullptr) { - FeedFuzzedDataProvider(data, size); - env.CallStaticVoidMethod(jclass_, fuzzer_test_one_input_data_, - GetFuzzedDataProviderJavaObject(jvm_)); - } else { - jbyteArray byte_array = env.NewByteArray(size); - if (byte_array == nullptr) { - env.ExceptionDescribe(); - throw std::runtime_error(std::string("Cannot create byte array")); - } - env.SetByteArrayRegion(byte_array, 0, size, - reinterpret_cast(data)); - env.CallStaticVoidMethod(jclass_, fuzzer_test_one_input_bytes_, byte_array); - env.DeleteLocalRef(byte_array); - } - - const auto finding = GetFinding(); - if (finding != nullptr) { - jlong dedup_token = computeDedupToken(finding); - // Check whether this stack trace has been encountered before if - // `--keep_going` has been supplied. - if (dedup_token != 0 && FLAGS_keep_going > 1 && - std::find(ignore_tokens_.cbegin(), ignore_tokens_.cend(), - dedup_token) != ignore_tokens_.end()) { - env.DeleteLocalRef(finding); - return RunResult::kOk; - } else { - ignore_tokens_.push_back(dedup_token); - std::cout << std::endl; - std::cerr << "== Java Exception: " << getStackTrace(finding); - env.DeleteLocalRef(finding); - if (FLAGS_dedup) { - std::cout << "DEDUP_TOKEN: " << std::hex << std::setfill('0') - << std::setw(16) << dedup_token << std::endl; - } - if (ignore_tokens_.size() < static_cast(FLAGS_keep_going)) { - return RunResult::kDumpAndContinue; - } else { - return RunResult::kException; - } - } - } - return RunResult::kOk; -} - -// Returns a fuzzer finding as a Throwable (or nullptr if there is none), -// clearing any JVM exceptions in the process. -jthrowable FuzzTargetRunner::GetFinding() const { - auto &env = jvm_.GetEnv(); - jthrowable unprocessed_finding = nullptr; - if (env.ExceptionCheck()) { - unprocessed_finding = env.ExceptionOccurred(); - env.ExceptionClear(); - } - // Explicitly reported findings take precedence over uncaught exceptions. - if (auto reported_finding = - (jthrowable)env.GetStaticObjectField(jazzer_, last_finding_); - reported_finding != nullptr) { - env.DeleteLocalRef(unprocessed_finding); - unprocessed_finding = reported_finding; - env.SetStaticObjectField(jazzer_, last_finding_, nullptr); - } - jthrowable processed_finding = preprocessException(unprocessed_finding); - // If preprocessException returns the same local reference that we passed to - // it, we must not decrease the reference count as the returned object will - // outlive this method. Otherwise, we have to delete it to prevent leaking the - // unprocessed exception. - if (processed_finding != unprocessed_finding) { - env.DeleteLocalRef(unprocessed_finding); - } - return processed_finding; -} - -void FuzzTargetRunner::DumpReproducer(const uint8_t *data, std::size_t size) { - auto &env = jvm_.GetEnv(); - std::string data_sha1 = jazzer::Sha1Hash(data, size); - if (!FLAGS_autofuzz.empty()) { - auto autofuzz_fuzz_target_class = env.FindClass(kAutofuzzFuzzTargetClass); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - return; - } - auto dump_reproducer = env.GetStaticMethodID( - autofuzz_fuzz_target_class, "dumpReproducer", - "(Lcom/code_intelligence/jazzer/api/FuzzedDataProvider;Ljava/lang/" - "String;Ljava/lang/String;)V"); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - return; - } - FeedFuzzedDataProvider(data, size); - auto reproducer_path_jni = env.NewStringUTF(FLAGS_reproducer_path.c_str()); - auto data_sha1_jni = env.NewStringUTF(data_sha1.c_str()); - env.CallStaticVoidMethod(autofuzz_fuzz_target_class, dump_reproducer, - GetFuzzedDataProviderJavaObject(jvm_), - reproducer_path_jni, data_sha1_jni); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - return; - } - env.DeleteLocalRef(data_sha1_jni); - env.DeleteLocalRef(reproducer_path_jni); - return; - } - std::string base64_data; - if (fuzzer_test_one_input_data_) { - // Record the data retrieved from the FuzzedDataProvider and supply it to a - // Java-only CannedFuzzedDataProvider in the reproducer. - FeedFuzzedDataProvider(data, size); - jobject recorder = GetRecordingFuzzedDataProviderJavaObject(jvm_); - env.CallStaticVoidMethod(jclass_, fuzzer_test_one_input_data_, recorder); - const auto finding = GetFinding(); - if (finding == nullptr) { - LOG(ERROR) << "Failed to reproduce crash when rerunning with recorder"; - } - env.DeleteLocalRef(finding); - base64_data = SerializeRecordingFuzzedDataProvider(jvm_, recorder); - } else { - absl::string_view data_str(reinterpret_cast(data), size); - absl::Base64Escape(data_str, &base64_data); - } - const char *fuzz_target_call = fuzzer_test_one_input_data_ - ? kTestOneInputWithData - : kTestOneInputWithBytes; - // The serialization of recorded FuzzedDataProvider invocations can get to - // long to be stored in one String variable in the template. This is - // mitigated by chunking the data and concatenating it again in the generated - // code. - absl::ByLength chunk_delimiter = absl::ByLength(dataChunkMaxLength); - std::string chunked_base64_data = - absl::StrJoin(absl::StrSplit(base64_data, chunk_delimiter), "\", \""); - - std::string reproducer = - absl::Substitute(kBaseReproducer, data_sha1, chunked_base64_data, - FLAGS_target_class, fuzz_target_call); - std::string reproducer_filename = absl::StrFormat("Crash_%s.java", data_sha1); - std::string reproducer_full_path = absl::StrFormat( - "%s%c%s", FLAGS_reproducer_path, kPathSeparator, reproducer_filename); - std::ofstream reproducer_out(reproducer_full_path); - reproducer_out << reproducer; - std::cout << absl::StrFormat( - "reproducer_path='%s'; Java reproducer written to %s", - FLAGS_reproducer_path, reproducer_full_path) - << std::endl; -} - -std::string FuzzTargetRunner::DetectFuzzTargetClass() const { - jclass manifest_utils = jvm_.FindClass(kManifestUtilsClass); - jmethodID detect_fuzz_target_class = jvm_.GetStaticMethodID( - manifest_utils, "detectFuzzTargetClass", "()Ljava/lang/String;", true); - auto &env = jvm_.GetEnv(); - auto jni_fuzz_target_class = (jstring)(env.CallStaticObjectMethod( - manifest_utils, detect_fuzz_target_class)); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - exit(1); - } - if (jni_fuzz_target_class == nullptr) return ""; - - const char *fuzz_target_class_cstr = - env.GetStringUTFChars(jni_fuzz_target_class, nullptr); - std::string fuzz_target_class = std::string(fuzz_target_class_cstr); - env.ReleaseStringUTFChars(jni_fuzz_target_class, fuzz_target_class_cstr); - env.DeleteLocalRef(jni_fuzz_target_class); - - return fuzz_target_class; -} } // namespace jazzer diff --git a/driver/fuzz_target_runner.h b/driver/fuzz_target_runner.h index 23d18fb0..ad4f6944 100644 --- a/driver/fuzz_target_runner.h +++ b/driver/fuzz_target_runner.h @@ -16,61 +16,9 @@ #pragma once -#include - #include #include -#include "jvm_tooling.h" - namespace jazzer { - -enum class RunResult { - kOk, - kException, - kDumpAndContinue, -}; - -// Invokes the following static methods in the java fuzz target class: -// 1. On construction: -// - `public static void fuzzerInitialize()` -// OR -// - `public static void fuzzerInitialize(String[] args)` -// 2. On every call of Run(): -// - `public static void fuzzerTestOneInput(FuzzedDataProvider data)` -// OR -// - `public static void fuzzerTestOneInput(byte[] input)` -// 3. On destruction: -// - `public static void fuzzerTearDown()` -class FuzzTargetRunner : public ExceptionPrinter { - private: - const JVM &jvm_; - jclass jclass_; - jmethodID fuzzer_initialize_; - jmethodID fuzzer_initialize_with_args_; - jmethodID fuzzer_test_one_input_bytes_; - jmethodID fuzzer_test_one_input_data_; - jmethodID fuzzer_tear_down_; - jclass jazzer_; - jfieldID last_finding_; - std::vector ignore_tokens_; - - [[nodiscard]] std::string DetectFuzzTargetClass() const; - [[nodiscard]] jthrowable GetFinding() const; - - public: - // Initializes the java fuzz target by calling `void fuzzerInitialize(...)`. - explicit FuzzTargetRunner(JVM &jvm); - - // Calls the fuzz target tear down function. This can be useful to join any - // Threads so that the JVM shuts down correctly. - virtual ~FuzzTargetRunner(); - - // Propagate the fuzzer input to the java fuzz target. - RunResult Run(const uint8_t *data, std::size_t size); - - void DumpReproducer(const uint8_t *data, std::size_t size); -}; - std::vector fuzzTargetRunnerFlagsAsDefines(); } // namespace jazzer diff --git a/driver/fuzzed_data_provider_test.cpp b/driver/fuzzed_data_provider_test.cpp index a402d8f1..438a90e8 100644 --- a/driver/fuzzed_data_provider_test.cpp +++ b/driver/fuzzed_data_provider_test.cpp @@ -26,12 +26,8 @@ #include "tools/cpp/runfiles/runfiles.h" DECLARE_string(cp); -DECLARE_string(jvm_args); DECLARE_bool(hooks); -DECLARE_string(target_class); -DECLARE_string(target_args); - namespace jazzer { std::pair FixUpModifiedUtf8(const uint8_t* pos, @@ -134,12 +130,12 @@ constexpr std::size_t kValidModifiedUtf8NumBytes = 100000; constexpr uint32_t kValidModifiedUtf8Seed = 0x12345678; TEST_F(FuzzedDataProviderTest, InvalidModifiedUtf8AfterFixup) { - auto modified_utf8_validator = jvm_->FindClass("test.ModifiedUtf8Encoder"); + auto& env = jvm_->GetEnv(); + auto modified_utf8_validator = env.FindClass("test/ModifiedUtf8Encoder"); ASSERT_NE(nullptr, modified_utf8_validator); - auto string_to_modified_utf_bytes = jvm_->GetStaticMethodID( + auto string_to_modified_utf_bytes = env.GetStaticMethodID( modified_utf8_validator, "encode", "(Ljava/lang/String;)[B"); ASSERT_NE(nullptr, string_to_modified_utf_bytes); - auto& env = jvm_->GetEnv(); auto random_bytes = std::vector(kValidModifiedUtf8NumBytes); auto random = std::mt19937(kValidModifiedUtf8Seed); for (bool ascii_only : {false, true}) { diff --git a/driver/java_reproducer.cpp b/driver/java_reproducer.cpp deleted file mode 100644 index ed4c6755..00000000 --- a/driver/java_reproducer.cpp +++ /dev/null @@ -1,79 +0,0 @@ -// 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 "java_reproducer.h" - -#include "fuzzed_data_provider.h" -#include "jvm_tooling.h" - -namespace { -const char kRecordingFuzzedDataProviderClass[] = - "com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider"; -} - -namespace jazzer { -jobject GetFuzzedDataProviderJavaObject(const JVM &jvm) { - static jobject java_object = nullptr; - if (java_object == nullptr) { - jclass java_class = jvm.FindClass(kFuzzedDataProviderImplClass); - jmethodID java_constructor = jvm.GetMethodID(java_class, "", "()V"); - jobject local_ref = jvm.GetEnv().NewObject(java_class, java_constructor); - // We leak a global reference here as it will be used until JVM exit. - java_object = jvm.GetEnv().NewGlobalRef(local_ref); - } - return java_object; -} - -jobject GetRecordingFuzzedDataProviderJavaObject(const JVM &jvm) { - auto &env = jvm.GetEnv(); - jclass java_class = jvm.FindClass(kRecordingFuzzedDataProviderClass); - jmethodID java_make_proxy = jvm.GetStaticMethodID( - java_class, "makeFuzzedDataProviderProxy", - "()Lcom/code_intelligence/jazzer/api/FuzzedDataProvider;", true); - jobject local_ref = env.CallStaticObjectMethod(java_class, java_make_proxy); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - exit(1); - } - // This global reference is deleted in SerializeRecordingFuzzedDataProvider. - jobject global_ref = env.NewGlobalRef(local_ref); - env.DeleteLocalRef(local_ref); - return global_ref; -} - -std::string SerializeRecordingFuzzedDataProvider(const JVM &jvm, - jobject recorder) { - auto &env = jvm.GetEnv(); - jclass java_class = jvm.FindClass(kRecordingFuzzedDataProviderClass); - jmethodID java_serialize = - jvm.GetStaticMethodID(java_class, "serializeFuzzedDataProviderProxy", - "(Lcom/code_intelligence/jazzer/api/" - "FuzzedDataProvider;)Ljava/lang/String;", - true); - auto serialized_recorder = - (jstring)env.CallStaticObjectMethod(java_class, java_serialize, recorder); - env.DeleteLocalRef(java_class); - env.DeleteGlobalRef(recorder); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - exit(1); - } - const char *serialized_recorder_cstr = - env.GetStringUTFChars(serialized_recorder, nullptr); - std::string out(serialized_recorder_cstr); - env.ReleaseStringUTFChars(serialized_recorder, serialized_recorder_cstr); - env.DeleteLocalRef(serialized_recorder); - return out; -} -} // namespace jazzer diff --git a/driver/java_reproducer.h b/driver/java_reproducer.h deleted file mode 100644 index b3202b14..00000000 --- a/driver/java_reproducer.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include "jvm_tooling.h" - -namespace jazzer { -// Gets the single global reference to a Java FuzzedDataProvider object. The -// object itself doesn't hold any state and only exists to make the UX better by -// providing it as an argument to the fuzz target instead of relying on static -// calls. -jobject GetFuzzedDataProviderJavaObject(const JVM &jvm); - -jobject GetRecordingFuzzedDataProviderJavaObject(const JVM &jvm); - -std::string SerializeRecordingFuzzedDataProvider(const JVM &jvm, - jobject recorder); -} // namespace jazzer diff --git a/driver/java_reproducer_templates.h b/driver/java_reproducer_templates.h deleted file mode 100644 index 452e6312..00000000 --- a/driver/java_reproducer_templates.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include - -constexpr const char *kBaseReproducer = - R"java(import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -public class Crash_$0 { - static final String base64Bytes = String.join("", "$1"); - - public static void main(String[] args) throws Throwable { - ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true); - try { - Method fuzzerInitialize = $2.class.getMethod("fuzzerInitialize"); - fuzzerInitialize.invoke(null); - } catch (NoSuchMethodException ignored) { - try { - Method fuzzerInitialize = $2.class.getMethod("fuzzerInitialize", String[].class); - fuzzerInitialize.invoke(null, (Object) args); - } catch (NoSuchMethodException ignored1) { - } catch (IllegalAccessException | InvocationTargetException e) { - e.printStackTrace(); - System.exit(1); - } - } catch (IllegalAccessException | InvocationTargetException e) { - e.printStackTrace(); - System.exit(1); - } - $3 - $2.fuzzerTestOneInput(input); - } -} -)java"; - -constexpr const char *kTestOneInputWithBytes = - "byte[] input = java.util.Base64.getDecoder().decode(base64Bytes);"; - -constexpr const char *kTestOneInputWithData = - "com.code_intelligence.jazzer.api.CannedFuzzedDataProvider input = new " - "com.code_intelligence.jazzer.api.CannedFuzzedDataProvider(base64Bytes)" - ";"; diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index c69c044f..05b118ab 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -101,8 +101,8 @@ DECLARE_bool(fake_pcs); #define ARG_SEPARATOR ":" #endif -extern "C" JNIEXPORT jint JNICALL JNI_OnLoad_jazzer_initialize(JavaVM *vm, - void *) { +extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL +JNI_OnLoad_jazzer_initialize(JavaVM *vm, void *) { return JNI_VERSION_1_8; } @@ -344,167 +344,4 @@ JVM::JVM(std::string_view executable_path, std::string_view seed) { JNIEnv &JVM::GetEnv() const { return *env_; } JVM::~JVM() { jvm_->DestroyJavaVM(); } - -jclass JVM::FindClass(std::string class_name) const { - auto &env = GetEnv(); - std::replace(class_name.begin(), class_name.end(), '.', '/'); - const auto ret = env.FindClass(class_name.c_str()); - if (ret == nullptr) { - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - throw std::runtime_error( - absl::StrFormat("Could not find class %s", class_name)); - } else { - throw std::runtime_error(absl::StrFormat( - "Java class '%s' not found without exception", class_name)); - } - } - return ret; -} - -jmethodID JVM::GetStaticMethodID(jclass jclass, const std::string &jmethod, - const std::string &signature, - bool is_required) const { - auto &env = GetEnv(); - const auto ret = - env.GetStaticMethodID(jclass, jmethod.c_str(), signature.c_str()); - if (ret == nullptr) { - if (is_required) { - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - } - throw std::runtime_error( - absl::StrFormat("Static method '%s' not found", jmethod)); - } else { - LOG(INFO) << "did not find method " << jmethod << " with signature " - << signature; - env.ExceptionClear(); - } - } - return ret; -} - -jmethodID JVM::GetMethodID(jclass jclass, const std::string &jmethod, - const std::string &signature) const { - auto &env = GetEnv(); - const auto ret = env.GetMethodID(jclass, jmethod.c_str(), signature.c_str()); - if (ret == nullptr) { - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - } - throw std::runtime_error(absl::StrFormat("Method '%s' not found", jmethod)); - } - return ret; -} - -jfieldID JVM::GetStaticFieldID(jclass class_id, const std::string &field_name, - const std::string &type) const { - auto &env = GetEnv(); - const auto ret = - env.GetStaticFieldID(class_id, field_name.c_str(), type.c_str()); - if (ret == nullptr) { - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - } - throw std::runtime_error( - absl::StrFormat("Field '%s' not found", field_name)); - } - return ret; -} - -ExceptionPrinter::ExceptionPrinter(JVM &jvm) - : jvm_(jvm), - string_writer_class_(jvm.FindClass("java/io/StringWriter")), - string_writer_constructor_( - jvm.GetMethodID(string_writer_class_, "", "()V")), - string_writer_to_string_method_(jvm.GetMethodID( - string_writer_class_, "toString", "()Ljava/lang/String;")), - print_writer_class_(jvm.FindClass("java/io/PrintWriter")), - print_writer_constructor_(jvm.GetMethodID(print_writer_class_, "", - "(Ljava/io/Writer;)V")) { - auto throwable_class = jvm.FindClass("java/lang/Throwable"); - print_stack_trace_method_ = jvm.GetMethodID( - throwable_class, "printStackTrace", "(Ljava/io/PrintWriter;)V"); - if (FLAGS_hooks) { - exception_utils_ = jvm.FindClass(kExceptionUtilsClassName); - compute_dedup_token_method_ = jvm.GetStaticMethodID( - exception_utils_, "computeDedupToken", "(Ljava/lang/Throwable;)J"); - preprocess_throwable_method_ = - jvm.GetStaticMethodID(exception_utils_, "preprocessThrowable", - "(Ljava/lang/Throwable;)Ljava/lang/Throwable;"); - } -} - -// The JNI way of writing: -// StringWriter stringWriter = new StringWriter(); -// PrintWriter printWriter = new PrintWriter(stringWriter); -// e.printStackTrace(printWriter); -// return stringWriter.toString(); -std::string ExceptionPrinter::getStackTrace(jthrowable exception) const { - auto &env = jvm_.GetEnv(); - if (exception == nullptr) { - return ""; - } - - auto string_writer = - env.NewObject(string_writer_class_, string_writer_constructor_); - if (string_writer == nullptr) { - env.ExceptionDescribe(); - return ""; - } - auto print_writer = env.NewObject(print_writer_class_, - print_writer_constructor_, string_writer); - if (print_writer == nullptr) { - env.ExceptionDescribe(); - return ""; - } - - env.CallVoidMethod(exception, print_stack_trace_method_, print_writer); - env.DeleteLocalRef(print_writer); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - return ""; - } - auto exception_string_object = reinterpret_cast( - env.CallObjectMethod(string_writer, string_writer_to_string_method_)); - env.DeleteLocalRef(string_writer); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - return ""; - } - - auto char_pointer = env.GetStringUTFChars(exception_string_object, nullptr); - std::string exception_string(char_pointer); - env.ReleaseStringUTFChars(exception_string_object, char_pointer); - env.DeleteLocalRef(exception_string_object); - return exception_string; -} - -jthrowable ExceptionPrinter::preprocessException(jthrowable exception) const { - if (exception == nullptr) return nullptr; - auto &env = jvm_.GetEnv(); - if (!FLAGS_hooks || !preprocess_throwable_method_) return exception; - auto processed_exception = (jthrowable)(env.CallStaticObjectMethod( - exception_utils_, preprocess_throwable_method_, exception)); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - return exception; - } - return processed_exception; -} - -jlong ExceptionPrinter::computeDedupToken(jthrowable exception) const { - auto &env = jvm_.GetEnv(); - if (!FLAGS_hooks || exception == nullptr || - compute_dedup_token_method_ == nullptr) - return 0; - const auto dedup_token = env.CallStaticLongMethod( - exception_utils_, compute_dedup_token_method_, exception); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - return 0; - } - return dedup_token; -} - } // namespace jazzer diff --git a/driver/jvm_tooling.h b/driver/jvm_tooling.h index 88ce177f..80595f7a 100644 --- a/driver/jvm_tooling.h +++ b/driver/jvm_tooling.h @@ -42,46 +42,5 @@ class JVM { // Get the JNI environment for interaction with the running JVM instance. JNIEnv &GetEnv() const; - - jclass FindClass(std::string class_name) const; - jmethodID GetStaticMethodID(jclass class_id, const std::string &method_name, - const std::string &signature, - bool is_required = true) const; - jmethodID GetMethodID(jclass class_id, const std::string &method_name, - const std::string &signature) const; - jfieldID GetStaticFieldID(jclass jclass, const std::string &field_name, - const std::string &type) const; -}; - -// Adds convenience methods to convert a jvm exception to std::string -// using StringWriter and PrintWriter. The stack trace can be subjected to -// further processing, such as deduplication token computation and severity -// annotation. -class ExceptionPrinter { - private: - const JVM &jvm_; - - jclass string_writer_class_; - jmethodID string_writer_constructor_; - jmethodID string_writer_to_string_method_; - - jclass print_writer_class_; - jmethodID print_writer_constructor_; - jmethodID print_stack_trace_method_; - - jclass exception_utils_; - jmethodID compute_dedup_token_method_; - jmethodID preprocess_throwable_method_; - - protected: - explicit ExceptionPrinter(JVM &jvm); - - // returns the current JVM exception stack trace as a string - std::string getStackTrace(jthrowable exception) const; - // augments the throwable with additional information such as severity markers - jthrowable preprocessException(jthrowable exception) const; - // returns a hash of the exception stack trace for deduplication purposes - jlong computeDedupToken(jthrowable exception) const; }; - } /* namespace jazzer */ diff --git a/driver/jvm_tooling_test.cpp b/driver/jvm_tooling_test.cpp index 88d32f7f..527ff4fb 100644 --- a/driver/jvm_tooling_test.cpp +++ b/driver/jvm_tooling_test.cpp @@ -14,18 +14,13 @@ #include "jvm_tooling.h" -#include "coverage_tracker.h" -#include "fuzz_target_runner.h" #include "gflags/gflags.h" #include "gtest/gtest.h" #include "tools/cpp/runfiles/runfiles.h" DECLARE_string(cp); +DECLARE_bool(hooks); DECLARE_string(jvm_args); -DECLARE_string(target_class); -DECLARE_string(target_args); -DECLARE_string(agent_path); -DECLARE_string(instrumentation_excludes); #ifdef _WIN32 #define ARG_SEPARATOR ";" @@ -35,22 +30,15 @@ DECLARE_string(instrumentation_excludes); namespace jazzer { -std::vector splitOnSpace(const std::string &s); - -TEST(SpaceSplit, SpaceSplitSimple) { - ASSERT_EQ((std::vector{"first", "se\\ cond", "third"}), - splitOnSpace("first se\\ cond third")); -} - class JvmToolingTest : public ::testing::Test { protected: // After DestroyJavaVM() no new JVM instance can be created in the same // process, so we set up a single JVM instance for this test binary which gets // destroyed after all tests in this test suite have finished. static void SetUpTestCase() { + FLAGS_hooks = false; FLAGS_jvm_args = "-Denv1=va\\" ARG_SEPARATOR "l1\\\\" ARG_SEPARATOR "-Denv2=val2"; - FLAGS_instrumentation_excludes = "**"; using ::bazel::tools::cpp::runfiles::Runfiles; Runfiles *runfiles = Runfiles::CreateForTest(); FLAGS_cp = runfiles->Rlocation(FLAGS_cp); @@ -65,26 +53,15 @@ class JvmToolingTest : public ::testing::Test { std::unique_ptr JvmToolingTest::jvm_ = nullptr; -TEST_F(JvmToolingTest, ClassNotFound) { - ASSERT_THROW(jvm_->FindClass(""), std::runtime_error); - ASSERT_THROW(jvm_->FindClass("test.NonExistingClass"), std::runtime_error); - ASSERT_THROW(jvm_->FindClass("test/NonExistingClass"), std::runtime_error); -} - -TEST_F(JvmToolingTest, ClassInClassPath) { - ASSERT_NE(nullptr, jvm_->FindClass("test.PropertyPrinter")); - ASSERT_NE(nullptr, jvm_->FindClass("test/PropertyPrinter")); -} - TEST_F(JvmToolingTest, JniProperties) { - auto property_printer_class = jvm_->FindClass("test.PropertyPrinter"); + auto &env = jvm_->GetEnv(); + auto property_printer_class = env.FindClass("test/PropertyPrinter"); ASSERT_NE(nullptr, property_printer_class); auto method_id = - jvm_->GetStaticMethodID(property_printer_class, "printProperty", - "(Ljava/lang/String;)Ljava/lang/String;"); + env.GetStaticMethodID(property_printer_class, "printProperty", + "(Ljava/lang/String;)Ljava/lang/String;"); ASSERT_NE(nullptr, method_id); - auto &env = jvm_->GetEnv(); for (const auto &el : std::vector>{ {"not set property", ""}, {"env1", "va" ARG_SEPARATOR "l1\\"}, @@ -98,32 +75,8 @@ TEST_F(JvmToolingTest, JniProperties) { } else { ASSERT_NE(nullptr, ret); jboolean is_copy; - ASSERT_EQ(el.second, jvm_->GetEnv().GetStringUTFChars(ret, &is_copy)); + ASSERT_EQ(el.second, env.GetStringUTFChars(ret, &is_copy)); } } } - -class ExceptionPrinterTest : public ExceptionPrinter { - public: - ExceptionPrinterTest(JVM &jvm) : ExceptionPrinter(jvm), jvm_(jvm) {} - - std::string TriggerJvmException() { - jclass illegal_argument_exception = - jvm_.FindClass("java.lang.IllegalArgumentException"); - jvm_.GetEnv().ThrowNew(illegal_argument_exception, "Test"); - jthrowable exception = jvm_.GetEnv().ExceptionOccurred(); - jvm_.GetEnv().ExceptionClear(); - return getStackTrace(exception); - } - - private: - const JVM &jvm_; -}; - -TEST_F(JvmToolingTest, ExceptionPrinter) { - ExceptionPrinterTest exception_printer(*jvm_); - // a.k.a std::string.startsWith(java.lang...) - ASSERT_TRUE(exception_printer.TriggerJvmException().rfind( - "java.lang.IllegalArgumentException", 0) == 0); -} } // namespace jazzer diff --git a/driver/libfuzzer_driver.cpp b/driver/libfuzzer_driver.cpp index bbcd94dd..25bec5da 100644 --- a/driver/libfuzzer_driver.cpp +++ b/driver/libfuzzer_driver.cpp @@ -26,7 +26,7 @@ #include "absl/strings/match.h" #include "absl/strings/str_format.h" #include "absl/strings/strip.h" -#include "fuzz_target_runner.h" +#include "fuzzed_data_provider.h" #include "gflags/gflags.h" #include "glog/logging.h" #include "jvm_tooling.h" diff --git a/driver/libfuzzer_driver.h b/driver/libfuzzer_driver.h index 53713092..14af2a7a 100644 --- a/driver/libfuzzer_driver.h +++ b/driver/libfuzzer_driver.h @@ -19,8 +19,6 @@ #include #include -#include "absl/strings/match.h" -#include "fuzzed_data_provider.h" #include "jvm_tooling.h" namespace jazzer { @@ -34,9 +32,6 @@ class AbstractLibfuzzerDriver { virtual int TestOneInput(const uint8_t *data, std::size_t size) = 0; - // Default value of the libFuzzer -error_exitcode flag. - static constexpr int kErrorExitCode = 77; - // A libFuzzer-registered callback that outputs the crashing input, but does // not include a stack trace. static void (*libfuzzer_print_crashing_input_)(); @@ -57,8 +52,6 @@ class LibfuzzerDriver : public AbstractLibfuzzerDriver { ~LibfuzzerDriver() override = default; - void DumpReproducer(const uint8_t *data, std::size_t size); - private: static std::string getUsageString(); }; diff --git a/driver/testdata/BUILD.bazel b/driver/testdata/BUILD.bazel index 901ae89b..c3c24431 100644 --- a/driver/testdata/BUILD.bazel +++ b/driver/testdata/BUILD.bazel @@ -3,9 +3,4 @@ java_binary( srcs = glob(["test/*.java"]), create_executable = False, visibility = ["//visibility:public"], - deps = [ - "//agent/src/main/java/com/code_intelligence/jazzer/api", - "//agent/src/main/java/com/code_intelligence/jazzer/runtime", - "//agent/src/main/java/com/code_intelligence/jazzer/runtime:coverage_map", - ], ) -- cgit v1.2.3 From a2c6ba526877b17c5499909ec80110e681478441 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 4 Aug 2022 15:16:55 +0200 Subject: driver: Move fake_pcs flag to runner This decouples jvm_tooling from libfuzzer_callbacks. --- driver/BUILD.bazel | 1 - driver/fuzz_target_runner.cpp | 7 +++++++ driver/jvm_tooling.cpp | 6 ------ driver/libfuzzer_callbacks.cpp | 6 ------ 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index cec70d25..45c843a1 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -144,7 +144,6 @@ cc_library( ], deps = [ ":fuzzed_data_provider", - ":libfuzzer_callbacks", "@bazel_tools//tools/cpp/runfiles", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index adf15fee..43ed6331 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -57,6 +57,11 @@ DEFINE_string(autofuzz, "", DEFINE_string(autofuzz_ignore, "", "Fully qualified class names of exceptions to ignore during " "autofuzz. Separated by comma."); +DEFINE_bool( + fake_pcs, false, + "Supply synthetic Java program counters to libFuzzer trace hooks to " + "make value profiling more effective. Enabled by default if " + "-use_value_profile=1 is specified."); DECLARE_bool(hooks); @@ -74,6 +79,8 @@ std::vector fuzzTargetRunnerFlagsAsDefines() { absl::StrFormat("-Djazzer.autofuzz=%s", FLAGS_autofuzz), absl::StrFormat("-Djazzer.autofuzz_ignore=%s", FLAGS_autofuzz_ignore), absl::StrFormat("-Djazzer.hooks=%s", FLAGS_hooks ? "true" : "false"), + absl::StrFormat("-Djazzer.fake_pcs=%s", + FLAGS_fake_pcs ? "true" : "false"), }; } } // namespace jazzer diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index 05b118ab..b8eccb5d 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -93,8 +93,6 @@ DEFINE_bool(hooks, true, "coverage information will be processed. This can be useful for " "running a regression test on non-instrumented bytecode."); -DECLARE_bool(fake_pcs); - #ifdef _WIN32 #define ARG_SEPARATOR ";" #else @@ -277,10 +275,6 @@ JVM::JVM(std::string_view executable_path, std::string_view seed) { std::string seed_property = absl::StrFormat("-Djazzer.seed=%s", seed); options.push_back( JavaVMOption{.optionString = const_cast(seed_property.c_str())}); - std::string fake_pcs_property = absl::StrFormat( - "-Djazzer.fake_pcs=%s", FLAGS_fake_pcs ? "true" : "false"); - options.push_back(JavaVMOption{ - .optionString = const_cast(fake_pcs_property.c_str())}); std::vector fuzz_target_runner_defines = ::jazzer::fuzzTargetRunnerFlagsAsDefines(); diff --git a/driver/libfuzzer_callbacks.cpp b/driver/libfuzzer_callbacks.cpp index 97243a43..83c03576 100644 --- a/driver/libfuzzer_callbacks.cpp +++ b/driver/libfuzzer_callbacks.cpp @@ -26,12 +26,6 @@ #include "gflags/gflags.h" #include "glog/logging.h" -DEFINE_bool( - fake_pcs, false, - "Supply synthetic Java program counters to libFuzzer trace hooks to " - "make value profiling more effective. Enabled by default if " - "-use_value_profile=1 is specified."); - namespace { bool is_using_native_libraries = false; std::once_flag ignore_list_flag; -- cgit v1.2.3 From fde3c4ec1c7961add3b91ec52f6ee7f947a4c30c Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 4 Aug 2022 15:20:40 +0200 Subject: driver: Extract libfuzzer_driver into a separate target --- driver/BUILD.bazel | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index 45c843a1..da7c2b6d 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -121,19 +121,32 @@ cc_library( alwayslink = True, ) +cc_library( + name = "libfuzzer_driver", + srcs = [":libfuzzer_driver.cpp"], + hdrs = [":libfuzzer_driver.h"], + deps = [ + ":fuzzed_data_provider", + ":jvm_tooling_lib", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_glog//:glog", + "@fmeum_rules_jni//jni:libjvm", + "@jazzer_com_github_gflags_gflags//:gflags", + ], +) + cc_library( name = "jvm_tooling_lib", srcs = [ "fuzz_target_runner.cpp", "fuzz_target_runner.h", "jvm_tooling.cpp", - "libfuzzer_driver.cpp", "utils.cpp", "utils.h", ], hdrs = [ "jvm_tooling.h", - "libfuzzer_driver.h", ], # Needs to be linked statically for JNI_OnLoad_jazzer_initialize to be found # by the JVM. @@ -143,12 +156,11 @@ cc_library( "manual", ], deps = [ - ":fuzzed_data_provider", "@bazel_tools//tools/cpp/runfiles", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_glog//:glog", - "@fmeum_rules_jni//jni:libjvm", + "@fmeum_rules_jni//jni", "@jazzer_com_github_gflags_gflags//:gflags", ], ) @@ -161,8 +173,8 @@ cc_17_library( linkstatic = True, deps = [ ":coverage_tracker", - ":jvm_tooling_lib", ":libfuzzer_callbacks", + ":libfuzzer_driver", "//driver/src/main/java/com/code_intelligence/jazzer/driver:fuzz_target_runner.hdrs", "@jazzer_libfuzzer//:libFuzzer", ], -- cgit v1.2.3 From f8d26cf5047d0e0da61a260a0504040fa7c36bd1 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 10 Aug 2022 11:25:35 +0200 Subject: driver: Refactor ReproducerTemplate The chunking logic is moved out of FuzzTargetRunner and ReproducerTemplate becomes a proper instantiable class that collects the fixed information required to produce Java reproducers. --- .../code_intelligence/jazzer/driver/BUILD.bazel | 6 +- .../jazzer/driver/FuzzTargetRunner.java | 19 +---- .../jazzer/driver/ReproducerTemplate.java | 85 ++++++++++++++++++++++ .../jazzer/driver/ReproducerTemplates.java | 54 -------------- 4 files changed, 91 insertions(+), 73 deletions(-) create mode 100644 driver/src/main/java/com/code_intelligence/jazzer/driver/ReproducerTemplate.java delete mode 100644 driver/src/main/java/com/code_intelligence/jazzer/driver/ReproducerTemplates.java diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel b/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel index 3eb39aee..fc291497 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -10,7 +10,7 @@ java_jni_library( ], deps = [ ":opt", - ":reproducer_templates", + ":reproducer_template", "//agent/src/main/java/com/code_intelligence/jazzer/api", "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz", "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor", @@ -21,8 +21,8 @@ java_jni_library( ) java_library( - name = "reproducer_templates", - srcs = ["ReproducerTemplates.java"], + name = "reproducer_template", + srcs = ["ReproducerTemplate.java"], resources = ["Reproducer.java.tmpl"], deps = [":opt"], ) diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index c4c7e6b1..9e465aa2 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -38,7 +38,6 @@ import java.lang.reflect.Modifier; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; import java.util.Base64; import java.util.Collections; import java.util.HashSet; @@ -59,16 +58,13 @@ public final class FuzzTargetRunner { private static final String FUZZER_TEST_ONE_INPUT = "fuzzerTestOneInput"; private static final String FUZZER_INITIALIZE = "fuzzerInitialize"; private static final String FUZZER_TEARDOWN = "fuzzerTearDown"; - // A constant pool CONSTANT_Utf8_info entry should be able to hold data of size - // uint16, but somehow this does not seem to be the case and leads to invalid - // code crash reproducer code. Reducing the size by one resolves the problem. - private static final int DATA_CHUNK_MAX_LENGTH = Short.MAX_VALUE - 1; private static final Set ignoredTokens = new HashSet<>(Opt.ignore); private static final FuzzedDataProvider fuzzedDataProvider = new FuzzedDataProviderImpl(); private static final Class fuzzTargetClass; private static final MethodHandle fuzzTarget; public static final boolean useFuzzedDataProvider; + private static final ReproducerTemplate reproducerTemplate; private static long runCount = 0; static { @@ -118,6 +114,7 @@ public final class FuzzTargetRunner { } catch (IllegalAccessException e) { throw new RuntimeException(e); } + reproducerTemplate = new ReproducerTemplate(fuzzTargetClass.getName(), useFuzzedDataProvider); Method initializeNoArgs = targetPublicStaticMethodOrNull(FUZZER_INITIALIZE); Method initializeWithArgs = targetPublicStaticMethodOrNull(FUZZER_INITIALIZE, String[].class); @@ -307,17 +304,7 @@ public final class FuzzTargetRunner { base64Data = Base64.getEncoder().encodeToString(data); } - // The serialization of recorded FuzzedDataProvider invocations can get too long to be emitted - // into the template as a single String literal. This is mitigated by chunking the data and - // concatenating it again in the generated code. - ArrayList chunks = new ArrayList<>(); - for (int i = 0; i <= base64Data.length() / DATA_CHUNK_MAX_LENGTH; i++) { - chunks.add(base64Data.substring(i * DATA_CHUNK_MAX_LENGTH, - Math.min((i + 1) * DATA_CHUNK_MAX_LENGTH, base64Data.length()))); - } - String chunkedBase64Data = String.join("\", \"", chunks); - ReproducerTemplates.dumpReproducer( - chunkedBase64Data, dataSha1, fuzzTargetClass.getName(), useFuzzedDataProvider); + reproducerTemplate.dumpReproducer(base64Data, dataSha1); } private static Method targetPublicStaticMethodOrNull(String name, Class... parameterTypes) { diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/ReproducerTemplate.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/ReproducerTemplate.java new file mode 100644 index 00000000..0c7721cf --- /dev/null +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/ReproducerTemplate.java @@ -0,0 +1,85 @@ +/* + * Copyright 2022 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.driver; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.stream.Collectors; + +final class ReproducerTemplate { + // A constant pool CONSTANT_Utf8_info entry should be able to hold data of size + // uint16, but somehow this does not seem to be the case and leads to invalid + // code crash reproducer code. Reducing the size by one resolves the problem. + private static final int DATA_CHUNK_MAX_LENGTH = Short.MAX_VALUE - 1; + private static final String RAW_BYTES_INPUT = + "byte[] input = java.util.Base64.getDecoder().decode(base64Bytes);"; + private static final String FUZZED_DATA_PROVIDER_INPUT = + "com.code_intelligence.jazzer.api.CannedFuzzedDataProvider input = new com.code_intelligence.jazzer.api.CannedFuzzedDataProvider(base64Bytes);"; + + private final String targetClass; + private final boolean useFuzzedDataProvider; + + public ReproducerTemplate(String targetClass, boolean useFuzzedDataProvider) { + this.targetClass = targetClass; + this.useFuzzedDataProvider = useFuzzedDataProvider; + } + + /** + * Emits a Java reproducer to {@code Crash_HASH.java} in {@code Opt.reproducerPath}. + * + * @param data the Base64-encoded data to emit as a string literal + * @param sha the SHA1 hash of the raw fuzzer input + */ + public void dumpReproducer(String data, String sha) { + String targetArg = useFuzzedDataProvider ? FUZZED_DATA_PROVIDER_INPUT : RAW_BYTES_INPUT; + String template = new BufferedReader( + new InputStreamReader(ReproducerTemplate.class.getResourceAsStream("Reproducer.java.tmpl"), + StandardCharsets.UTF_8)) + .lines() + .collect(Collectors.joining("\n")); + String chunkedData = chunkStringLiteral(data); + String javaSource = String.format(template, sha, chunkedData, targetClass, targetArg); + Path javaPath = Paths.get(Opt.reproducerPath, String.format("Crash_%s.java", sha)); + try { + Files.write(javaPath, javaSource.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); + } catch (IOException e) { + System.err.printf("ERROR: Failed to write Java reproducer to %s%n", javaPath); + e.printStackTrace(); + } + System.out.printf( + "reproducer_path='%s'; Java reproducer written to %s%n", Opt.reproducerPath, javaPath); + } + + // The serialization of recorded FuzzedDataProvider invocations can get too long to be emitted + // into the template as a single String literal. This is mitigated by chunking the data and + // concatenating it again in the generated code. + private String chunkStringLiteral(String data) { + ArrayList chunks = new ArrayList<>(); + for (int i = 0; i <= data.length() / DATA_CHUNK_MAX_LENGTH; i++) { + chunks.add(data.substring( + i * DATA_CHUNK_MAX_LENGTH, Math.min((i + 1) * DATA_CHUNK_MAX_LENGTH, data.length()))); + } + return String.join("\", \"", chunks); + } +} diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/ReproducerTemplates.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/ReproducerTemplates.java deleted file mode 100644 index 68042079..00000000 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/ReproducerTemplates.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2022 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.driver; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.stream.Collectors; - -final class ReproducerTemplates { - private static final String rawBytesInput = - "byte[] input = java.util.Base64.getDecoder().decode(base64Bytes);"; - private static final String fuzzedDataProviderInput = - "com.code_intelligence.jazzer.api.CannedFuzzedDataProvider input = new com.code_intelligence.jazzer.api.CannedFuzzedDataProvider(base64Bytes);"; - - public static void dumpReproducer( - String base64, String dataSha, String targetClass, boolean useFuzzedDataProvider) { - String targetArg = useFuzzedDataProvider ? fuzzedDataProviderInput : rawBytesInput; - String template = new BufferedReader( - new InputStreamReader(ReproducerTemplates.class.getResourceAsStream("Reproducer.java.tmpl"), - StandardCharsets.UTF_8)) - .lines() - .collect(Collectors.joining("\n")); - String javaSource = String.format(template, dataSha, base64, targetClass, targetArg); - Path javaPath = Paths.get(Opt.reproducerPath, String.format("Crash_%s.java", dataSha)); - try { - Files.write(javaPath, javaSource.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); - } catch (IOException e) { - System.err.printf("ERROR: Failed to write Java reproducer to %s%n", javaPath); - e.printStackTrace(); - } - System.out.printf( - "reproducer_path='%s'; Java reproducer written to %s%n", Opt.reproducerPath, javaPath); - } -} -- cgit v1.2.3 From cc83f0d50d88d21d6c8bade046f7112a56d8f155 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 8 Aug 2022 13:47:55 +0200 Subject: driver: Split libfuzzer_{driver,fuzz_target} into main and library By using LLVMFuzzerRunDriver, we can remove the LibfuzzerDriver class and replace it by: * a real main function that preprocesses arguments for libFuzzer and starts a JVM; * everything else turned into a proper library with no dependency on our custom JVM class. This will allow us to turn everything except the main function into a JNI shared library, opening up the possibility to launch Jazzer from an already running JVM. --- .../code_intelligence/jazzer/runtime/BUILD.bazel | 2 +- driver/BUILD.bazel | 39 +++- driver/fuzz_target_runner.cpp | 125 +++++++++++ driver/fuzz_target_runner.h | 15 ++ driver/jazzer_main.cpp | 219 ++++++++++++++++++ driver/jvm_tooling.cpp | 33 --- driver/libfuzzer_driver.cpp | 245 --------------------- driver/libfuzzer_driver.h | 59 ----- driver/libfuzzer_fuzz_target.cpp | 106 --------- driver/sanitizer_symbols.cpp | 2 +- driver/sanitizer_symbols_for_tests.cpp | 6 +- .../jazzer/driver/FuzzTargetRunner.java | 4 + third_party/libFuzzer.BUILD | 4 +- 13 files changed, 399 insertions(+), 460 deletions(-) create mode 100644 driver/jazzer_main.cpp delete mode 100644 driver/libfuzzer_driver.cpp delete mode 100644 driver/libfuzzer_driver.h delete mode 100644 driver/libfuzzer_fuzz_target.cpp diff --git a/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/BUILD.bazel b/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/BUILD.bazel index 555d5c7d..083979a1 100644 --- a/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/BUILD.bazel +++ b/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/BUILD.bazel @@ -7,6 +7,6 @@ cc_jni_library( deps = [ "//agent/src/jmh/java/com/code_intelligence/jazzer/runtime:fuzzer_callbacks.hdrs", "//driver:sanitizer_hooks_with_pc", - "@jazzer_libfuzzer//:libFuzzer", + "@jazzer_libfuzzer//:libfuzzer_no_main", ], ) diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index da7c2b6d..3db417e4 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -122,11 +122,10 @@ cc_library( ) cc_library( - name = "libfuzzer_driver", - srcs = [":libfuzzer_driver.cpp"], - hdrs = [":libfuzzer_driver.h"], + name = "jazzer_main", + srcs = [":jazzer_main.cpp"], deps = [ - ":fuzzed_data_provider", + ":fuzz_target_runner", ":jvm_tooling_lib", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", @@ -136,11 +135,23 @@ cc_library( ], ) +cc_library( + name = "fuzz_target_runner", + srcs = ["fuzz_target_runner.cpp"], + hdrs = ["fuzz_target_runner.h"], + deps = [ + ":fuzzed_data_provider", + "//driver/src/main/java/com/code_intelligence/jazzer/driver:fuzz_target_runner.hdrs", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@fmeum_rules_jni//jni", + "@jazzer_com_github_gflags_gflags//:gflags", + ], +) + cc_library( name = "jvm_tooling_lib", srcs = [ - "fuzz_target_runner.cpp", - "fuzz_target_runner.h", "jvm_tooling.cpp", "utils.cpp", "utils.h", @@ -156,6 +167,7 @@ cc_library( "manual", ], deps = [ + ":fuzz_target_runner", "@bazel_tools//tools/cpp/runfiles", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", @@ -167,16 +179,17 @@ cc_library( cc_17_library( name = "driver_lib", - srcs = [ - "libfuzzer_fuzz_target.cpp", - ], linkstatic = True, deps = [ + # This includes an explicit list of all cc_library targets providing + # symbols for JNI dynamic linking. ":coverage_tracker", + ":fuzz_target_runner", + ":fuzzed_data_provider", + ":jazzer_main", ":libfuzzer_callbacks", - ":libfuzzer_driver", - "//driver/src/main/java/com/code_intelligence/jazzer/driver:fuzz_target_runner.hdrs", - "@jazzer_libfuzzer//:libFuzzer", + ":signal_handler", + "@jazzer_libfuzzer//:libfuzzer_no_main", ], alwayslink = True, ) @@ -314,6 +327,7 @@ cc_test( includes = ["."], deps = [ ":jvm_tooling_lib", + ":sanitizer_symbols_for_tests", ":test_main", "@bazel_tools//tools/cpp/runfiles", "@googletest//:gtest", @@ -336,6 +350,7 @@ cc_test( deps = [ ":fuzzed_data_provider", ":jvm_tooling_lib", + ":sanitizer_symbols_for_tests", ":test_main", "@bazel_tools//tools/cpp/runfiles", "@googletest//:gtest", diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index 43ed6331..14268f80 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -12,12 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. +/** + * A native wrapper around the FuzzTargetRunner Java class that executes it as a + * libFuzzer fuzz target. + */ + #include "fuzz_target_runner.h" +#include + #include #include #include "absl/strings/str_format.h" +#include "com_code_intelligence_jazzer_driver_FuzzTargetRunner.h" +#include "driver/fuzzed_data_provider.h" #include "gflags/gflags.h" DEFINE_string( @@ -65,6 +74,44 @@ DEFINE_bool( DECLARE_bool(hooks); +extern "C" int LLVMFuzzerRunDriver(int *argc, char ***argv, + int (*UserCb)(const uint8_t *Data, + size_t Size)); + +namespace { +constexpr auto kFuzzTargetRunnerClassName = + "com/code_intelligence/jazzer/driver/FuzzTargetRunner"; + +bool gUseFuzzedDataProvider; +jclass gRunner; +jmethodID gRunOneId; +JNIEnv *gEnv; + +// A libFuzzer-registered callback that outputs the crashing input, but does +// not include a stack trace. +void (*gLibfuzzerPrintCrashingInput)() = nullptr; + +int testOneInput(const uint8_t *data, const std::size_t size) { + JNIEnv &env = *gEnv; + jbyteArray input = nullptr; + jint jsize = + std::min(size, static_cast(std::numeric_limits::max())); + if (gUseFuzzedDataProvider) { + ::jazzer::FeedFuzzedDataProvider(data, size); + } else { + input = env.NewByteArray(jsize); + env.SetByteArrayRegion(input, 0, jsize, + reinterpret_cast(data)); + } + int res = env.CallStaticIntMethod(gRunner, gRunOneId, input); + if (env.ExceptionCheck()) { + env.ExceptionDescribe(); + _Exit(1); + } + return res; +} +} // namespace + namespace jazzer { std::vector fuzzTargetRunnerFlagsAsDefines() { return { @@ -83,4 +130,82 @@ std::vector fuzzTargetRunnerFlagsAsDefines() { FLAGS_fake_pcs ? "true" : "false"), }; } + +int StartFuzzer(JNIEnv *env, int argc, char **argv) { + gEnv = env; + jclass runner = env->FindClass(kFuzzTargetRunnerClassName); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + _Exit(1); + } + gRunner = reinterpret_cast(env->NewGlobalRef(runner)); + gRunOneId = env->GetStaticMethodID(runner, "runOne", "([B)I"); + jfieldID use_fuzzed_data_provider_id = + env->GetStaticFieldID(runner, "useFuzzedDataProvider", "Z"); + gUseFuzzedDataProvider = + env->GetStaticBooleanField(runner, use_fuzzed_data_provider_id); + + return LLVMFuzzerRunDriver(&argc, &argv, testOneInput); +} + +void DumpJvmStackTraces() { + JavaVM *vm; + jsize num_vms; + JNI_GetCreatedJavaVMs(&vm, 1, &num_vms); + if (num_vms != 1) { + return; + } + JNIEnv *env = nullptr; + if (vm->AttachCurrentThread(reinterpret_cast(&env), nullptr) != + JNI_OK) { + return; + } + jmethodID dumpStack = + env->GetStaticMethodID(gRunner, "dumpAllStackTraces", "()V"); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return; + } + env->CallStaticVoidMethod(gRunner, dumpStack); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return; + } + // Do not detach as we may be the main thread (but the JVM exits anyway). +} } // namespace jazzer + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner_printCrashingInput( + JNIEnv *, jclass) { + gLibfuzzerPrintCrashingInput(); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner__1Exit( + JNIEnv *, jclass, jint exit_code) { + _Exit(exit_code); +} + +// This symbol is defined by sanitizers if linked into Jazzer or in +// sanitizer_symbols.cpp if no sanitizer is used. +extern "C" void __sanitizer_set_death_callback(void (*)()); + +// We apply a patch to libFuzzer to make it call this function instead of +// __sanitizer_set_death_callback to pass us the death callback. +extern "C" [[maybe_unused]] void __jazzer_set_death_callback( + void (*callback)()) { + gLibfuzzerPrintCrashingInput = callback; + __sanitizer_set_death_callback([]() { + ::jazzer::DumpJvmStackTraces(); + gLibfuzzerPrintCrashingInput(); + // Ideally, we would be able to call driver_cleanup here to perform a + // graceful shutdown of the JVM. However, doing this directly results in a + // nested bug report by ASan or UBSan, likely because something about the + // stack/thread context in which they generate reports is incompatible with + // the JVM shutdown process. use_sigaltstack=0 does not help though, so this + // might be on us. The alternative of calling driver_cleanup in a new thread + // and joining on it results in an endless wait in DestroyJavaVM, even when + // the main thread is detached beforehand - it is not clear why. + }); +} diff --git a/driver/fuzz_target_runner.h b/driver/fuzz_target_runner.h index ad4f6944..95d8ab1c 100644 --- a/driver/fuzz_target_runner.h +++ b/driver/fuzz_target_runner.h @@ -16,9 +16,24 @@ #pragma once +#include + #include #include namespace jazzer { std::vector fuzzTargetRunnerFlagsAsDefines(); + +/* + * Starts libFuzzer with the provided command-line arguments and runs the + * FuzzTargetRunner Java class in the provided JVM. + */ +int StartFuzzer(JNIEnv *env, int argc, char **argv); + +/* + * Print the stack traces of all active JVM threads. + * + * This function can be called from any thread. + */ +void DumpJvmStackTraces(); } // namespace jazzer diff --git a/driver/jazzer_main.cpp b/driver/jazzer_main.cpp new file mode 100644 index 00000000..a1fa0330 --- /dev/null +++ b/driver/jazzer_main.cpp @@ -0,0 +1,219 @@ +// 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. + +/* + * Jazzer's native main function, which: + * 1. defines default settings for ASan and UBSan; + * 2. preprocesses the command-line arguments passed to libFuzzer; + * 3. starts a JVM; + * 4. passes control to the fuzz target runner. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/strings/match.h" +#include "absl/strings/str_format.h" +#include "absl/strings/strip.h" +#include "driver/fuzz_target_runner.h" +#include "gflags/gflags.h" +#include "glog/logging.h" +#include "jvm_tooling.h" + +using namespace std::string_literals; + +// Defined by glog +DECLARE_bool(log_prefix); + +// Defined in libfuzzer_callbacks.cpp +DECLARE_bool(fake_pcs); + +// Defined in jvm_tooling.cpp +DECLARE_string(id_sync_file); + +// Defined in fuzz_target_runner.cpp +DECLARE_string(coverage_report); + +// Defined in fuzz_target_runner.cpp +DECLARE_string(coverage_dump); + +namespace { +bool is_asan_active = false; +} + +extern "C" { +[[maybe_unused]] const char *__asan_default_options() { + is_asan_active = true; + // LeakSanitizer is not yet supported as it reports too many false positives + // due to how the JVM GC works. + // We use a distinguished exit code to recognize ASan crashes in tests. + // Also specify abort_on_error=0 explicitly since ASan aborts rather than + // exits on macOS by default, which would cause our exit code to be ignored. + return "abort_on_error=0,detect_leaks=0,exitcode=76"; +} + +[[maybe_unused]] const char *__ubsan_default_options() { + // We use a distinguished exit code to recognize UBSan crashes in tests. + // Also specify abort_on_error=0 explicitly since UBSan aborts rather than + // exits on macOS by default, which would cause our exit code to be ignored. + return "abort_on_error=0,exitcode=76"; +} +} + +namespace { +const std::string kUsageMessage = + R"(Test java fuzz targets using libFuzzer. Usage: + jazzer --cp= --target_class= )"; + +std::unique_ptr<::jazzer::JVM> gJvm; + +std::string GetNewTempFilePath() { + auto temp_dir = std::filesystem::temp_directory_path(); + + std::string temp_filename_suffix(32, '\0'); + std::random_device rng; + std::uniform_int_distribution dist(0, 'z' - 'a'); + std::generate_n(temp_filename_suffix.begin(), temp_filename_suffix.length(), + [&rng, &dist] { return static_cast('a' + dist(rng)); }); + + auto temp_path = temp_dir / ("jazzer-" + temp_filename_suffix); + if (std::filesystem::exists(temp_path)) + throw std::runtime_error("Random temp file path exists: " + + temp_path.string()); + return temp_path.string(); +} +} // namespace + +int main(int argc, char **argv) { + gflags::SetUsageMessage(kUsageMessage); + // Disable glog log prefixes to mimic libFuzzer output. + FLAGS_log_prefix = false; + google::InitGoogleLogging(argv[0]); + rules_jni_init(argv[0]); + + const auto argv_end = argv + argc; + + // Parse libFuzzer flags to determine Jazzer flag defaults before letting + // gflags parse the command line. + if (std::find(argv, argv_end, "-use_value_profile=1"s) != argv_end) { + FLAGS_fake_pcs = true; + } + + // All libFuzzer flags start with a single dash, our arguments all start with + // a double dash. We can thus filter out the arguments meant for gflags by + // taking only those with a leading double dash. + std::vector our_args = {*argv}; + std::copy_if( + argv, argv_end, std::back_inserter(our_args), + [](const auto arg) { return absl::StartsWith(std::string(arg), "--"); }); + int our_argc = our_args.size(); + char **our_argv = our_args.data(); + // Let gflags consume its flags, but keep them in the argument list in case + // libFuzzer forwards the command line (e.g. with -jobs or -minimize_crash). + gflags::ParseCommandLineFlags(&our_argc, &our_argv, false); + + // The potentially modified command line arguments passed to libFuzzer at the + // end of this function. + std::vector modified_argv = std::vector(argv, argv_end); + + bool spawns_subprocesses = false; + if (std::any_of(argv, argv_end, [](std::string_view arg) { + return absl::StartsWith(arg, "-fork=") || + absl::StartsWith(arg, "-jobs=") || + absl::StartsWith(arg, "-merge="); + })) { + spawns_subprocesses = true; + if (!FLAGS_coverage_report.empty()) { + LOG(WARNING) << "WARN: --coverage_report does not support parallel " + "fuzzing and has been disabled"; + FLAGS_coverage_report = ""; + } + if (!FLAGS_coverage_dump.empty()) { + LOG(WARNING) << "WARN: --coverage_dump does not support parallel " + "fuzzing and has been disabled"; + FLAGS_coverage_dump = ""; + } + if (FLAGS_id_sync_file.empty()) { + // Create an empty temporary file used for coverage ID synchronization and + // pass its path to the agent in every child process. This requires adding + // the argument to argv for it to be picked up by libFuzzer, which then + // forwards it to child processes. + FLAGS_id_sync_file = GetNewTempFilePath(); + std::string new_arg = + absl::StrFormat("--id_sync_file=%s", FLAGS_id_sync_file); + // This argument can be accessed by libFuzzer at any (later) time and thus + // cannot be safely freed by us. + modified_argv.push_back(strdup(new_arg.c_str())); + } + // Creates the file, truncating it if it exists. + std::ofstream touch_file(FLAGS_id_sync_file, std::ios_base::trunc); + + auto cleanup_fn = [] { + try { + std::filesystem::remove(std::filesystem::path(FLAGS_id_sync_file)); + } catch (...) { + // We should not throw exceptions during shutdown. + } + }; + std::atexit(cleanup_fn); + } + + std::string seed; + // Search for the last occurence of a "-seed" argument as that is the one that + // is used by libFuzzer. + auto seed_pos = std::find_if( + std::reverse_iterator(argv_end), std::reverse_iterator(argv), + [](std::string_view arg) { return absl::StartsWith(arg, "-seed="); }); + if (seed_pos != std::reverse_iterator(argv)) { + // An explicit seed has been provided on the command-line, record its value + // so that it can be forwarded to the agent. + seed = absl::StripPrefix(*seed_pos, "-seed="); + } else { + // No explicit seed has been set. Since Jazzer hooks might still want to use + // a seed and we have to ensure that a fuzzing run can be reproduced by + // setting the seed printed by libFuzzer, we generate a seed for it here so + // that the two stay in sync. + unsigned int random_seed = std::random_device()(); + seed = std::to_string(random_seed); + // Only add the -seed argument to the command line if not running in a mode + // that spawns subprocesses. These would inherit the same seed, which might + // make them less effective. + if (!spawns_subprocesses) { + std::string seed_arg = "-seed=" + seed; + // This argument can be accessed by libFuzzer at any (later) time and thus + // cannot be safely freed by us. + modified_argv.push_back(strdup(seed_arg.c_str())); + } + } + // Terminate modified_argv. + int modified_argc = modified_argv.size(); + modified_argv.push_back(nullptr); + + if (is_asan_active) { + std::cerr << "WARN: Jazzer is not compatible with LeakSanitizer yet. Leaks " + "are not reported." + << std::endl; + } + + gJvm = std::make_unique(argv[0], seed); + return jazzer::StartFuzzer(&gJvm->GetEnv(), modified_argc, + modified_argv.data()); +} diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index b8eccb5d..cca58f99 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -107,43 +107,10 @@ JNI_OnLoad_jazzer_initialize(JavaVM *vm, void *) { namespace { constexpr auto kAgentBazelRunfilesPath = "jazzer/agent/jazzer_agent_deploy.jar"; constexpr auto kAgentFileName = "jazzer_agent_deploy.jar"; -constexpr const char kExceptionUtilsClassName[] = - "com/code_intelligence/jazzer/runtime/ExceptionUtils"; } // namespace namespace jazzer { -void DumpJvmStackTraces() { - JavaVM *vm; - jsize num_vms; - JNI_GetCreatedJavaVMs(&vm, 1, &num_vms); - if (num_vms != 1) { - return; - } - JNIEnv *env = nullptr; - if (vm->AttachCurrentThread(reinterpret_cast(&env), nullptr) != - JNI_OK) { - return; - } - jclass exceptionUtils = env->FindClass(kExceptionUtilsClassName); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - return; - } - jmethodID dumpStack = - env->GetStaticMethodID(exceptionUtils, "dumpAllStackTraces", "()V"); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - return; - } - env->CallStaticVoidMethod(exceptionUtils, dumpStack); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - return; - } - // Do not detach as we may be the main thread (but the JVM exits anyway). -} - std::string_view dirFromFullPath(std::string_view path) { const auto pos = path.rfind(kPathSeparator); if (pos != std::string_view::npos) { diff --git a/driver/libfuzzer_driver.cpp b/driver/libfuzzer_driver.cpp deleted file mode 100644 index 25bec5da..00000000 --- a/driver/libfuzzer_driver.cpp +++ /dev/null @@ -1,245 +0,0 @@ -// 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 "libfuzzer_driver.h" - -#include - -#include -#include -#include -#include -#include -#include - -#include "absl/strings/match.h" -#include "absl/strings/str_format.h" -#include "absl/strings/strip.h" -#include "fuzzed_data_provider.h" -#include "gflags/gflags.h" -#include "glog/logging.h" -#include "jvm_tooling.h" - -using namespace std::string_literals; - -// Defined by glog -DECLARE_bool(log_prefix); - -// Defined in libfuzzer_callbacks.cpp -DECLARE_bool(fake_pcs); - -// Defined in jvm_tooling.cpp -DECLARE_string(id_sync_file); - -// Defined in fuzz_target_runner.cpp -DECLARE_string(coverage_report); - -// Defined in fuzz_target_runner.cpp -DECLARE_string(coverage_dump); - -namespace { -constexpr auto kFuzzTargetRunnerClassName = - "com/code_intelligence/jazzer/driver/FuzzTargetRunner"; - -bool gUseFuzzedDataProvider; -jclass gRunner; -jmethodID gRunOneId; - -std::vector modified_argv; - -std::string GetNewTempFilePath() { - auto temp_dir = std::filesystem::temp_directory_path(); - - std::string temp_filename_suffix(32, '\0'); - std::random_device rng; - std::uniform_int_distribution dist(0, 'z' - 'a'); - std::generate_n(temp_filename_suffix.begin(), temp_filename_suffix.length(), - [&rng, &dist] { return static_cast('a' + dist(rng)); }); - - auto temp_path = temp_dir / ("jazzer-" + temp_filename_suffix); - if (std::filesystem::exists(temp_path)) - throw std::runtime_error("Random temp file path exists: " + - temp_path.string()); - return temp_path.string(); -} -} // namespace - -namespace jazzer { -// A libFuzzer-registered callback that outputs the crashing input, but does -// not include a stack trace. -void (*AbstractLibfuzzerDriver::libfuzzer_print_crashing_input_)() = nullptr; - -AbstractLibfuzzerDriver::AbstractLibfuzzerDriver( - int *argc, char ***argv, const std::string &usage_string) { - gflags::SetUsageMessage(usage_string); - // Disable glog log prefixes to mimic libFuzzer output. - FLAGS_log_prefix = false; - google::InitGoogleLogging((*argv)[0]); - rules_jni_init((*argv)[0]); - - const auto argv_start = *argv; - const auto argv_end = *argv + *argc; - - // Parse libFuzzer flags to determine Jazzer flag defaults before letting - // gflags parse the command line. - if (std::find(argv_start, argv_end, "-use_value_profile=1"s) != argv_end) { - FLAGS_fake_pcs = true; - } - - // All libFuzzer flags start with a single dash, our arguments all start with - // a double dash. We can thus filter out the arguments meant for gflags by - // taking only those with a leading double dash. - std::vector our_args = {*argv_start}; - std::copy_if( - argv_start, argv_end, std::back_inserter(our_args), - [](const auto arg) { return absl::StartsWith(std::string(arg), "--"); }); - int our_argc = our_args.size(); - char **our_argv = our_args.data(); - // Let gflags consume its flags, but keep them in the argument list in case - // libFuzzer forwards the command line (e.g. with -jobs or -minimize_crash). - gflags::ParseCommandLineFlags(&our_argc, &our_argv, false); - - // Perform modifications of the command line arguments passed to libFuzzer if - // necessary by modifying this copy, which will be made the regular argv at - // the end of this function. - modified_argv = std::vector(argv_start, argv_end); - - bool spawns_subprocesses = false; - if (std::any_of(argv_start, argv_end, [](std::string_view arg) { - return absl::StartsWith(arg, "-fork=") || - absl::StartsWith(arg, "-jobs=") || - absl::StartsWith(arg, "-merge="); - })) { - spawns_subprocesses = true; - if (!FLAGS_coverage_report.empty()) { - LOG(WARNING) << "WARN: --coverage_report does not support parallel " - "fuzzing and has been disabled"; - FLAGS_coverage_report = ""; - } - if (!FLAGS_coverage_dump.empty()) { - LOG(WARNING) << "WARN: --coverage_dump does not support parallel " - "fuzzing and has been disabled"; - FLAGS_coverage_dump = ""; - } - if (FLAGS_id_sync_file.empty()) { - // Create an empty temporary file used for coverage ID synchronization and - // pass its path to the agent in every child process. This requires adding - // the argument to argv for it to be picked up by libFuzzer, which then - // forwards it to child processes. - FLAGS_id_sync_file = GetNewTempFilePath(); - std::string new_arg = - absl::StrFormat("--id_sync_file=%s", FLAGS_id_sync_file); - // This argument can be accessed by libFuzzer at any (later) time and thus - // cannot be safely freed by us. - modified_argv.push_back(strdup(new_arg.c_str())); - *argc += 1; - } - // Creates the file, truncating it if it exists. - std::ofstream touch_file(FLAGS_id_sync_file, std::ios_base::trunc); - - auto cleanup_fn = [] { - try { - std::filesystem::remove(std::filesystem::path(FLAGS_id_sync_file)); - } catch (...) { - // We should not throw exceptions during shutdown. - } - }; - std::atexit(cleanup_fn); - } - - std::string seed; - // Search for the last occurence of a "-seed" argument as that is the one that - // is used by libFuzzer. - auto seed_pos = std::find_if( - std::reverse_iterator(argv_end), std::reverse_iterator(argv_start), - [](std::string_view arg) { return absl::StartsWith(arg, "-seed="); }); - if (seed_pos != std::reverse_iterator(argv_start)) { - // An explicit seed has been provided on the command-line, record its value - // so that it can be forwarded to the agent. - seed = absl::StripPrefix(*seed_pos, "-seed="); - } else { - // No explicit seed has been set. Since Jazzer hooks might still want to use - // a seed and we have to ensure that a fuzzing run can be reproduced by - // setting the seed printed by libFuzzer, we generate a seed for it here so - // that the two stay in sync. - unsigned int random_seed = std::random_device()(); - seed = std::to_string(random_seed); - // Only add the -seed argument to the command line if not running in a mode - // that spawns subprocesses. These would inherit the same seed, which might - // make them less effective. - if (!spawns_subprocesses) { - std::string seed_arg = "-seed=" + seed; - // This argument can be accessed by libFuzzer at any (later) time and thus - // cannot be safely freed by us. - modified_argv.push_back(strdup(seed_arg.c_str())); - *argc += 1; - } - } - - // Terminate modified_argv. - modified_argv.push_back(nullptr); - // Modify argv and argc for libFuzzer. modified_argv must not be changed - // after this point. - *argv = modified_argv.data(); - - initJvm(**argv, seed); -} - -void AbstractLibfuzzerDriver::initJvm(std::string_view executable_path, - std::string_view seed) { - jvm_ = std::make_unique(executable_path, seed); -} - -LibfuzzerDriver::LibfuzzerDriver(int *argc, char ***argv) - : AbstractLibfuzzerDriver(argc, argv, getUsageString()) { - JNIEnv &env = jvm_->GetEnv(); - jclass runner = env.FindClass(kFuzzTargetRunnerClassName); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - _Exit(1); - } - gRunner = reinterpret_cast(env.NewGlobalRef(runner)); - gRunOneId = env.GetStaticMethodID(runner, "runOne", "([B)I"); - jfieldID use_fuzzed_data_provider_id = - env.GetStaticFieldID(runner, "useFuzzedDataProvider", "Z"); - gUseFuzzedDataProvider = - env.GetStaticBooleanField(runner, use_fuzzed_data_provider_id); -} - -std::string LibfuzzerDriver::getUsageString() { - return R"(Test java fuzz targets using libFuzzer. Usage: - jazzer --cp= --target_class= )"; -} - -int LibfuzzerDriver::TestOneInput(const uint8_t *data, const std::size_t size) { - JNIEnv &env = jvm_->GetEnv(); - jbyteArray input = nullptr; - jint jsize = - std::min(size, static_cast(std::numeric_limits::max())); - if (gUseFuzzedDataProvider) { - ::jazzer::FeedFuzzedDataProvider(data, size); - } else { - input = env.NewByteArray(jsize); - env.SetByteArrayRegion(input, 0, jsize, - reinterpret_cast(data)); - } - int res = env.CallStaticIntMethod(gRunner, gRunOneId, input); - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - _Exit(1); - } - return res; -} - -} // namespace jazzer diff --git a/driver/libfuzzer_driver.h b/driver/libfuzzer_driver.h deleted file mode 100644 index 14af2a7a..00000000 --- a/driver/libfuzzer_driver.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include -#include - -#include "jvm_tooling.h" - -namespace jazzer { - -class AbstractLibfuzzerDriver { - public: - AbstractLibfuzzerDriver(int *argc, char ***argv, - const std::string &usage_string); - - virtual ~AbstractLibfuzzerDriver() = default; - - virtual int TestOneInput(const uint8_t *data, std::size_t size) = 0; - - // A libFuzzer-registered callback that outputs the crashing input, but does - // not include a stack trace. - static void (*libfuzzer_print_crashing_input_)(); - - protected: - // wrapper around the running jvm instance - std::unique_ptr jvm_; - - private: - void initJvm(std::string_view executable_path, std::string_view seed); -}; - -class LibfuzzerDriver : public AbstractLibfuzzerDriver { - public: - LibfuzzerDriver(int *argc, char ***argv); - - int TestOneInput(const uint8_t *data, std::size_t size) override; - - ~LibfuzzerDriver() override = default; - - private: - static std::string getUsageString(); -}; - -} // namespace jazzer diff --git a/driver/libfuzzer_fuzz_target.cpp b/driver/libfuzzer_fuzz_target.cpp deleted file mode 100644 index 58380a24..00000000 --- a/driver/libfuzzer_fuzz_target.cpp +++ /dev/null @@ -1,106 +0,0 @@ -// 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 - -#include "com_code_intelligence_jazzer_driver_FuzzTargetRunner.h" -#include "libfuzzer_driver.h" - -namespace { -bool is_asan_active = false; -} - -extern "C" { -[[maybe_unused]] const char *__asan_default_options() { - is_asan_active = true; - // LeakSanitizer is not yet supported as it reports too many false positives - // due to how the JVM GC works. - // We use a distinguished exit code to recognize ASan crashes in tests. - // Also specify abort_on_error=0 explicitly since ASan aborts rather than - // exits on macOS by default, which would cause our exit code to be ignored. - return "abort_on_error=0,detect_leaks=0,exitcode=76"; -} - -[[maybe_unused]] const char *__ubsan_default_options() { - // We use a distinguished exit code to recognize UBSan crashes in tests. - // Also specify abort_on_error=0 explicitly since UBSan aborts rather than - // exits on macOS by default, which would cause our exit code to be ignored. - return "abort_on_error=0,exitcode=76"; -} -} - -namespace { -using Driver = jazzer::LibfuzzerDriver; - -std::unique_ptr gLibfuzzerDriver; -} // namespace - -extern "C" void driver_cleanup() { - // Free the libfuzzer driver which triggers a clean JVM shutdown. - gLibfuzzerDriver.reset(nullptr); -} - -// This symbol is defined by sanitizers if linked into Jazzer or in -// sanitizer_symbols.cpp if no sanitizer is used. -extern "C" void __sanitizer_set_death_callback(void (*)()); - -// We apply a patch to libFuzzer to make it call this function instead of -// __sanitizer_set_death_callback to pass us the death callback. -extern "C" [[maybe_unused]] void __jazzer_set_death_callback( - void (*callback)()) { - jazzer::AbstractLibfuzzerDriver::libfuzzer_print_crashing_input_ = callback; - __sanitizer_set_death_callback([]() { - jazzer::DumpJvmStackTraces(); - jazzer::AbstractLibfuzzerDriver::libfuzzer_print_crashing_input_(); - // Ideally, we would be able to call driver_cleanup here to perform a - // graceful shutdown of the JVM. However, doing this directly results in a - // nested bug report by ASan or UBSan, likely because something about the - // stack/thread context in which they generate reports is incompatible with - // the JVM shutdown process. use_sigaltstack=0 does not help though, so this - // might be on us. The alternative of calling driver_cleanup in a new thread - // and joining on it results in an endless wait in DestroyJavaVM, even when - // the main thread is detached beforehand - it is not clear why. - }); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner_printCrashingInput( - JNIEnv *, jclass) { - jazzer::AbstractLibfuzzerDriver::libfuzzer_print_crashing_input_(); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner__1Exit( - JNIEnv *, jclass, jint exit_code) { - _Exit(exit_code); -} - -// Entry point called by libfuzzer before any LLVMFuzzerTestOneInput(...) -// invocations. -extern "C" [[maybe_unused]] int LLVMFuzzerInitialize(int *argc, char ***argv) { - if (is_asan_active) { - std::cerr << "WARN: Jazzer is not compatible with LeakSanitizer yet. Leaks " - "are not reported." - << std::endl; - } - gLibfuzzerDriver = std::make_unique(argc, argv); - std::atexit(&driver_cleanup); - return 0; -} - -// Called by the fuzzer for every fuzzing input. -extern "C" [[maybe_unused]] int LLVMFuzzerTestOneInput(const uint8_t *data, - const size_t size) { - return gLibfuzzerDriver->TestOneInput(data, size); -} diff --git a/driver/sanitizer_symbols.cpp b/driver/sanitizer_symbols.cpp index c009fcbe..bd28d104 100644 --- a/driver/sanitizer_symbols.cpp +++ b/driver/sanitizer_symbols.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Called in libfuzzer_driver.cpp. +// Called in fuzz_target_runner.cpp. extern "C" void __sanitizer_set_death_callback(void (*)()) {} // Suppress libFuzzer warnings about missing sanitizer methods in non-sanitizer diff --git a/driver/sanitizer_symbols_for_tests.cpp b/driver/sanitizer_symbols_for_tests.cpp index 62623f5f..5cbd862d 100644 --- a/driver/sanitizer_symbols_for_tests.cpp +++ b/driver/sanitizer_symbols_for_tests.cpp @@ -16,7 +16,7 @@ #include // Symbols exported by libFuzzer that are required by libfuzzer_callbacks and -// CoverageTracker. +// coverage_tracker and fuzz_target_runner. extern "C" { void __sanitizer_cov_8bit_counters_init(uint8_t *start, uint8_t *end) {} void __sanitizer_cov_pcs_init(const uintptr_t *pcs_beg, @@ -38,4 +38,8 @@ void __sanitizer_cov_trace_div8(uint64_t val) {} void __sanitizer_cov_trace_gep(uintptr_t idx) {} void __sanitizer_cov_trace_pc_indir(uintptr_t callee) {} void __sanitizer_set_death_callback(void (*callback)()) {} +int LLVMFuzzerRunDriver(int *argc, char ***argv, + int (*UserCb)(const uint8_t *Data, size_t Size)) { + return 0; +} } diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index 9e465aa2..a6bd9505 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -211,6 +211,10 @@ public final class FuzzTargetRunner { return 0; } + public static void dumpAllStackTraces() { + ExceptionUtils.dumpAllStackTraces(); + } + private static void shutdown() { if (!Opt.coverageDump.isEmpty() || !Opt.coverageReport.isEmpty()) { int[] everCoveredIds = CoverageMap.getEverCoveredIds(); diff --git a/third_party/libFuzzer.BUILD b/third_party/libFuzzer.BUILD index bdb0ee56..bf902f21 100644 --- a/third_party/libFuzzer.BUILD +++ b/third_party/libFuzzer.BUILD @@ -1,8 +1,8 @@ cc_library( - name = "libFuzzer", + name = "libfuzzer_no_main", srcs = glob([ "*.cpp", - ]), + ], exclude = ["FuzzerMain.cpp"]), hdrs = glob([ "*.h", "*.def", -- cgit v1.2.3 From d47477729fad2016d6eb81101532085120da7153 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 8 Aug 2022 13:51:11 +0200 Subject: driver: Remove unused SHA1 helper Inline the remaining single constant into jvm_tooling.cpp. --- driver/BUILD.bazel | 10 +-- driver/jvm_tooling.cpp | 5 +- driver/utils.cpp | 208 ------------------------------------------------- driver/utils.h | 32 -------- 4 files changed, 5 insertions(+), 250 deletions(-) delete mode 100644 driver/utils.cpp delete mode 100644 driver/utils.h diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index 3db417e4..2d25ccc6 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -151,14 +151,8 @@ cc_library( cc_library( name = "jvm_tooling_lib", - srcs = [ - "jvm_tooling.cpp", - "utils.cpp", - "utils.h", - ], - hdrs = [ - "jvm_tooling.h", - ], + srcs = ["jvm_tooling.cpp"], + hdrs = ["jvm_tooling.h"], # Needs to be linked statically for JNI_OnLoad_jazzer_initialize to be found # by the JVM. linkstatic = True, diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index cca58f99..63d2d5a5 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -29,7 +29,6 @@ #include "gflags/gflags.h" #include "glog/logging.h" #include "tools/cpp/runfiles/runfiles.h" -#include "utils.h" DEFINE_string(cp, ".", "the classpath to use for fuzzing. Behaves analogously to java's " @@ -93,10 +92,12 @@ DEFINE_bool(hooks, true, "coverage information will be processed. This can be useful for " "running a regression test on non-instrumented bytecode."); -#ifdef _WIN32 +#if defined(_WIN32) || defined(_WIN64) #define ARG_SEPARATOR ";" +constexpr auto kPathSeparator = '\\'; #else #define ARG_SEPARATOR ":" +constexpr auto kPathSeparator = '/'; #endif extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL diff --git a/driver/utils.cpp b/driver/utils.cpp deleted file mode 100644 index 4d8042e3..00000000 --- a/driver/utils.cpp +++ /dev/null @@ -1,208 +0,0 @@ -// 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 "utils.h" - -#include -#include -#include -#include -#include - -namespace { -// BEGIN: Obtained from https://github.com/x42/liboauth/blob/master/src/sha1.c: -/* This code is public-domain - it is based on libcrypt - * placed in the public domain by Wei Dai and other contributors. - */ - -#ifdef __BIG_ENDIAN__ -#define SHA_BIG_ENDIAN -#elif defined __LITTLE_ENDIAN__ -/* override */ -#elif defined __BYTE_ORDER -#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ -#define SHA_BIG_ENDIAN -#endif -#else // ! defined __LITTLE_ENDIAN__ -#include // machine/endian.h -#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ -#define SHA_BIG_ENDIAN -#endif -#endif - -/* header */ - -#define HASH_LENGTH 20 -#define BLOCK_LENGTH 64 - -typedef struct sha1nfo { - uint32_t buffer[BLOCK_LENGTH / 4]; - uint32_t state[HASH_LENGTH / 4]; - uint32_t byteCount; - uint8_t bufferOffset; - uint8_t keyBuffer[BLOCK_LENGTH]; - uint8_t innerHash[HASH_LENGTH]; -} sha1nfo; - -/* public API - prototypes - TODO: doxygen*/ - -/** - */ -void sha1_init(sha1nfo *s); -/** - */ -void sha1_writebyte(sha1nfo *s, uint8_t data); -/** - */ -void sha1_write(sha1nfo *s, const char *data, size_t len); -/** - */ -uint8_t *sha1_result(sha1nfo *s); - -/* code */ -#define SHA1_K0 0x5a827999 -#define SHA1_K20 0x6ed9eba1 -#define SHA1_K40 0x8f1bbcdc -#define SHA1_K60 0xca62c1d6 - -void sha1_init(sha1nfo *s) { - s->state[0] = 0x67452301; - s->state[1] = 0xefcdab89; - s->state[2] = 0x98badcfe; - s->state[3] = 0x10325476; - s->state[4] = 0xc3d2e1f0; - s->byteCount = 0; - s->bufferOffset = 0; -} - -uint32_t sha1_rol32(uint32_t number, uint8_t bits) { - return ((number << bits) | (number >> (32 - bits))); -} - -void sha1_hashBlock(sha1nfo *s) { - uint8_t i; - uint32_t a, b, c, d, e, t; - - a = s->state[0]; - b = s->state[1]; - c = s->state[2]; - d = s->state[3]; - e = s->state[4]; - for (i = 0; i < 80; i++) { - if (i >= 16) { - t = s->buffer[(i + 13) & 15] ^ s->buffer[(i + 8) & 15] ^ - s->buffer[(i + 2) & 15] ^ s->buffer[i & 15]; - s->buffer[i & 15] = sha1_rol32(t, 1); - } - if (i < 20) { - t = (d ^ (b & (c ^ d))) + SHA1_K0; - } else if (i < 40) { - t = (b ^ c ^ d) + SHA1_K20; - } else if (i < 60) { - t = ((b & c) | (d & (b | c))) + SHA1_K40; - } else { - t = (b ^ c ^ d) + SHA1_K60; - } - t += sha1_rol32(a, 5) + e + s->buffer[i & 15]; - e = d; - d = c; - c = sha1_rol32(b, 30); - b = a; - a = t; - } - s->state[0] += a; - s->state[1] += b; - s->state[2] += c; - s->state[3] += d; - s->state[4] += e; -} - -void sha1_addUncounted(sha1nfo *s, uint8_t data) { - uint8_t *const b = (uint8_t *)s->buffer; -#ifdef SHA_BIG_ENDIAN - b[s->bufferOffset] = data; -#else - b[s->bufferOffset ^ 3] = data; -#endif - s->bufferOffset++; - if (s->bufferOffset == BLOCK_LENGTH) { - sha1_hashBlock(s); - s->bufferOffset = 0; - } -} - -void sha1_writebyte(sha1nfo *s, uint8_t data) { - ++s->byteCount; - sha1_addUncounted(s, data); -} - -void sha1_write(sha1nfo *s, const char *data, size_t len) { - for (; len--;) sha1_writebyte(s, (uint8_t)*data++); -} - -void sha1_pad(sha1nfo *s) { - // Implement SHA-1 padding (fips180-2 §5.1.1) - - // Pad with 0x80 followed by 0x00 until the end of the block - sha1_addUncounted(s, 0x80); - while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00); - - // Append length in the last 8 bytes - sha1_addUncounted(s, 0); // We're only using 32 bit lengths - sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths - sha1_addUncounted(s, 0); // So zero pad the top bits - sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8 - sha1_addUncounted( - s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as - sha1_addUncounted(s, s->byteCount >> 13); // byte. - sha1_addUncounted(s, s->byteCount >> 5); - sha1_addUncounted(s, s->byteCount << 3); -} - -uint8_t *sha1_result(sha1nfo *s) { - // Pad to complete the last block - sha1_pad(s); - -#ifndef SHA_BIG_ENDIAN - // Swap byte order back - int i; - for (i = 0; i < 5; i++) { - s->state[i] = (((s->state[i]) << 24) & 0xff000000) | - (((s->state[i]) << 8) & 0x00ff0000) | - (((s->state[i]) >> 8) & 0x0000ff00) | - (((s->state[i]) >> 24) & 0x000000ff); - } -#endif - - // Return pointer to hash (20 characters) - return (uint8_t *)s->state; -} -// END: Obtained from https://github.com/x42/liboauth/blob/master/src/sha1.c: -} // namespace - -namespace jazzer { -std::string Sha1Hash(const uint8_t *data, size_t size) { - sha1nfo hasher; - sha1_init(&hasher); - sha1_write(&hasher, reinterpret_cast(data), size); - const uint8_t *hash = sha1_result(&hasher); - std::ostringstream out; - for (size_t i = 0; i < HASH_LENGTH; ++i) { - // Cast required because uint8_t would print as a char. - out << std::hex << std::setfill('0') << std::setw(2) - << static_cast(hash[i]); - } - return out.str(); -} -} // namespace jazzer diff --git a/driver/utils.h b/driver/utils.h deleted file mode 100644 index 99d7b60e..00000000 --- a/driver/utils.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include -#include -#include - -namespace jazzer { - -#if defined(_WIN32) || defined(_WIN64) -constexpr auto kPathSeparator = '\\'; -#else -constexpr auto kPathSeparator = '/'; -#endif - -std::string Sha1Hash(const uint8_t *data, size_t size); -} // namespace jazzer -- cgit v1.2.3 From e14430e2f0b2a4a73844d4c36bf1f98bbf4a3e22 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 8 Aug 2022 15:55:57 +0200 Subject: driver: Remove gflags dependency of fuzz_target_runner Achieved by inlining the flags into jvm_tooling, which is the only remaining consumer of these flags. --- driver/BUILD.bazel | 4 --- driver/fuzz_target_runner.cpp | 67 +---------------------------------------- driver/fuzz_target_runner.h | 2 -- driver/jvm_tooling.cpp | 70 +++++++++++++++++++++++++++++++++++++++---- 4 files changed, 66 insertions(+), 77 deletions(-) diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index 2d25ccc6..1c9edc10 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -142,10 +142,7 @@ cc_library( deps = [ ":fuzzed_data_provider", "//driver/src/main/java/com/code_intelligence/jazzer/driver:fuzz_target_runner.hdrs", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/strings:str_format", "@fmeum_rules_jni//jni", - "@jazzer_com_github_gflags_gflags//:gflags", ], ) @@ -161,7 +158,6 @@ cc_library( "manual", ], deps = [ - ":fuzz_target_runner", "@bazel_tools//tools/cpp/runfiles", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index 14268f80..c68511a8 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -21,58 +21,11 @@ #include +#include #include -#include -#include "absl/strings/str_format.h" #include "com_code_intelligence_jazzer_driver_FuzzTargetRunner.h" #include "driver/fuzzed_data_provider.h" -#include "gflags/gflags.h" - -DEFINE_string( - target_class, "", - "The Java class that contains the static fuzzerTestOneInput function"); -DEFINE_string(target_args, "", - "Arguments passed to fuzzerInitialize as a String array. " - "Separated by space."); - -DEFINE_uint32(keep_going, 0, - "Continue fuzzing until N distinct exception stack traces have" - "been encountered. Defaults to exit after the first finding " - "unless --autofuzz is specified."); -DEFINE_bool(dedup, true, - "Emit a dedup token for every finding. Defaults to true and is " - "required for --keep_going and --ignore."); -DEFINE_string( - ignore, "", - "Comma-separated list of crash dedup tokens to ignore. This is useful to " - "continue fuzzing before a crash is fixed."); - -DEFINE_string(reproducer_path, ".", - "Path at which fuzzing reproducers are stored. Defaults to the " - "current directory."); -DEFINE_string(coverage_report, "", - "Path at which a coverage report is stored when the fuzzer " - "exits. If left empty, no report is generated (default)"); -DEFINE_string(coverage_dump, "", - "Path at which a coverage dump is stored when the fuzzer " - "exits. If left empty, no dump is generated (default)"); - -DEFINE_string(autofuzz, "", - "Fully qualified reference to a method on the classpath that " - "should be fuzzed automatically (example: System.out::println). " - "Fuzzing will continue even after a finding; specify " - "--keep_going=N to stop after N findings."); -DEFINE_string(autofuzz_ignore, "", - "Fully qualified class names of exceptions to ignore during " - "autofuzz. Separated by comma."); -DEFINE_bool( - fake_pcs, false, - "Supply synthetic Java program counters to libFuzzer trace hooks to " - "make value profiling more effective. Enabled by default if " - "-use_value_profile=1 is specified."); - -DECLARE_bool(hooks); extern "C" int LLVMFuzzerRunDriver(int *argc, char ***argv, int (*UserCb)(const uint8_t *Data, @@ -113,24 +66,6 @@ int testOneInput(const uint8_t *data, const std::size_t size) { } // namespace namespace jazzer { -std::vector fuzzTargetRunnerFlagsAsDefines() { - return { - absl::StrFormat("-Djazzer.target_class=%s", FLAGS_target_class), - absl::StrFormat("-Djazzer.target_args=%s", FLAGS_target_args), - absl::StrFormat("-Djazzer.keep_going=%d", FLAGS_keep_going), - absl::StrFormat("-Djazzer.dedup=%s", FLAGS_dedup ? "true" : "false"), - absl::StrFormat("-Djazzer.ignore=%s", FLAGS_ignore), - absl::StrFormat("-Djazzer.reproducer_path=%s", FLAGS_reproducer_path), - absl::StrFormat("-Djazzer.coverage_report=%s", FLAGS_coverage_report), - absl::StrFormat("-Djazzer.coverage_dump=%s", FLAGS_coverage_dump), - absl::StrFormat("-Djazzer.autofuzz=%s", FLAGS_autofuzz), - absl::StrFormat("-Djazzer.autofuzz_ignore=%s", FLAGS_autofuzz_ignore), - absl::StrFormat("-Djazzer.hooks=%s", FLAGS_hooks ? "true" : "false"), - absl::StrFormat("-Djazzer.fake_pcs=%s", - FLAGS_fake_pcs ? "true" : "false"), - }; -} - int StartFuzzer(JNIEnv *env, int argc, char **argv) { gEnv = env; jclass runner = env->FindClass(kFuzzTargetRunnerClassName); diff --git a/driver/fuzz_target_runner.h b/driver/fuzz_target_runner.h index 95d8ab1c..fbc3f1e6 100644 --- a/driver/fuzz_target_runner.h +++ b/driver/fuzz_target_runner.h @@ -22,8 +22,6 @@ #include namespace jazzer { -std::vector fuzzTargetRunnerFlagsAsDefines(); - /* * Starts libFuzzer with the provided command-line arguments and runs the * FuzzTargetRunner Java class in the provided JVM. diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index 63d2d5a5..ad5e0ad9 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -25,7 +25,6 @@ #include "absl/strings/str_join.h" #include "absl/strings/str_replace.h" #include "absl/strings/str_split.h" -#include "fuzz_target_runner.h" #include "gflags/gflags.h" #include "glog/logging.h" #include "tools/cpp/runfiles/runfiles.h" @@ -92,6 +91,49 @@ DEFINE_bool(hooks, true, "coverage information will be processed. This can be useful for " "running a regression test on non-instrumented bytecode."); +DEFINE_string( + target_class, "", + "The Java class that contains the static fuzzerTestOneInput function"); +DEFINE_string(target_args, "", + "Arguments passed to fuzzerInitialize as a String array. " + "Separated by space."); + +DEFINE_uint32(keep_going, 0, + "Continue fuzzing until N distinct exception stack traces have" + "been encountered. Defaults to exit after the first finding " + "unless --autofuzz is specified."); +DEFINE_bool(dedup, true, + "Emit a dedup token for every finding. Defaults to true and is " + "required for --keep_going and --ignore."); +DEFINE_string( + ignore, "", + "Comma-separated list of crash dedup tokens to ignore. This is useful to " + "continue fuzzing before a crash is fixed."); + +DEFINE_string(reproducer_path, ".", + "Path at which fuzzing reproducers are stored. Defaults to the " + "current directory."); +DEFINE_string(coverage_report, "", + "Path at which a coverage report is stored when the fuzzer " + "exits. If left empty, no report is generated (default)"); +DEFINE_string(coverage_dump, "", + "Path at which a coverage dump is stored when the fuzzer " + "exits. If left empty, no dump is generated (default)"); + +DEFINE_string(autofuzz, "", + "Fully qualified reference to a method on the classpath that " + "should be fuzzed automatically (example: System.out::println). " + "Fuzzing will continue even after a finding; specify " + "--keep_going=N to stop after N findings."); +DEFINE_string(autofuzz_ignore, "", + "Fully qualified class names of exceptions to ignore during " + "autofuzz. Separated by comma."); +DEFINE_bool( + fake_pcs, false, + "Supply synthetic Java program counters to libFuzzer trace hooks to " + "make value profiling more effective. Enabled by default if " + "-use_value_profile=1 is specified."); + #if defined(_WIN32) || defined(_WIN64) #define ARG_SEPARATOR ";" constexpr auto kPathSeparator = '\\'; @@ -108,9 +150,6 @@ JNI_OnLoad_jazzer_initialize(JavaVM *vm, void *) { namespace { constexpr auto kAgentBazelRunfilesPath = "jazzer/agent/jazzer_agent_deploy.jar"; constexpr auto kAgentFileName = "jazzer_agent_deploy.jar"; -} // namespace - -namespace jazzer { std::string_view dirFromFullPath(std::string_view path) { const auto pos = path.rfind(kPathSeparator); @@ -178,6 +217,24 @@ std::string agentArgsFromFlags() { return absl::StrJoin(args, ","); } +std::vector fuzzTargetRunnerFlagsAsDefines() { + return { + absl::StrFormat("-Djazzer.target_class=%s", FLAGS_target_class), + absl::StrFormat("-Djazzer.target_args=%s", FLAGS_target_args), + absl::StrFormat("-Djazzer.keep_going=%d", FLAGS_keep_going), + absl::StrFormat("-Djazzer.dedup=%s", FLAGS_dedup ? "true" : "false"), + absl::StrFormat("-Djazzer.ignore=%s", FLAGS_ignore), + absl::StrFormat("-Djazzer.reproducer_path=%s", FLAGS_reproducer_path), + absl::StrFormat("-Djazzer.coverage_report=%s", FLAGS_coverage_report), + absl::StrFormat("-Djazzer.coverage_dump=%s", FLAGS_coverage_dump), + absl::StrFormat("-Djazzer.autofuzz=%s", FLAGS_autofuzz), + absl::StrFormat("-Djazzer.autofuzz_ignore=%s", FLAGS_autofuzz_ignore), + absl::StrFormat("-Djazzer.hooks=%s", FLAGS_hooks ? "true" : "false"), + absl::StrFormat("-Djazzer.fake_pcs=%s", + FLAGS_fake_pcs ? "true" : "false"), + }; +} + // Splits a string at the ARG_SEPARATOR unless it is escaped with a backslash. // Backslash itself can be escaped with another backslash. std::vector splitEscaped(const std::string &str) { @@ -205,6 +262,9 @@ std::vector splitEscaped(const std::string &str) { return parts; } +} // namespace + +namespace jazzer { JVM::JVM(std::string_view executable_path, std::string_view seed) { // combine class path from command line flags and JAVA_FUZZER_CLASSPATH env @@ -245,7 +305,7 @@ JVM::JVM(std::string_view executable_path, std::string_view seed) { JavaVMOption{.optionString = const_cast(seed_property.c_str())}); std::vector fuzz_target_runner_defines = - ::jazzer::fuzzTargetRunnerFlagsAsDefines(); + fuzzTargetRunnerFlagsAsDefines(); for (const auto &define : fuzz_target_runner_defines) { options.push_back( JavaVMOption{.optionString = const_cast(define.c_str())}); -- cgit v1.2.3 From 0ab5240d6d1beb8672fba9efe99a937f28928a82 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 8 Aug 2022 15:57:32 +0200 Subject: tests: Speculative fix for ForkModeFuzzer flakiness on Windows --- tests/BUILD.bazel | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index ea5a3b0b..95d5f209 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -73,7 +73,7 @@ java_fuzz_target_test( }, expected_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow"], fuzzer_args = [ - "-fork=3", + "-fork=2", "--additional_jvm_args=-Dbaz=baz", ] + select({ # \\\\ becomes \\ when evaluated as a Starlark string literal, then \ in @@ -81,6 +81,8 @@ java_fuzz_target_test( "@platforms//os:windows": ["--jvm_args=-Dfoo=foo;-Dbar=b\\\\;ar"], "//conditions:default": ["--jvm_args=-Dfoo=foo:-Dbar=b\\\\:ar"], }), + # Consumes more resources than can be expressed via the size attribute. + tags = ["exclusive-if-local"], target_class = "com.example.ForkModeFuzzer", # The exit codes of the forked libFuzzer processes are not picked up correctly. target_compatible_with = SKIP_ON_MACOS, -- cgit v1.2.3 From 2ad261c1ec4adc73c99b5e7a7b55fe9f39ba3561 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 8 Aug 2022 15:59:07 +0200 Subject: driver: Remove gflags/glog deps of libfuzzer_callbacks One logging call is turned unconditional, the other one is removed as it seems rarely useful - the feature is best-effort anyway. --- driver/BUILD.bazel | 2 -- driver/libfuzzer_callbacks.cpp | 14 +++----------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index 1c9edc10..a3d39b0c 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -95,9 +95,7 @@ cc_library( deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/runtime:trace_data_flow_native_callbacks.hdrs", "@com_google_absl//absl/strings", - "@com_google_glog//:glog", "@fmeum_rules_jni//jni", - "@jazzer_com_github_gflags_gflags//:gflags", ] + select({ # We statically link the fuzzer callbacks on Windows since linking a shared library against # symbols exported by a binary is difficult: We would need a .def file to export the symbols diff --git a/driver/libfuzzer_callbacks.cpp b/driver/libfuzzer_callbacks.cpp index 83c03576..a6df806b 100644 --- a/driver/libfuzzer_callbacks.cpp +++ b/driver/libfuzzer_callbacks.cpp @@ -23,8 +23,6 @@ #include "absl/strings/match.h" #include "absl/strings/str_split.h" #include "com_code_intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks.h" -#include "gflags/gflags.h" -#include "glog/logging.h" namespace { bool is_using_native_libraries = false; @@ -37,7 +35,6 @@ std::vector> ignore_for_interception_ranges; * ...). */ void ignoreLibraryForInterception(const std::string &lib_name) { - const auto num_address_ranges = ignore_for_interception_ranges.size(); std::ifstream loaded_libs("/proc/self/maps"); if (!loaded_libs) { // This early exit is taken e.g. on macOS, where /proc does not exist. @@ -84,11 +81,6 @@ void ignoreLibraryForInterception(const std::string &lib_name) { } ignore_for_interception_ranges.emplace_back(start, end); } - const auto num_code_segments = - ignore_for_interception_ranges.size() - num_address_ranges; - LOG(INFO) << "added " << num_code_segments - << " code segment of native library " << lib_name - << " to interceptor ignorelist"; } const std::vector kLibrariesToIgnoreForInterception = { @@ -124,9 +116,9 @@ extern "C" [[maybe_unused]] bool __sanitizer_weak_is_relevant_pc( Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_handleLibraryLoad( JNIEnv *env, jclass cls) { std::call_once(ignore_list_flag, [] { - LOG(INFO) - << "detected a native library load, enabling interception for libc " - "functions"; + std::cout << "INFO: detected a native library load, enabling interception " + "for libc functions" + << std::endl; for (const auto &lib_name : kLibrariesToIgnoreForInterception) ignoreLibraryForInterception(lib_name); // Enable the ignore list after it has been populated since vector is not -- cgit v1.2.3 From 809d1fb614d7754d5b32b51e4bbc99ef68b31271 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 8 Aug 2022 17:51:14 +0200 Subject: driver: Decouple jazzer_main from fuzz_target_runner This commit gets rid of the last dependency between the native driver and the (future) JNI shared library. --- driver/BUILD.bazel | 3 +- driver/fuzz_target_runner.cpp | 84 ++++++++++++---- driver/jazzer_main.cpp | 109 +++++++++++++++------ .../jazzer/driver/FuzzTargetRunner.java | 21 ++++ 4 files changed, 165 insertions(+), 52 deletions(-) diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index a3d39b0c..a667b430 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -123,7 +123,6 @@ cc_library( name = "jazzer_main", srcs = [":jazzer_main.cpp"], deps = [ - ":fuzz_target_runner", ":jvm_tooling_lib", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", @@ -142,6 +141,8 @@ cc_library( "//driver/src/main/java/com/code_intelligence/jazzer/driver:fuzz_target_runner.hdrs", "@fmeum_rules_jni//jni", ], + # With sanitizers, symbols are only referenced dynamically via JNI. + alwayslink = True, ) cc_library( diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index c68511a8..fd361f80 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -32,9 +32,6 @@ extern "C" int LLVMFuzzerRunDriver(int *argc, char ***argv, size_t Size)); namespace { -constexpr auto kFuzzTargetRunnerClassName = - "com/code_intelligence/jazzer/driver/FuzzTargetRunner"; - bool gUseFuzzedDataProvider; jclass gRunner; jmethodID gRunOneId; @@ -66,23 +63,6 @@ int testOneInput(const uint8_t *data, const std::size_t size) { } // namespace namespace jazzer { -int StartFuzzer(JNIEnv *env, int argc, char **argv) { - gEnv = env; - jclass runner = env->FindClass(kFuzzTargetRunnerClassName); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - _Exit(1); - } - gRunner = reinterpret_cast(env->NewGlobalRef(runner)); - gRunOneId = env->GetStaticMethodID(runner, "runOne", "([B)I"); - jfieldID use_fuzzed_data_provider_id = - env->GetStaticFieldID(runner, "useFuzzedDataProvider", "Z"); - gUseFuzzedDataProvider = - env->GetStaticBooleanField(runner, use_fuzzed_data_provider_id); - - return LLVMFuzzerRunDriver(&argc, &argv, testOneInput); -} - void DumpJvmStackTraces() { JavaVM *vm; jsize num_vms; @@ -110,6 +90,70 @@ void DumpJvmStackTraces() { } } // namespace jazzer +[[maybe_unused]] jint +Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner_startLibFuzzer( + JNIEnv *env, jclass runner, jobjectArray args) { + gEnv = env; + gRunner = reinterpret_cast(env->NewGlobalRef(runner)); + gRunOneId = env->GetStaticMethodID(runner, "runOne", "([B)I"); + if (gRunOneId == nullptr) { + env->ExceptionDescribe(); + _Exit(1); + } + jfieldID use_fuzzed_data_provider_id = + env->GetStaticFieldID(runner, "useFuzzedDataProvider", "Z"); + if (use_fuzzed_data_provider_id == nullptr) { + env->ExceptionDescribe(); + _Exit(1); + } + gUseFuzzedDataProvider = + env->GetStaticBooleanField(runner, use_fuzzed_data_provider_id); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + _Exit(1); + } + + int argc = env->GetArrayLength(args); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + _Exit(1); + } + std::vector argv_strings; + std::vector argv_c; + for (jsize i = 0; i < argc; i++) { + auto arg_jni = + reinterpret_cast(env->GetObjectArrayElement(args, i)); + if (arg_jni == nullptr) { + env->ExceptionDescribe(); + _Exit(1); + } + jbyte *arg_c = env->GetByteArrayElements(arg_jni, nullptr); + if (arg_c == nullptr) { + env->ExceptionDescribe(); + _Exit(1); + } + std::size_t arg_size = env->GetArrayLength(arg_jni); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + _Exit(1); + } + argv_strings.emplace_back(reinterpret_cast(arg_c), arg_size); + env->ReleaseByteArrayElements(arg_jni, arg_c, JNI_ABORT); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + _Exit(1); + } + } + for (jsize i = 0; i < argc; i++) { + argv_c.emplace_back(argv_strings[i].c_str()); + } + // Null-terminate argv. + argv_c.emplace_back(nullptr); + + const char **argv = argv_c.data(); + return LLVMFuzzerRunDriver(&argc, const_cast(&argv), testOneInput); +} + [[maybe_unused]] void Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner_printCrashingInput( JNIEnv *, jclass) { diff --git a/driver/jazzer_main.cpp b/driver/jazzer_main.cpp index a1fa0330..147852d0 100644 --- a/driver/jazzer_main.cpp +++ b/driver/jazzer_main.cpp @@ -33,7 +33,6 @@ #include "absl/strings/match.h" #include "absl/strings/str_format.h" #include "absl/strings/strip.h" -#include "driver/fuzz_target_runner.h" #include "gflags/gflags.h" #include "glog/logging.h" #include "jvm_tooling.h" @@ -82,8 +81,8 @@ namespace { const std::string kUsageMessage = R"(Test java fuzz targets using libFuzzer. Usage: jazzer --cp= --target_class= )"; - -std::unique_ptr<::jazzer::JVM> gJvm; +const std::string kFuzzTargetRunnerClassName = + "com/code_intelligence/jazzer/driver/FuzzTargetRunner"; std::string GetNewTempFilePath() { auto temp_dir = std::filesystem::temp_directory_path(); @@ -100,6 +99,60 @@ std::string GetNewTempFilePath() { temp_path.string()); return temp_path.string(); } + +int StartLibFuzzer(std::unique_ptr jvm, + std::vector argv) { + JNIEnv &env = jvm->GetEnv(); + jclass runner = env.FindClass(kFuzzTargetRunnerClassName.c_str()); + if (runner == nullptr) { + env.ExceptionDescribe(); + return 1; + } + jmethodID startFuzzer = + env.GetStaticMethodID(runner, "startLibFuzzer", "([[B)I"); + if (startFuzzer == nullptr) { + env.ExceptionDescribe(); + return 1; + } + jclass byteArrayClass = env.FindClass("[B"); + if (byteArrayClass == nullptr) { + env.ExceptionDescribe(); + return 1; + } + jobjectArray args = env.NewObjectArray(argv.size(), byteArrayClass, nullptr); + if (args == nullptr) { + env.ExceptionDescribe(); + return 1; + } + for (jsize i = 0; i < argv.size(); ++i) { + jint len = argv[i].size(); + jbyteArray arg = env.NewByteArray(len); + if (arg == nullptr) { + env.ExceptionDescribe(); + return 1; + } + // startFuzzer expects UTF-8 encoded strings that are not null-terminated. + env.SetByteArrayRegion(arg, 0, len, + reinterpret_cast(argv[i].data())); + if (env.ExceptionCheck()) { + env.ExceptionDescribe(); + return 1; + } + env.SetObjectArrayElement(args, i, arg); + if (env.ExceptionCheck()) { + env.ExceptionDescribe(); + return 1; + } + env.DeleteLocalRef(arg); + } + int res = env.CallStaticIntMethod(runner, startFuzzer, args); + if (env.ExceptionCheck()) { + env.ExceptionDescribe(); + return 1; + } + env.DeleteLocalRef(args); + return res; +} } // namespace int main(int argc, char **argv) { @@ -117,22 +170,26 @@ int main(int argc, char **argv) { FLAGS_fake_pcs = true; } - // All libFuzzer flags start with a single dash, our arguments all start with - // a double dash. We can thus filter out the arguments meant for gflags by - // taking only those with a leading double dash. - std::vector our_args = {*argv}; - std::copy_if( - argv, argv_end, std::back_inserter(our_args), - [](const auto arg) { return absl::StartsWith(std::string(arg), "--"); }); - int our_argc = our_args.size(); - char **our_argv = our_args.data(); - // Let gflags consume its flags, but keep them in the argument list in case - // libFuzzer forwards the command line (e.g. with -jobs or -minimize_crash). - gflags::ParseCommandLineFlags(&our_argc, &our_argv, false); + { + // All libFuzzer flags start with a single dash, our arguments all start + // with a double dash. We can thus filter out the arguments meant for gflags + // by taking only those with a leading double dash. + std::vector our_args = {*argv}; + std::copy_if(argv, argv_end, std::back_inserter(our_args), + [](const auto arg) { + return absl::StartsWith(std::string(arg), "--"); + }); + int our_argc = our_args.size(); + char **our_argv = our_args.data(); + // Let gflags consume its flags, but keep them in the argument list in case + // libFuzzer forwards the command line (e.g. with -jobs or -minimize_crash). + gflags::ParseCommandLineFlags(&our_argc, &our_argv, false); + } // The potentially modified command line arguments passed to libFuzzer at the // end of this function. - std::vector modified_argv = std::vector(argv, argv_end); + std::vector modified_argv = + std::vector(argv, argv_end); bool spawns_subprocesses = false; if (std::any_of(argv, argv_end, [](std::string_view arg) { @@ -157,11 +214,8 @@ int main(int argc, char **argv) { // the argument to argv for it to be picked up by libFuzzer, which then // forwards it to child processes. FLAGS_id_sync_file = GetNewTempFilePath(); - std::string new_arg = - absl::StrFormat("--id_sync_file=%s", FLAGS_id_sync_file); - // This argument can be accessed by libFuzzer at any (later) time and thus - // cannot be safely freed by us. - modified_argv.push_back(strdup(new_arg.c_str())); + modified_argv.emplace_back( + absl::StrFormat("--id_sync_file=%s", FLAGS_id_sync_file)); } // Creates the file, truncating it if it exists. std::ofstream touch_file(FLAGS_id_sync_file, std::ios_base::trunc); @@ -197,15 +251,9 @@ int main(int argc, char **argv) { // that spawns subprocesses. These would inherit the same seed, which might // make them less effective. if (!spawns_subprocesses) { - std::string seed_arg = "-seed=" + seed; - // This argument can be accessed by libFuzzer at any (later) time and thus - // cannot be safely freed by us. - modified_argv.push_back(strdup(seed_arg.c_str())); + modified_argv.emplace_back("-seed=" + seed); } } - // Terminate modified_argv. - int modified_argc = modified_argv.size(); - modified_argv.push_back(nullptr); if (is_asan_active) { std::cerr << "WARN: Jazzer is not compatible with LeakSanitizer yet. Leaks " @@ -213,7 +261,6 @@ int main(int argc, char **argv) { << std::endl; } - gJvm = std::make_unique(argv[0], seed); - return jazzer::StartFuzzer(&gJvm->GetEnv(), modified_argc, - modified_argv.data()); + return StartLibFuzzer(std::make_unique(argv[0], seed), + modified_argv); } diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index a6bd9505..0cda6d25 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -36,8 +36,10 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.HashSet; @@ -137,6 +139,17 @@ public final class FuzzTargetRunner { Runtime.getRuntime().addShutdownHook(new Thread(FuzzTargetRunner::shutdown)); } + /* + * Starts libFuzzer via LLVMFuzzerRunDriver. + */ + public static int startLibFuzzer(String[] args) { + // Convert the arguments to UTF8 before passing them on to JNI as there are no JNI functions to + // get (unmodified) UTF-8 out of a jstring. + return startLibFuzzer(Arrays.stream(args) + .map(str -> str.getBytes(StandardCharsets.UTF_8)) + .toArray(byte[][] ::new)); + } + /** * Executes the user-provided fuzz target once. * @@ -337,6 +350,14 @@ public final class FuzzTargetRunner { return String.join("", Collections.nCopies(numLeadingZeroes, "0")) + unpadded; } + /** + * Starts libFuzzer via LLVMFuzzerRunDriver. + * + * @param args command-line arguments encoded in UTF-8 (not null-terminated) + * @return the return value of LLVMFuzzerRunDriver + */ + private static native int startLibFuzzer(byte[][] args); + /** * Causes libFuzzer to write the current input to disk as a crashing input and emit some * information about it to stderr. -- cgit v1.2.3 From 9764949e39214481892c408e1aac36297f820cc0 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 10 Aug 2022 13:33:41 +0200 Subject: driver: Warn when AttachCurrentThread fails --- driver/fuzz_target_runner.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index fd361f80..a7b2fa15 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -21,6 +21,7 @@ #include +#include #include #include @@ -73,6 +74,8 @@ void DumpJvmStackTraces() { JNIEnv *env = nullptr; if (vm->AttachCurrentThread(reinterpret_cast(&env), nullptr) != JNI_OK) { + std::cerr << "WARN: AttachCurrentThread failed in DumpJvmStackTraces" + << std::endl; return; } jmethodID dumpStack = -- cgit v1.2.3 From 88f404c2021efac34adcac331716f4dbe618492c Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 12 Aug 2022 14:41:47 +0200 Subject: driver: Fix memory leak for byte[] targets We never deleted the local reference to the byte[] array passed to the fuzz target. --- driver/fuzz_target_runner.cpp | 8 +++++--- tests/BUILD.bazel | 16 ++++++++++++++++ .../java/com/example/BytesMemoryLeakFuzzer.java | 21 +++++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 tests/src/test/java/com/example/BytesMemoryLeakFuzzer.java diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp index a7b2fa15..0edafd9b 100644 --- a/driver/fuzz_target_runner.cpp +++ b/driver/fuzz_target_runner.cpp @@ -44,17 +44,19 @@ void (*gLibfuzzerPrintCrashingInput)() = nullptr; int testOneInput(const uint8_t *data, const std::size_t size) { JNIEnv &env = *gEnv; - jbyteArray input = nullptr; jint jsize = std::min(size, static_cast(std::numeric_limits::max())); + int res; if (gUseFuzzedDataProvider) { ::jazzer::FeedFuzzedDataProvider(data, size); + res = env.CallStaticIntMethod(gRunner, gRunOneId, nullptr); } else { - input = env.NewByteArray(jsize); + jbyteArray input = env.NewByteArray(jsize); env.SetByteArrayRegion(input, 0, jsize, reinterpret_cast(data)); + res = env.CallStaticIntMethod(gRunner, gRunOneId, input); + env.DeleteLocalRef(input); } - int res = env.CallStaticIntMethod(gRunner, gRunOneId, input); if (env.ExceptionCheck()) { env.ExceptionDescribe(); _Exit(1); diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 95d5f209..b43aa67f 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -181,3 +181,19 @@ java_fuzz_target_test( }), target_class = "com.example.DisabledHooksFuzzer", ) + +java_fuzz_target_test( + name = "BytesMemoryLeakFuzzer", + timeout = "short", + srcs = ["src/test/java/com/example/BytesMemoryLeakFuzzer.java"], + env = { + "JAVA_OPTS": "-Xmx200m", + }, + expect_crash = False, + fuzzer_args = [ + # Before the bug was fixed, either the GC overhead limit or the overall heap limit was + # reached by this target in this number of runs. + "-runs=10000000", + ], + target_class = "com.example.BytesMemoryLeakFuzzer", +) diff --git a/tests/src/test/java/com/example/BytesMemoryLeakFuzzer.java b/tests/src/test/java/com/example/BytesMemoryLeakFuzzer.java new file mode 100644 index 00000000..95406316 --- /dev/null +++ b/tests/src/test/java/com/example/BytesMemoryLeakFuzzer.java @@ -0,0 +1,21 @@ +/* + * Copyright 2022 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.example; + +public class BytesMemoryLeakFuzzer { + public static void fuzzerTestOneInput(byte[] data) {} +} -- cgit v1.2.3 From 9076da807d9990a7806836fa5666bc43fb5270ae Mon Sep 17 00:00:00 2001 From: henryrneh Date: Thu, 11 Aug 2022 16:35:49 +0200 Subject: Add support for Tomcat and Jakarta Expression Language Injection Bug Detector. --- .../jazzer/sanitizers/ExpressionLanguageInjection.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt index 77675990..1dc1d5f0 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt @@ -44,6 +44,16 @@ object ExpressionLanguageInjection { targetClassName = "javax.el.ExpressionFactory", targetMethod = "createMethodExpression", ), + MethodHook( + type = HookType.BEFORE, + targetClassName = "jakarta.el.ExpressionFactory", + targetMethod = "createValueExpression", + ), + MethodHook( + type = HookType.BEFORE, + targetClassName = "jakarta.el.ExpressionFactory", + targetMethod = "createMethodExpression", + ), ) @JvmStatic fun hookElExpressionFactory( -- cgit v1.2.3 From b7837adbd3d34b02a39aaa2d6bad20c95c6be86c Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 12 Aug 2022 15:01:29 +0200 Subject: deps: Update libFuzzer --- repositories.bzl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/repositories.bzl b/repositories.bzl index 51114480..e6347ab1 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -145,6 +145,7 @@ def jazzer_dependencies(): http_archive, name = "jazzer_libfuzzer", build_file = Label("//third_party:libFuzzer.BUILD"), - sha256 = "630202d393114f828f350da57d42a6d4fa12ed614a578021d87ba8056dbec4c4", - url = "https://github.com/CodeIntelligenceTesting/llvm-project-jazzer/releases/download/2022-02-26/jazzer-libfuzzer-2022-02-26.tar.gz", + sha256 = "3732ff706e5d049dbc76c2078d9e3ad265c6ccbe1b9ed749ae199df0f3118aac", + strip_prefix = "llvm-project-jazzer-2022-08-12/compiler-rt/lib/fuzzer", + url = "https://github.com/CodeIntelligenceTesting/llvm-project-jazzer/archive/refs/tags/2022-08-12.tar.gz", ) -- cgit v1.2.3 From 08b6c8b71f6e5b65c0ef84d16f51be93c303b481 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 12 Aug 2022 14:51:23 +0200 Subject: driver: Remove unnecessary and ineffective coverage replay In the newest version, libFuzzer no longer exits when no coverage is attained during the first two executions, so replaying coverage is no longer needed. According to the newly added test, replaying the coverage actually wasn't effective. --- .../jazzer/instrumentor/CoverageRecorder.kt | 5 ----- .../jazzer/driver/FuzzTargetRunner.java | 13 +++---------- tests/BUILD.bazel | 14 ++++++++++++++ tests/src/test/java/com/example/NoCoverageFuzzer.java | 19 +++++++++++++++++++ 4 files changed, 36 insertions(+), 15 deletions(-) create mode 100644 tests/src/test/java/com/example/NoCoverageFuzzer.java diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt index 275057f0..098cf389 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt @@ -59,11 +59,6 @@ object CoverageRecorder { additionalCoverage.addAll(CoverageMap.getCoveredIds()) } - @JvmStatic - fun replayCoveredIds() { - CoverageMap.replayCoveredIds(additionalCoverage) - } - /** * [dumpCoverageReport] dumps a human-readable coverage report of files using any [coveredIds] to [dumpFileName]. */ diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index 0cda6d25..aedf8eb6 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -67,7 +67,6 @@ public final class FuzzTargetRunner { private static final MethodHandle fuzzTarget; public static final boolean useFuzzedDataProvider; private static final ReproducerTemplate reproducerTemplate; - private static long runCount = 0; static { String targetClassName = determineFuzzTargetClassName(); @@ -133,6 +132,9 @@ public final class FuzzTargetRunner { } if (Opt.hooks) { + // libFuzzer will clear the coverage map after this method returns and keeps no record of the + // coverage accumulated so far (e.g. by static initializers). We record it here to keep it + // around for JaCoCo coverage reports. CoverageRecorder.updateCoveredIdsWithCoverageMap(); } @@ -159,15 +161,6 @@ public final class FuzzTargetRunner { * this is always 0. The function may exit the process instead of returning. */ public static int runOne(byte[] data) { - if (Opt.hooks && runCount < 2) { - runCount++; - // For the first two runs only, replay the coverage recorded from static initializers. - // libFuzzer cleared the coverage map after they ran and could fail to see any coverage, - // triggering an early exit, if we don't replay it here. - // https://github.com/llvm/llvm-project/blob/957a5e987444d3193575d6ad8afe6c75da00d794/compiler-rt/lib/fuzzer/FuzzerLoop.cpp#L804-L809 - CoverageRecorder.replayCoveredIds(); - } - Throwable finding = null; try { if (useFuzzedDataProvider) { diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index b43aa67f..ee927aef 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -197,3 +197,17 @@ java_fuzz_target_test( ], target_class = "com.example.BytesMemoryLeakFuzzer", ) + +# Verifies that Jazzer continues fuzzing when the first two executions did not result in any +# coverage feedback. +java_fuzz_target_test( + name = "NoCoverageFuzzer", + timeout = "short", + srcs = ["src/test/java/com/example/NoCoverageFuzzer.java"], + expect_crash = False, + fuzzer_args = [ + "-runs=10", + "--instrumentation_excludes=**", + ], + target_class = "com.example.NoCoverageFuzzer", +) diff --git a/tests/src/test/java/com/example/NoCoverageFuzzer.java b/tests/src/test/java/com/example/NoCoverageFuzzer.java new file mode 100644 index 00000000..a1f8b4ea --- /dev/null +++ b/tests/src/test/java/com/example/NoCoverageFuzzer.java @@ -0,0 +1,19 @@ +// Copyright 2022 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.example; + +public class NoCoverageFuzzer { + public static void fuzzerTestOneInput(byte[] data) {} +} -- cgit v1.2.3 From db064eb5aeb9db1462a6ec84292d8528762e0f8c Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 12 Aug 2022 15:23:54 +0200 Subject: all: Use patched-in sanitizer hooks with PC instead of trampoline We are already patching libFuzzer anyway and will continue to do so for the foreseeable future. Thus, we might as well get rid of the highly complex and architecture-dependent trampoline hack. --- README.md | 3 - .../runtime/TraceDataFlowNativeCallbacks.java | 115 ++--------- .../jazzer/runtime/jazzer_fuzzer_callbacks.cpp | 109 +--------- driver/BUILD.bazel | 13 -- driver/jazzer_main.cpp | 9 - driver/jvm_tooling.cpp | 9 +- driver/sanitizer_hooks_with_pc.cpp | 221 --------------------- driver/sanitizer_hooks_with_pc.h | 2 - driver/sanitizer_hooks_with_pc_test.cpp | 189 ------------------ driver/sanitizer_symbols_for_tests.cpp | 16 +- .../com/example/ExampleValueProfileFuzzer.java | 6 +- 11 files changed, 38 insertions(+), 654 deletions(-) delete mode 100644 driver/sanitizer_hooks_with_pc.cpp delete mode 100644 driver/sanitizer_hooks_with_pc_test.cpp diff --git a/README.md b/README.md index 20ee3111..ff5875f2 100644 --- a/README.md +++ b/README.md @@ -439,9 +439,6 @@ associated with the particular bytecode location and used to provide additional See [ExampleValueProfileFuzzer.java](https://github.com/CodeIntelligenceTesting/jazzer/tree/main/examples/src/main/java/com/example/ExampleValueProfileFuzzer.java) for a fuzz target that would be very hard to fuzz without value profile. -As passing the bytecode location back to libFuzzer requires inline assembly and may thus not be fully portable, it can be disabled -via the flag `--nofake_pcs`. - ### Custom Hooks In order to obtain information about data passed into functions such as `String.equals` or `String.startsWith`, Jazzer diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java index 5a773560..cf9b0c55 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java @@ -28,10 +28,6 @@ final public class TraceDataFlowNativeCallbacks { } } - // Making this static final ensures that the JIT will eliminate the dead branch of a construct - // such as: - // if (USE_FAKE_PCS) ... else ... - private static final boolean USE_FAKE_PCS = useFakePcs(); // Note that we are not encoding as modified UTF-8 here: The FuzzedDataProvider transparently // converts CESU8 into modified UTF-8 by coding null bytes on two bytes. Since the fuzzer is more // likely to insert literal null bytes, having both the fuzzer input and the reported string @@ -39,39 +35,6 @@ final public class TraceDataFlowNativeCallbacks { // UTF-8. private static final Charset FUZZED_DATA_CHARSET = Charset.forName("CESU8"); - /* trace-cmp */ - public static void traceCmpInt(int arg1, int arg2, int pc) { - if (USE_FAKE_PCS) { - traceCmpIntWithPc(arg1, arg2, pc); - } else { - traceCmpInt(arg1, arg2); - } - } - - public static void traceConstCmpInt(int arg1, int arg2, int pc) { - if (USE_FAKE_PCS) { - traceConstCmpIntWithPc(arg1, arg2, pc); - } else { - traceConstCmpInt(arg1, arg2); - } - } - - public static void traceCmpLong(long arg1, long arg2, int pc) { - if (USE_FAKE_PCS) { - traceCmpLongWithPc(arg1, arg2, pc); - } else { - traceCmpLong(arg1, arg2); - } - } - - public static void traceSwitch(long val, long[] cases, int pc) { - if (USE_FAKE_PCS) { - traceSwitchWithPc(val, cases, pc); - } else { - traceSwitch(val, cases); - } - } - public static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc); public static void traceStrcmp(String s1, String s2, int result, int pc) { @@ -82,48 +45,7 @@ final public class TraceDataFlowNativeCallbacks { traceStrstr0(encodeForLibFuzzer(s2), pc); } - /* trace-div */ - public static void traceDivInt(int val, int pc) { - if (USE_FAKE_PCS) { - traceDivIntWithPc(val, pc); - } else { - traceDivInt(val); - } - } - - public static void traceDivLong(long val, int pc) { - if (USE_FAKE_PCS) { - traceDivLongWithPc(val, pc); - } else { - traceDivLong(val); - } - } - - /* trace-gep */ - public static void traceGep(long val, int pc) { - if (USE_FAKE_PCS) { - traceGepWithPc(val, pc); - } else { - traceGep(val); - } - } - - /* indirect-calls */ - public static void tracePcIndir(int callee, int caller) { - if (!USE_FAKE_PCS) { - // Without fake PCs, tracePcIndir will not record the relation between callee and pc, which - // makes it useless. - return; - } - tracePcIndir0(callee, caller); - } - public static void traceReflectiveCall(Executable callee, int pc) { - if (!USE_FAKE_PCS) { - // Without fake PCs, tracePcIndir will not record the relation between callee and pc, which - // makes it useless. - return; - } String className = callee.getDeclaringClass().getCanonicalName(); String executableName = callee.getName(); String descriptor = Utils.getDescriptor(callee); @@ -158,6 +80,19 @@ final public class TraceDataFlowNativeCallbacks { } } + /* trace-cmp */ + public static native void traceCmpInt(int arg1, int arg2, int pc); + public static native void traceConstCmpInt(int arg1, int arg2, int pc); + public static native void traceCmpLong(long arg1, long arg2, int pc); + public static native void traceSwitch(long val, long[] cases, int pc); + /* trace-div */ + public static native void traceDivInt(int val, int pc); + public static native void traceDivLong(long val, int pc); + /* trace-gep */ + public static native void traceGep(long val, int pc); + /* indirect-calls */ + public static native void tracePcIndir(int callee, int caller); + public static native void handleLibraryLoad(); private static byte[] encodeForLibFuzzer(String str) { @@ -166,29 +101,5 @@ final public class TraceDataFlowNativeCallbacks { return str.substring(0, Math.min(str.length(), 64)).getBytes(FUZZED_DATA_CHARSET); } - private static boolean useFakePcs() { - String rawFakePcs = System.getProperty("jazzer.fake_pcs"); - if (rawFakePcs == null) { - return false; - } - return Boolean.parseBoolean(rawFakePcs); - } - private static native void traceStrstr0(byte[] needle, int pc); - - private static native void traceCmpInt(int arg1, int arg2); - private static native void traceCmpIntWithPc(int arg1, int arg2, int pc); - private static native void traceConstCmpInt(int arg1, int arg2); - private static native void traceConstCmpIntWithPc(int arg1, int arg2, int pc); - private static native void traceCmpLong(long arg1, long arg2); - private static native void traceCmpLongWithPc(long arg1, long arg2, int pc); - private static native void traceSwitch(long val, long[] cases); - private static native void traceSwitchWithPc(long val, long[] cases, int pc); - private static native void traceDivInt(int val); - private static native void traceDivIntWithPc(int val, int pc); - private static native void traceDivLong(long val); - private static native void traceDivLongWithPc(long val, int pc); - private static native void traceGep(long val); - private static native void traceGepWithPc(long val, int pc); - private static native void tracePcIndir0(int callee, int caller); } diff --git a/agent/src/main/native/com/code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp b/agent/src/main/native/com/code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp index 6be2daed..a4b0214a 100644 --- a/agent/src/main/native/com/code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp +++ b/agent/src/main/native/com/code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp @@ -88,97 +88,42 @@ JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_ [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpLong( - JNIEnv *env, jclass cls, jlong value1, jlong value2) { - __sanitizer_cov_trace_cmp8(value1, value2); -} - -extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpLong( - jlong value1, jlong value2) { - __sanitizer_cov_trace_cmp8(value1, value2); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpLongWithPc( JNIEnv *env, jclass cls, jlong value1, jlong value2, jint id) { __sanitizer_cov_trace_cmp8_with_pc(idToPc(id), value1, value2); } extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpLongWithPc( +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpLong( jlong value1, jlong value2, jint id) { __sanitizer_cov_trace_cmp8_with_pc(idToPc(id), value1, value2); } [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpInt( - JNIEnv *env, jclass cls, jint value1, jint value2) { - __sanitizer_cov_trace_cmp4(value1, value2); -} - -extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpInt( - jint value1, jint value2) { - __sanitizer_cov_trace_cmp4(value1, value2); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpIntWithPc( JNIEnv *env, jclass cls, jint value1, jint value2, jint id) { __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); } extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpIntWithPc( +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpInt( jint value1, jint value2, jint id) { __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); } [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceConstCmpInt( - JNIEnv *env, jclass cls, jint value1, jint value2) { - __sanitizer_cov_trace_cmp4(value1, value2); -} - -extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceConstCmpInt( - jint value1, jint value2) { - __sanitizer_cov_trace_cmp4(value1, value2); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceConstCmpIntWithPc( JNIEnv *env, jclass cls, jint value1, jint value2, jint id) { __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); } extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceConstCmpIntWithPc( +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceConstCmpInt( jint value1, jint value2, jint id) { __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); } [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitch( - JNIEnv *env, jclass cls, jlong switch_value, - jlongArray libfuzzer_case_values) { - auto *case_values = static_cast( - env->GetPrimitiveArrayCritical(libfuzzer_case_values, nullptr)); - __sanitizer_cov_trace_switch(switch_value, - reinterpret_cast(case_values)); - env->ReleasePrimitiveArrayCritical(libfuzzer_case_values, case_values, - JNI_ABORT); -} - -extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitch( - jlong switch_value, jint libfuzzer_case_values_length, jlong *case_values) { - __sanitizer_cov_trace_switch(switch_value, - reinterpret_cast(case_values)); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitchWithPc( JNIEnv *env, jclass cls, jlong switch_value, jlongArray libfuzzer_case_values, jint id) { auto *case_values = static_cast( @@ -190,7 +135,7 @@ Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwi } extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitchWithPc( +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitch( jlong switch_value, jint libfuzzer_case_values_length, jlong *case_values, jint id) { __sanitizer_cov_trace_switch_with_pc( @@ -199,85 +144,49 @@ JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_ [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivLong( - JNIEnv *env, jclass cls, jlong value) { - __sanitizer_cov_trace_div8(value); -} - -extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivLong( - jlong value) { - __sanitizer_cov_trace_div8(value); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivLongWithPc( JNIEnv *env, jclass cls, jlong value, jint id) { __sanitizer_cov_trace_div8_with_pc(idToPc(id), value); } extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivLongWithPc( +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivLong( jlong value, jint id) { __sanitizer_cov_trace_div8_with_pc(idToPc(id), value); } [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivInt( - JNIEnv *env, jclass cls, jint value) { - __sanitizer_cov_trace_div4(value); -} - -extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivInt( - jint value) { - __sanitizer_cov_trace_div4(value); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivIntWithPc( JNIEnv *env, jclass cls, jint value, jint id) { __sanitizer_cov_trace_div4_with_pc(idToPc(id), value); } extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivIntWithPc( +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivInt( jint value, jint id) { __sanitizer_cov_trace_div4_with_pc(idToPc(id), value); } [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceGep( - JNIEnv *env, jclass cls, jlong idx) { - __sanitizer_cov_trace_gep(static_cast(idx)); -} - -extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceGep( - jlong idx) { - __sanitizer_cov_trace_gep(static_cast(idx)); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceGepWithPc( JNIEnv *env, jclass cls, jlong idx, jint id) { __sanitizer_cov_trace_gep_with_pc(idToPc(id), static_cast(idx)); } extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceGepWithPc( +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceGep( jlong idx, jint id) { __sanitizer_cov_trace_gep_with_pc(idToPc(id), static_cast(idx)); } [[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_tracePcIndir0( +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_tracePcIndir( JNIEnv *env, jclass cls, jint caller_id, jint callee_id) { __sanitizer_cov_trace_pc_indir_with_pc(idToPc(caller_id), static_cast(callee_id)); } extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_tracePcIndir0( +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_tracePcIndir( jint caller_id, jint callee_id) { __sanitizer_cov_trace_pc_indir_with_pc(idToPc(caller_id), static_cast(callee_id)); diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index a667b430..1d74678d 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -3,9 +3,7 @@ load("//bazel:cc.bzl", "cc_17_library") cc_library( name = "sanitizer_hooks_with_pc", - srcs = ["sanitizer_hooks_with_pc.cpp"], hdrs = ["sanitizer_hooks_with_pc.h"], - linkstatic = True, visibility = [ "//agent/src/jmh/native/com/code_intelligence/jazzer/runtime:__pkg__", "//agent/src/main/native/com/code_intelligence/jazzer/runtime:__pkg__", @@ -13,17 +11,6 @@ cc_library( ], ) -cc_test( - name = "sanitizer_hooks_with_pc_test", - size = "small", - srcs = ["sanitizer_hooks_with_pc_test.cpp"], - deps = [ - ":sanitizer_hooks_with_pc", - "@googletest//:gtest", - "@googletest//:gtest_main", - ], -) - cc_library( name = "fuzzed_data_provider", srcs = [ diff --git a/driver/jazzer_main.cpp b/driver/jazzer_main.cpp index 147852d0..b96c8665 100644 --- a/driver/jazzer_main.cpp +++ b/driver/jazzer_main.cpp @@ -42,9 +42,6 @@ using namespace std::string_literals; // Defined by glog DECLARE_bool(log_prefix); -// Defined in libfuzzer_callbacks.cpp -DECLARE_bool(fake_pcs); - // Defined in jvm_tooling.cpp DECLARE_string(id_sync_file); @@ -164,12 +161,6 @@ int main(int argc, char **argv) { const auto argv_end = argv + argc; - // Parse libFuzzer flags to determine Jazzer flag defaults before letting - // gflags parse the command line. - if (std::find(argv, argv_end, "-use_value_profile=1"s) != argv_end) { - FLAGS_fake_pcs = true; - } - { // All libFuzzer flags start with a single dash, our arguments all start // with a double dash. We can thus filter out the arguments meant for gflags diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index ad5e0ad9..1f109949 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -128,11 +128,8 @@ DEFINE_string(autofuzz, "", DEFINE_string(autofuzz_ignore, "", "Fully qualified class names of exceptions to ignore during " "autofuzz. Separated by comma."); -DEFINE_bool( - fake_pcs, false, - "Supply synthetic Java program counters to libFuzzer trace hooks to " - "make value profiling more effective. Enabled by default if " - "-use_value_profile=1 is specified."); +DEFINE_bool(fake_pcs, false, + "No-op flag that remains for backwards compatibility only."); #if defined(_WIN32) || defined(_WIN64) #define ARG_SEPARATOR ";" @@ -230,8 +227,6 @@ std::vector fuzzTargetRunnerFlagsAsDefines() { absl::StrFormat("-Djazzer.autofuzz=%s", FLAGS_autofuzz), absl::StrFormat("-Djazzer.autofuzz_ignore=%s", FLAGS_autofuzz_ignore), absl::StrFormat("-Djazzer.hooks=%s", FLAGS_hooks ? "true" : "false"), - absl::StrFormat("-Djazzer.fake_pcs=%s", - FLAGS_fake_pcs ? "true" : "false"), }; } diff --git a/driver/sanitizer_hooks_with_pc.cpp b/driver/sanitizer_hooks_with_pc.cpp deleted file mode 100644 index eb68addf..00000000 --- a/driver/sanitizer_hooks_with_pc.cpp +++ /dev/null @@ -1,221 +0,0 @@ -// 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 "sanitizer_hooks_with_pc.h" - -#include -#include - -// libFuzzer's compare hooks obtain the caller's address from the compiler -// builtin __builtin_return_adress. Since Java code will invoke the hooks always -// from the same native function, this builtin would always return the same -// value. Internally, the libFuzzer hooks call through to the always inlined -// HandleCmp and thus can't be mimicked without patching libFuzzer. -// -// We solve this problem via an inline assembly trampoline construction that -// translates a runtime argument `fake_pc` in the range [0, 512) into a call to -// a hook with a fake return address whose lower 9 bits are `fake_pc` up to a -// constant shift. This is achieved by pushing a return address pointing into -// 512 ret instructions at offset `fake_pc` onto the stack and then jumping -// directly to the address of the hook. -// -// Note: We only set the lowest 9 bits of the return address since only these -// bits are used by the libFuzzer value profiling mode for integer compares, see -// https://github.com/llvm/llvm-project/blob/704d92607d26e696daba596b72cb70effe79a872/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp#L390 -// as well as -// https://github.com/llvm/llvm-project/blob/704d92607d26e696daba596b72cb70effe79a872/compiler-rt/lib/fuzzer/FuzzerValueBitMap.h#L34 -// ValueProfileMap.AddValue() truncates its argument to 16 bits and shifts the -// PC to the left by log_2(128)=7, which means that only the lowest 16 - 7 bits -// of the return address matter. String compare hooks use the lowest 12 bits, -// but take the return address as an argument and thus don't require the -// indirection through a trampoline. - -#define REPEAT_2(a) a a - -#define REPEAT_8(a) REPEAT_2(REPEAT_2(REPEAT_2(a))) - -#define REPEAT_128(a) REPEAT_2(REPEAT_8(REPEAT_8(a))) - -#define REPEAT_512(a) REPEAT_8(REPEAT_8(REPEAT_8(a))) - -// The first four registers to pass arguments in according to the -// platform-specific x64 calling convention. -#ifdef __aarch64__ -#define REG_1 "x0" -#define REG_2 "x1" -#define REG_3 "x2" -#define REG_4 "x3" -#elif _WIN64 -#define REG_1 "rcx" -#define REG_2 "rdx" -#define REG_3 "r8" -#define REG_4 "r9" -#else -#define REG_1 "rdi" -#define REG_2 "rsi" -#define REG_3 "rdx" -#define REG_4 "rcx" -#endif - -// Call the function at address `func` with arguments `arg1` and `arg2` while -// ensuring that the return address is `fake_pc` up to a globally constant -// offset. -__attribute__((noinline)) void trampoline(uint64_t arg1, uint64_t arg2, - void *func, uint16_t fake_pc) { - // arg1 and arg2 have to be forwarded according to the calling convention. - // We also fix func and fake_pc to their registers so that we can safely use - // rax below. - [[maybe_unused]] register uint64_t arg1_loc asm(REG_1) = arg1; - [[maybe_unused]] register uint64_t arg2_loc asm(REG_2) = arg2; - [[maybe_unused]] register void *func_loc asm(REG_3) = func; - [[maybe_unused]] register uint64_t fake_pc_loc asm(REG_4) = fake_pc; -#ifdef __aarch64__ - asm volatile( - // Load address of the ret sled into the default register for the return - // address (offset of four instructions, which means 16 bytes). - "adr x30, 16 \n\t" - // Clear the lowest 2 bits of fake_pc. All arm64 instructions are four - // bytes long, so we can't get better return address granularity than - // multiples of 4. - "and %[fake_pc], %[fake_pc], #0xFFFFFFFFFFFFFFFC \n\t" - // Add the offset of the fake_pc-th ret (rounded to 0 mod 4 above). - "add x30, x30, %[fake_pc] \n\t" - // Call the function by jumping to it and reusing all registers except - // for the modified return address register r30. - "br %[func] \n\t" - // The ret sled for arm64 consists of 128 b instructions jumping to the - // end of the function. Each instruction is 4 bytes long. The sled thus - // has the same byte length of 4 * 128 = 512 as the x86_64 sled, but - // coarser granularity. - REPEAT_128("b end_of_function\n\t") "end_of_function:\n\t" - : - : "r"(arg1_loc), - "r"(arg2_loc), [func] "r"(func_loc), [fake_pc] "r"(fake_pc_loc) - : "memory", "x30"); -#else - asm volatile goto( - // Load RIP-relative address of the end of this function. - "lea %l[end_of_function](%%rip), %%rax \n\t" - "push %%rax \n\t" - // Load RIP-relative address of the ret sled into rax. - "lea ret_sled(%%rip), %%rax \n\t" - // Add the offset of the fake_pc-th ret. - "add %[fake_pc], %%rax \n\t" - // Push the fake return address pointing to that ret. The hook will return - // to it and then immediately return to the end of this function. - "push %%rax \n\t" - // Call func with the fake return address on the stack. - // Function arguments arg1 and arg2 are passed unchanged in the registers - // RDI and RSI as governed by the x64 calling convention. - "jmp *%[func] \n\t" - // Append a sled of 2^9=512 ret instructions. - "ret_sled: \n\t" REPEAT_512("ret \n\t") - : - : "r"(arg1_loc), - "r"(arg2_loc), [func] "r"(func_loc), [fake_pc] "r"(fake_pc_loc) - : "memory" - : end_of_function); - -end_of_function: - return; -#endif -} - -namespace { -uintptr_t trampoline_offset = 0; -} - -void set_trampoline_offset() { - // Stores the additive inverse of the current return address modulo 0x200u in - // trampoline_offset. - trampoline_offset = - 0x200u - - (reinterpret_cast(__builtin_return_address(0)) & 0x1FFu); -} - -// Computes the additive shift that needs to be applied to the caller PC by -// caller_pc_to_fake_pc to make caller PC and resulting fake return address -// in their lowest 9 bits. This offset is constant for each binary, but may vary -// based on code generation specifics. By calibrating the trampoline, the fuzzer -// behavior is fully determined by the seed. -__attribute__((constructor)) void CalibrateTrampoline() { - trampoline(0, 0, reinterpret_cast(&set_trampoline_offset), 0); -} - -// Masks any address down to its lower 9 bits, adjusting for the trampoline -// shift. -__attribute__((always_inline)) inline uint16_t caller_pc_to_fake_pc( - const void *caller_pc) { - return (reinterpret_cast(caller_pc) + trampoline_offset) & 0x1FFu; -} - -// The original hooks exposed by libFuzzer. All of these get the caller's -// address via __builtin_return_address(0). -extern "C" { -void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2); -void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2); -void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases); -void __sanitizer_cov_trace_div4(uint32_t val); -void __sanitizer_cov_trace_div8(uint64_t val); -void __sanitizer_cov_trace_gep(uintptr_t idx); -void __sanitizer_cov_trace_pc_indir(uintptr_t callee); -} -void __sanitizer_cov_trace_cmp4_with_pc(void *caller_pc, uint32_t arg1, - uint32_t arg2) { - void *trace_cmp4 = reinterpret_cast(&__sanitizer_cov_trace_cmp4); - auto fake_pc = caller_pc_to_fake_pc(caller_pc); - trampoline(static_cast(arg1), static_cast(arg2), - trace_cmp4, fake_pc); -} - -void __sanitizer_cov_trace_cmp8_with_pc(void *caller_pc, uint64_t arg1, - uint64_t arg2) { - void *trace_cmp8 = reinterpret_cast(&__sanitizer_cov_trace_cmp8); - auto fake_pc = caller_pc_to_fake_pc(caller_pc); - trampoline(static_cast(arg1), static_cast(arg2), - trace_cmp8, fake_pc); -} - -void __sanitizer_cov_trace_switch_with_pc(void *caller_pc, uint64_t val, - uint64_t *cases) { - void *trace_switch = reinterpret_cast(&__sanitizer_cov_trace_switch); - auto fake_pc = caller_pc_to_fake_pc(caller_pc); - trampoline(static_cast(val), reinterpret_cast(cases), - trace_switch, fake_pc); -} - -void __sanitizer_cov_trace_div4_with_pc(void *caller_pc, uint32_t val) { - void *trace_div4 = reinterpret_cast(&__sanitizer_cov_trace_div4); - auto fake_pc = caller_pc_to_fake_pc(caller_pc); - trampoline(static_cast(val), 0, trace_div4, fake_pc); -} - -void __sanitizer_cov_trace_div8_with_pc(void *caller_pc, uint64_t val) { - void *trace_div8 = reinterpret_cast(&__sanitizer_cov_trace_div8); - auto fake_pc = caller_pc_to_fake_pc(caller_pc); - trampoline(static_cast(val), 0, trace_div8, fake_pc); -} - -void __sanitizer_cov_trace_gep_with_pc(void *caller_pc, uintptr_t idx) { - void *trace_gep = reinterpret_cast(&__sanitizer_cov_trace_gep); - auto fake_pc = caller_pc_to_fake_pc(caller_pc); - trampoline(static_cast(idx), 0, trace_gep, fake_pc); -} - -void __sanitizer_cov_trace_pc_indir_with_pc(void *caller_pc, uintptr_t callee) { - void *trace_pc_indir = - reinterpret_cast(&__sanitizer_cov_trace_pc_indir); - auto fake_pc = caller_pc_to_fake_pc(caller_pc); - trampoline(static_cast(callee), 0, trace_pc_indir, fake_pc); -} diff --git a/driver/sanitizer_hooks_with_pc.h b/driver/sanitizer_hooks_with_pc.h index d9861315..60340371 100644 --- a/driver/sanitizer_hooks_with_pc.h +++ b/driver/sanitizer_hooks_with_pc.h @@ -43,5 +43,3 @@ void __sanitizer_cov_trace_gep_with_pc(void *caller_pc, uintptr_t idx); void __sanitizer_cov_trace_pc_indir_with_pc(void *caller_pc, uintptr_t callee); } - -void CalibrateTrampoline(); diff --git a/driver/sanitizer_hooks_with_pc_test.cpp b/driver/sanitizer_hooks_with_pc_test.cpp deleted file mode 100644 index f18ba147..00000000 --- a/driver/sanitizer_hooks_with_pc_test.cpp +++ /dev/null @@ -1,189 +0,0 @@ -// 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 "sanitizer_hooks_with_pc.h" - -#include -#include -#include -#include - -#include "gtest/gtest.h" - -static std::vector gCoverageMap(512); - -inline void __attribute__((always_inline)) RecordCoverage() { - auto return_address = - reinterpret_cast(__builtin_return_address(0)); - auto idx = return_address & (gCoverageMap.size() - 1); - gCoverageMap[idx]++; -} - -extern "C" { -void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2) { - RecordCoverage(); -} - -void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2) { - RecordCoverage(); -} - -void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases) { - RecordCoverage(); -} - -void __sanitizer_cov_trace_div4(uint32_t val) { RecordCoverage(); } - -void __sanitizer_cov_trace_div8(uint64_t val) { RecordCoverage(); } - -void __sanitizer_cov_trace_gep(uintptr_t idx) { RecordCoverage(); } - -void __sanitizer_cov_trace_pc_indir(uintptr_t callee) { RecordCoverage(); } -} - -void ClearCoverage() { std::fill(gCoverageMap.begin(), gCoverageMap.end(), 0); } - -bool HasOptimalPcCoverage() { -#ifdef __aarch64__ - // All arm64 instructions are four bytes long and aligned to four bytes, so - // the lower two bits of each PC are fixed to 00. - return std::count(gCoverageMap.cbegin(), gCoverageMap.cend(), 0) <= - 3 * gCoverageMap.size() / 4; -#else - return std::count(gCoverageMap.cbegin(), gCoverageMap.cend(), 0) == 0; -#endif -} - -bool HasSingleCoveredPc() { - return std::count(gCoverageMap.cbegin(), gCoverageMap.cend(), 0) == - gCoverageMap.size() - 1; -} - -std::string PrettyPrintCoverage() { - std::ostringstream out; - std::size_t break_after = 16; - out << "Coverage:" << std::endl; - for (uintptr_t i = 0; i < gCoverageMap.size(); i++) { - out << (gCoverageMap[i] ? "X" : "_"); - if (i % break_after == break_after - 1) out << std::endl; - } - return out.str(); -} - -class TestFakePcTrampoline : public ::testing::Test { - protected: - TestFakePcTrampoline() { - ClearCoverage(); - CalibrateTrampoline(); - } -}; - -TEST_F(TestFakePcTrampoline, TraceCmp4Direct) { - for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { - __sanitizer_cov_trace_cmp4(i, i); - } - EXPECT_TRUE(HasSingleCoveredPc()) << PrettyPrintCoverage(); -} - -TEST_F(TestFakePcTrampoline, TraceCmp8Direct) { - for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { - __sanitizer_cov_trace_cmp8(i, i); - } - EXPECT_TRUE(HasSingleCoveredPc()) << PrettyPrintCoverage(); -} - -TEST_F(TestFakePcTrampoline, TraceSwitchDirect) { - for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { - __sanitizer_cov_trace_switch(i, nullptr); - } - EXPECT_TRUE(HasSingleCoveredPc()) << PrettyPrintCoverage(); -} - -TEST_F(TestFakePcTrampoline, TraceDiv4Direct) { - for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { - __sanitizer_cov_trace_div4(i); - } - EXPECT_TRUE(HasSingleCoveredPc()) << PrettyPrintCoverage(); -} - -TEST_F(TestFakePcTrampoline, TraceDiv8Direct) { - for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { - __sanitizer_cov_trace_div8(i); - } - EXPECT_TRUE(HasSingleCoveredPc()) << PrettyPrintCoverage(); -} - -TEST_F(TestFakePcTrampoline, TraceGepDirect) { - for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { - __sanitizer_cov_trace_gep(i); - } - EXPECT_TRUE(HasSingleCoveredPc()) << PrettyPrintCoverage(); -} - -TEST_F(TestFakePcTrampoline, TracePcIndirDirect) { - for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { - __sanitizer_cov_trace_pc_indir(i); - } - EXPECT_TRUE(HasSingleCoveredPc()) << PrettyPrintCoverage(); -} - -TEST_F(TestFakePcTrampoline, TraceCmp4Trampoline) { - for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { - __sanitizer_cov_trace_cmp4_with_pc(reinterpret_cast(i), i, i); - } - EXPECT_TRUE(HasOptimalPcCoverage()) << PrettyPrintCoverage(); -} - -TEST_F(TestFakePcTrampoline, TraceCmp8Trampoline) { - for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { - __sanitizer_cov_trace_cmp8_with_pc(reinterpret_cast(i), i, i); - } - EXPECT_TRUE(HasOptimalPcCoverage()) << PrettyPrintCoverage(); -} - -TEST_F(TestFakePcTrampoline, TraceSwitchTrampoline) { - for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { - __sanitizer_cov_trace_switch_with_pc(reinterpret_cast(i), i, - nullptr); - } - EXPECT_TRUE(HasOptimalPcCoverage()) << PrettyPrintCoverage(); -} - -TEST_F(TestFakePcTrampoline, TraceDiv4Trampoline) { - for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { - __sanitizer_cov_trace_div4_with_pc(reinterpret_cast(i), i); - } - EXPECT_TRUE(HasOptimalPcCoverage()) << PrettyPrintCoverage(); -} - -TEST_F(TestFakePcTrampoline, TraceDiv8Trampoline) { - for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { - __sanitizer_cov_trace_div8_with_pc(reinterpret_cast(i), i); - } - EXPECT_TRUE(HasOptimalPcCoverage()) << PrettyPrintCoverage(); -} - -TEST_F(TestFakePcTrampoline, TraceGepTrampoline) { - for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { - __sanitizer_cov_trace_gep_with_pc(reinterpret_cast(i), i); - } - EXPECT_TRUE(HasOptimalPcCoverage()) << PrettyPrintCoverage(); -} - -TEST_F(TestFakePcTrampoline, TracePcIndirTrampoline) { - for (uint32_t i = 0; i < gCoverageMap.size(); ++i) { - __sanitizer_cov_trace_pc_indir_with_pc(reinterpret_cast(i), i); - } - EXPECT_TRUE(HasOptimalPcCoverage()) << PrettyPrintCoverage(); -} diff --git a/driver/sanitizer_symbols_for_tests.cpp b/driver/sanitizer_symbols_for_tests.cpp index 5cbd862d..ab18dbfc 100644 --- a/driver/sanitizer_symbols_for_tests.cpp +++ b/driver/sanitizer_symbols_for_tests.cpp @@ -31,13 +31,19 @@ void __sanitizer_weak_hook_compare_bytes(void *caller_pc, const void *s1, void __sanitizer_weak_hook_memmem(void *called_pc, const void *s1, size_t len1, const void *s2, size_t len2, void *result) {} void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2) {} -void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2) {} void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases) {} -void __sanitizer_cov_trace_div4(uint32_t val) {} -void __sanitizer_cov_trace_div8(uint64_t val) {} -void __sanitizer_cov_trace_gep(uintptr_t idx) {} -void __sanitizer_cov_trace_pc_indir(uintptr_t callee) {} void __sanitizer_set_death_callback(void (*callback)()) {} +void __sanitizer_cov_trace_cmp4_with_pc(void *caller_pc, uint32_t arg1, + uint32_t arg2) {} +void __sanitizer_cov_trace_cmp8_with_pc(void *caller_pc, uint64_t arg1, + uint64_t arg2) {} +void __sanitizer_cov_trace_switch_with_pc(void *caller_pc, uint64_t val, + uint64_t *cases) {} +void __sanitizer_cov_trace_div4_with_pc(void *caller_pc, uint32_t val) {} +void __sanitizer_cov_trace_div8_with_pc(void *caller_pc, uint64_t val) {} +void __sanitizer_cov_trace_gep_with_pc(void *caller_pc, uintptr_t idx) {} +void __sanitizer_cov_trace_pc_indir_with_pc(void *caller_pc, uintptr_t callee) { +} int LLVMFuzzerRunDriver(int *argc, char ***argv, int (*UserCb)(const uint8_t *Data, size_t Size)) { return 0; diff --git a/examples/src/main/java/com/example/ExampleValueProfileFuzzer.java b/examples/src/main/java/com/example/ExampleValueProfileFuzzer.java index 1e45f428..b68ef6f7 100644 --- a/examples/src/main/java/com/example/ExampleValueProfileFuzzer.java +++ b/examples/src/main/java/com/example/ExampleValueProfileFuzzer.java @@ -37,9 +37,9 @@ public class ExampleValueProfileFuzzer { if (plaintextBlocks.length != 2) return; if (insecureEncrypt(plaintextBlocks[0]) == 0x9fc48ee64d3dc090L) { - // Without --fake_pcs (enabled by default with -use_value_profile=1), the fuzzer would get - // stuck here as the value profile information for long comparisons would not be able to - // distinguish between this comparison and the one above. + // Without variants of the fuzzer hooks for compares that also take in fake PCs, the fuzzer + // would get stuck here as the value profile information for long comparisons would not be + // able to distinguish between this comparison and the one above. if (insecureEncrypt(plaintextBlocks[1]) == 0x888a82ff483ad9c2L) { mustNeverBeCalled(); } -- cgit v1.2.3 From ed200e928d6cddc3d772eef5ba1ad505d911491f Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 9 Aug 2022 10:18:01 +0200 Subject: agent: Move utils out of runtime This makes runtime a java_library, which compiles much faster than a kt_jvm_library. --- .../com/code_intelligence/jazzer/agent/Agent.kt | 2 +- .../code_intelligence/jazzer/runtime/BUILD.bazel | 5 +- .../jazzer/runtime/ExceptionUtils.kt | 172 --------------------- .../jazzer/runtime/ManifestUtils.kt | 54 ------- .../com/code_intelligence/jazzer/utils/BUILD.bazel | 5 + .../jazzer/utils/ExceptionUtils.kt | 172 +++++++++++++++++++++ .../jazzer/utils/ManifestUtils.kt | 54 +++++++ .../code_intelligence/jazzer/driver/BUILD.bazel | 1 + .../jazzer/driver/FuzzTargetRunner.java | 4 +- 9 files changed, 236 insertions(+), 233 deletions(-) delete mode 100644 agent/src/main/java/com/code_intelligence/jazzer/runtime/ExceptionUtils.kt delete mode 100644 agent/src/main/java/com/code_intelligence/jazzer/runtime/ManifestUtils.kt create mode 100644 agent/src/main/java/com/code_intelligence/jazzer/utils/ExceptionUtils.kt create mode 100644 agent/src/main/java/com/code_intelligence/jazzer/utils/ManifestUtils.kt diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index e7396828..d3b4c4dd 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -19,13 +19,13 @@ package com.code_intelligence.jazzer.agent import com.code_intelligence.jazzer.instrumentor.CoverageRecorder import com.code_intelligence.jazzer.instrumentor.Hooks import com.code_intelligence.jazzer.instrumentor.InstrumentationType -import com.code_intelligence.jazzer.runtime.ManifestUtils import com.code_intelligence.jazzer.runtime.NativeLibHooks import com.code_intelligence.jazzer.runtime.SignalHandler import com.code_intelligence.jazzer.runtime.TraceCmpHooks import com.code_intelligence.jazzer.runtime.TraceDivHooks import com.code_intelligence.jazzer.runtime.TraceIndirHooks import com.code_intelligence.jazzer.utils.ClassNameGlobber +import com.code_intelligence.jazzer.utils.ManifestUtils import java.io.File import java.lang.instrument.Instrumentation import java.net.URI 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 8f418326..b6a0ad03 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,5 +1,4 @@ load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library", "jni_headers") -load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") java_library( name = "fuzzed_data_provider", @@ -85,13 +84,11 @@ java_library( ], ) -kt_jvm_library( +java_library( name = "runtime", srcs = [ - "ExceptionUtils.kt", "HardToCatchError.java", "JazzerInternal.java", - "ManifestUtils.kt", "NativeLibHooks.java", "RecordingFuzzedDataProvider.java", "TraceCmpHooks.java", diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/ExceptionUtils.kt b/agent/src/main/java/com/code_intelligence/jazzer/runtime/ExceptionUtils.kt deleted file mode 100644 index 2cc3b22a..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/ExceptionUtils.kt +++ /dev/null @@ -1,172 +0,0 @@ -// 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. - -@file:JvmName("ExceptionUtils") - -package com.code_intelligence.jazzer.runtime - -import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow -import java.lang.management.ManagementFactory -import java.nio.ByteBuffer -import java.security.MessageDigest - -private fun hash(throwable: Throwable, passToRootCause: Boolean): ByteArray = - MessageDigest.getInstance("SHA-256").run { - // It suffices to hash the stack trace of the deepest cause as the higher-level causes only - // contain part of the stack trace (plus possibly a different exception type). - var rootCause = throwable - if (passToRootCause) { - while (true) { - rootCause = rootCause.cause ?: break - } - } - update(rootCause.javaClass.name.toByteArray()) - for (element in rootCause.stackTrace) { - update(element.toString().toByteArray()) - } - if (throwable.suppressed.isNotEmpty()) { - update("suppressed".toByteArray()) - for (suppressed in throwable.suppressed) { - update(hash(suppressed, passToRootCause)) - } - } - digest() - } - -/** - * Computes a hash of the stack trace of [throwable] without messages. - * - * The hash can be used to deduplicate stack traces obtained on crashes. By not including the - * messages, this hash should not depend on the precise crashing input. - */ -fun computeDedupToken(throwable: Throwable): Long { - var passToRootCause = true - if (throwable is FuzzerSecurityIssueLow && throwable.cause is StackOverflowError) { - // Special handling for StackOverflowErrors as processed by preprocessThrowable: - // Only consider the repeated part of the stack trace and ignore the original stack trace in - // the cause. - passToRootCause = false - } - return ByteBuffer.wrap(hash(throwable, passToRootCause)).long -} - -/** - * Annotates [throwable] with a severity and additional information if it represents a bug type - * that has security content. - */ -fun preprocessThrowable(throwable: Throwable): Throwable = when (throwable) { - is StackOverflowError -> { - // StackOverflowErrors are hard to deduplicate as the top-most stack frames vary wildly, - // whereas the information that is most useful for deduplication detection is hidden in the - // rest of the (truncated) stack frame. - // We heuristically clean up the stack trace by taking the elements from the bottom and - // stopping at the first repetition of a frame. The original error is returned as the cause - // unchanged. - val observedFrames = mutableSetOf() - val bottomFramesWithoutRepetition = throwable.stackTrace.takeLastWhile { frame -> - (frame !in observedFrames).also { observedFrames.add(frame) } - } - FuzzerSecurityIssueLow("Stack overflow (use '${getReproducingXssArg()}' to reproduce)", throwable).apply { - stackTrace = bottomFramesWithoutRepetition.toTypedArray() - } - } - is OutOfMemoryError -> stripOwnStackTrace( - FuzzerSecurityIssueLow( - "Out of memory (use '${getReproducingXmxArg()}' to reproduce)", throwable - ) - ) - is VirtualMachineError -> stripOwnStackTrace(FuzzerSecurityIssueLow(throwable)) - else -> throwable -} - -/** - * Strips the stack trace of [throwable] (e.g. because it was created in a utility method), but not - * the stack traces of its causes. - */ -private fun stripOwnStackTrace(throwable: Throwable) = throwable.apply { - stackTrace = emptyArray() -} - -/** - * Returns a valid `-Xmx` JVM argument that sets the stack size to a value with which [StackOverflowError] findings can - * be reproduced, assuming the environment is sufficiently similar (e.g. OS and JVM version). - */ -private fun getReproducingXmxArg(): String? { - val maxHeapSizeInMegaBytes = (getNumericFinalFlagValue("MaxHeapSize") ?: return null) shr 20 - val conservativeMaxHeapSizeInMegaBytes = (maxHeapSizeInMegaBytes * 0.9).toInt() - return "-Xmx${conservativeMaxHeapSizeInMegaBytes}m" -} - -/** - * Returns a valid `-Xss` JVM argument that sets the stack size to a value with which [StackOverflowError] findings can - * be reproduced, assuming the environment is sufficiently similar (e.g. OS and JVM version). - */ -private fun getReproducingXssArg(): String? { - val threadStackSizeInKiloBytes = getNumericFinalFlagValue("ThreadStackSize") ?: return null - val conservativeThreadStackSizeInKiloBytes = (threadStackSizeInKiloBytes * 0.9).toInt() - return "-Xss${conservativeThreadStackSizeInKiloBytes}k" -} - -private fun getNumericFinalFlagValue(arg: String): Long? { - val argPattern = "$arg\\D*(\\d*)".toRegex() - return argPattern.find(javaFullFinalFlags ?: return null)?.groupValues?.get(1)?.toLongOrNull() -} - -private val javaFullFinalFlags by lazy { - readJavaFullFinalFlags() -} - -private fun readJavaFullFinalFlags(): String? { - val javaHome = System.getProperty("java.home") ?: return null - val javaBinary = "$javaHome/bin/java" - val currentJvmArgs = ManagementFactory.getRuntimeMXBean().inputArguments - val javaPrintFlagsProcess = ProcessBuilder( - listOf(javaBinary) + currentJvmArgs + listOf( - "-XX:+PrintFlagsFinal", - "-version" - ) - ).start() - return javaPrintFlagsProcess.inputStream.bufferedReader().useLines { lineSequence -> - lineSequence - .filter { it.contains("ThreadStackSize") || it.contains("MaxHeapSize") } - .joinToString("\n") - } -} - -fun dumpAllStackTraces() { - System.err.println("\nStack traces of all JVM threads:\n") - for ((thread, stack) in Thread.getAllStackTraces()) { - System.err.println(thread) - // Remove traces of this method and the methods it calls. - stack.asList() - .asReversed() - .takeWhile { - !( - it.className == "com.code_intelligence.jazzer.runtime.ExceptionUtils" && - it.methodName == "dumpAllStackTraces" - ) - } - .asReversed() - .forEach { frame -> - System.err.println("\tat $frame") - } - System.err.println() - } - System.err.println("Garbage collector stats:") - System.err.println( - ManagementFactory.getGarbageCollectorMXBeans().joinToString("\n", "\n", "\n") { - "${it.name}: ${it.collectionCount} collections took ${it.collectionTime}ms" - } - ) -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/ManifestUtils.kt b/agent/src/main/java/com/code_intelligence/jazzer/runtime/ManifestUtils.kt deleted file mode 100644 index d88c3e18..00000000 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/ManifestUtils.kt +++ /dev/null @@ -1,54 +0,0 @@ -// 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.runtime - -import java.util.jar.Manifest - -object ManifestUtils { - - const val FUZZ_TARGET_CLASS = "Jazzer-Fuzz-Target-Class" - const val HOOK_CLASSES = "Jazzer-Hook-Classes" - - fun combineManifestValues(attribute: String): List { - val manifests = ClassLoader.getSystemResources("META-INF/MANIFEST.MF") - return manifests.asSequence().mapNotNull { url -> - url.openStream().use { inputStream -> - val manifest = Manifest(inputStream) - manifest.mainAttributes.getValue(attribute) - } - }.toList() - } - - /** - * Returns the value of the `Fuzz-Target-Class` manifest attribute if there is a unique one among all manifest - * files in the classpath. - */ - @JvmStatic - fun detectFuzzTargetClass(): String? { - val fuzzTargets = combineManifestValues(FUZZ_TARGET_CLASS) - return when (fuzzTargets.size) { - 0 -> null - 1 -> fuzzTargets.first() - else -> { - println( - """ - |WARN: More than one Jazzer-Fuzz-Target-Class manifest entry detected on the - |classpath.""".trimMargin() - ) - null - } - } - } -} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/utils/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/utils/BUILD.bazel index 5e301efc..10e3477c 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/utils/BUILD.bazel +++ b/agent/src/main/java/com/code_intelligence/jazzer/utils/BUILD.bazel @@ -4,7 +4,12 @@ kt_jvm_library( name = "utils", srcs = [ "ClassNameGlobber.kt", + "ExceptionUtils.kt", + "ManifestUtils.kt", "Utils.kt", ], visibility = ["//visibility:public"], + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/api", + ], ) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/utils/ExceptionUtils.kt b/agent/src/main/java/com/code_intelligence/jazzer/utils/ExceptionUtils.kt new file mode 100644 index 00000000..30f6fb30 --- /dev/null +++ b/agent/src/main/java/com/code_intelligence/jazzer/utils/ExceptionUtils.kt @@ -0,0 +1,172 @@ +// 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. + +@file:JvmName("ExceptionUtils") + +package com.code_intelligence.jazzer.utils + +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow +import java.lang.management.ManagementFactory +import java.nio.ByteBuffer +import java.security.MessageDigest + +private fun hash(throwable: Throwable, passToRootCause: Boolean): ByteArray = + MessageDigest.getInstance("SHA-256").run { + // It suffices to hash the stack trace of the deepest cause as the higher-level causes only + // contain part of the stack trace (plus possibly a different exception type). + var rootCause = throwable + if (passToRootCause) { + while (true) { + rootCause = rootCause.cause ?: break + } + } + update(rootCause.javaClass.name.toByteArray()) + for (element in rootCause.stackTrace) { + update(element.toString().toByteArray()) + } + if (throwable.suppressed.isNotEmpty()) { + update("suppressed".toByteArray()) + for (suppressed in throwable.suppressed) { + update(hash(suppressed, passToRootCause)) + } + } + digest() + } + +/** + * Computes a hash of the stack trace of [throwable] without messages. + * + * The hash can be used to deduplicate stack traces obtained on crashes. By not including the + * messages, this hash should not depend on the precise crashing input. + */ +fun computeDedupToken(throwable: Throwable): Long { + var passToRootCause = true + if (throwable is FuzzerSecurityIssueLow && throwable.cause is StackOverflowError) { + // Special handling for StackOverflowErrors as processed by preprocessThrowable: + // Only consider the repeated part of the stack trace and ignore the original stack trace in + // the cause. + passToRootCause = false + } + return ByteBuffer.wrap(hash(throwable, passToRootCause)).long +} + +/** + * Annotates [throwable] with a severity and additional information if it represents a bug type + * that has security content. + */ +fun preprocessThrowable(throwable: Throwable): Throwable = when (throwable) { + is StackOverflowError -> { + // StackOverflowErrors are hard to deduplicate as the top-most stack frames vary wildly, + // whereas the information that is most useful for deduplication detection is hidden in the + // rest of the (truncated) stack frame. + // We heuristically clean up the stack trace by taking the elements from the bottom and + // stopping at the first repetition of a frame. The original error is returned as the cause + // unchanged. + val observedFrames = mutableSetOf() + val bottomFramesWithoutRepetition = throwable.stackTrace.takeLastWhile { frame -> + (frame !in observedFrames).also { observedFrames.add(frame) } + } + FuzzerSecurityIssueLow("Stack overflow (use '${getReproducingXssArg()}' to reproduce)", throwable).apply { + stackTrace = bottomFramesWithoutRepetition.toTypedArray() + } + } + is OutOfMemoryError -> stripOwnStackTrace( + FuzzerSecurityIssueLow( + "Out of memory (use '${getReproducingXmxArg()}' to reproduce)", throwable + ) + ) + is VirtualMachineError -> stripOwnStackTrace(FuzzerSecurityIssueLow(throwable)) + else -> throwable +} + +/** + * Strips the stack trace of [throwable] (e.g. because it was created in a utility method), but not + * the stack traces of its causes. + */ +private fun stripOwnStackTrace(throwable: Throwable) = throwable.apply { + stackTrace = emptyArray() +} + +/** + * Returns a valid `-Xmx` JVM argument that sets the stack size to a value with which [StackOverflowError] findings can + * be reproduced, assuming the environment is sufficiently similar (e.g. OS and JVM version). + */ +private fun getReproducingXmxArg(): String? { + val maxHeapSizeInMegaBytes = (getNumericFinalFlagValue("MaxHeapSize") ?: return null) shr 20 + val conservativeMaxHeapSizeInMegaBytes = (maxHeapSizeInMegaBytes * 0.9).toInt() + return "-Xmx${conservativeMaxHeapSizeInMegaBytes}m" +} + +/** + * Returns a valid `-Xss` JVM argument that sets the stack size to a value with which [StackOverflowError] findings can + * be reproduced, assuming the environment is sufficiently similar (e.g. OS and JVM version). + */ +private fun getReproducingXssArg(): String? { + val threadStackSizeInKiloBytes = getNumericFinalFlagValue("ThreadStackSize") ?: return null + val conservativeThreadStackSizeInKiloBytes = (threadStackSizeInKiloBytes * 0.9).toInt() + return "-Xss${conservativeThreadStackSizeInKiloBytes}k" +} + +private fun getNumericFinalFlagValue(arg: String): Long? { + val argPattern = "$arg\\D*(\\d*)".toRegex() + return argPattern.find(javaFullFinalFlags ?: return null)?.groupValues?.get(1)?.toLongOrNull() +} + +private val javaFullFinalFlags by lazy { + readJavaFullFinalFlags() +} + +private fun readJavaFullFinalFlags(): String? { + val javaHome = System.getProperty("java.home") ?: return null + val javaBinary = "$javaHome/bin/java" + val currentJvmArgs = ManagementFactory.getRuntimeMXBean().inputArguments + val javaPrintFlagsProcess = ProcessBuilder( + listOf(javaBinary) + currentJvmArgs + listOf( + "-XX:+PrintFlagsFinal", + "-version" + ) + ).start() + return javaPrintFlagsProcess.inputStream.bufferedReader().useLines { lineSequence -> + lineSequence + .filter { it.contains("ThreadStackSize") || it.contains("MaxHeapSize") } + .joinToString("\n") + } +} + +fun dumpAllStackTraces() { + System.err.println("\nStack traces of all JVM threads:\n") + for ((thread, stack) in Thread.getAllStackTraces()) { + System.err.println(thread) + // Remove traces of this method and the methods it calls. + stack.asList() + .asReversed() + .takeWhile { + !( + it.className == "com.code_intelligence.jazzer.runtime.ExceptionUtils" && + it.methodName == "dumpAllStackTraces" + ) + } + .asReversed() + .forEach { frame -> + System.err.println("\tat $frame") + } + System.err.println() + } + System.err.println("Garbage collector stats:") + System.err.println( + ManagementFactory.getGarbageCollectorMXBeans().joinToString("\n", "\n", "\n") { + "${it.name}: ${it.collectionCount} collections took ${it.collectionTime}ms" + } + ) +} diff --git a/agent/src/main/java/com/code_intelligence/jazzer/utils/ManifestUtils.kt b/agent/src/main/java/com/code_intelligence/jazzer/utils/ManifestUtils.kt new file mode 100644 index 00000000..25d0ade9 --- /dev/null +++ b/agent/src/main/java/com/code_intelligence/jazzer/utils/ManifestUtils.kt @@ -0,0 +1,54 @@ +// 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.utils + +import java.util.jar.Manifest + +object ManifestUtils { + + const val FUZZ_TARGET_CLASS = "Jazzer-Fuzz-Target-Class" + const val HOOK_CLASSES = "Jazzer-Hook-Classes" + + fun combineManifestValues(attribute: String): List { + val manifests = ClassLoader.getSystemResources("META-INF/MANIFEST.MF") + return manifests.asSequence().mapNotNull { url -> + url.openStream().use { inputStream -> + val manifest = Manifest(inputStream) + manifest.mainAttributes.getValue(attribute) + } + }.toList() + } + + /** + * Returns the value of the `Fuzz-Target-Class` manifest attribute if there is a unique one among all manifest + * files in the classpath. + */ + @JvmStatic + fun detectFuzzTargetClass(): String? { + val fuzzTargets = combineManifestValues(FUZZ_TARGET_CLASS) + return when (fuzzTargets.size) { + 0 -> null + 1 -> fuzzTargets.first() + else -> { + println( + """ + |WARN: More than one Jazzer-Fuzz-Target-Class manifest entry detected on the + |classpath.""".trimMargin() + ) + null + } + } + } +} diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel b/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel index fc291497..c64bbfd5 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -17,6 +17,7 @@ java_jni_library( "//agent/src/main/java/com/code_intelligence/jazzer/runtime", "//agent/src/main/java/com/code_intelligence/jazzer/runtime:coverage_map", "//agent/src/main/java/com/code_intelligence/jazzer/runtime:fuzzed_data_provider", + "//agent/src/main/java/com/code_intelligence/jazzer/utils", ], ) diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index aedf8eb6..843dc44b 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -24,11 +24,11 @@ import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.code_intelligence.jazzer.autofuzz.FuzzTarget; import com.code_intelligence.jazzer.instrumentor.CoverageRecorder; import com.code_intelligence.jazzer.runtime.CoverageMap; -import com.code_intelligence.jazzer.runtime.ExceptionUtils; import com.code_intelligence.jazzer.runtime.FuzzedDataProviderImpl; import com.code_intelligence.jazzer.runtime.JazzerInternal; -import com.code_intelligence.jazzer.runtime.ManifestUtils; import com.code_intelligence.jazzer.runtime.RecordingFuzzedDataProvider; +import com.code_intelligence.jazzer.utils.ExceptionUtils; +import com.code_intelligence.jazzer.utils.ManifestUtils; import java.io.IOException; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; -- cgit v1.2.3 From 547295d8dafa8e432301371fca8e28c4fcc4c096 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 11 Aug 2022 08:05:16 +0200 Subject: driver: Restrict access modifiers in FuzzTargetRunner --- .../jazzer/driver/FuzzTargetRunner.java | 30 ++++++++++++---------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index 843dc44b..18dda179 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -141,17 +141,6 @@ public final class FuzzTargetRunner { Runtime.getRuntime().addShutdownHook(new Thread(FuzzTargetRunner::shutdown)); } - /* - * Starts libFuzzer via LLVMFuzzerRunDriver. - */ - public static int startLibFuzzer(String[] args) { - // Convert the arguments to UTF8 before passing them on to JNI as there are no JNI functions to - // get (unmodified) UTF-8 out of a jstring. - return startLibFuzzer(Arrays.stream(args) - .map(str -> str.getBytes(StandardCharsets.UTF_8)) - .toArray(byte[][] ::new)); - } - /** * Executes the user-provided fuzz target once. * @@ -160,7 +149,7 @@ public final class FuzzTargetRunner { * @return the value that the native LLVMFuzzerTestOneInput function should return. Currently, * this is always 0. The function may exit the process instead of returning. */ - public static int runOne(byte[] data) { + static int runOne(byte[] data) { Throwable finding = null; try { if (useFuzzedDataProvider) { @@ -217,8 +206,15 @@ public final class FuzzTargetRunner { return 0; } - public static void dumpAllStackTraces() { - ExceptionUtils.dumpAllStackTraces(); + /* + * Starts libFuzzer via LLVMFuzzerRunDriver. + */ + static int startLibFuzzer(String[] args) { + // Convert the arguments to UTF8 before passing them on to JNI as there are no JNI functions to + // get (unmodified) UTF-8 out of a jstring. + return startLibFuzzer(Arrays.stream(args) + .map(str -> str.getBytes(StandardCharsets.UTF_8)) + .toArray(byte[][] ::new)); } private static void shutdown() { @@ -343,6 +339,12 @@ public final class FuzzTargetRunner { return String.join("", Collections.nCopies(numLeadingZeroes, "0")) + unpadded; } + // Accessed by fuzz_target_runner.cpp. + @SuppressWarnings("unused") + private static void dumpAllStackTraces() { + ExceptionUtils.dumpAllStackTraces(); + } + /** * Starts libFuzzer via LLVMFuzzerRunDriver. * -- cgit v1.2.3 From 0c8cb3317c2db790e1efebb5122afc1e6797a17c Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 11 Aug 2022 08:16:20 +0200 Subject: driver: Extract libFuzzer args processing into Driver and Opt --- agent/BUILD.bazel | 2 +- driver/jazzer_main.cpp | 17 +++++----- .../code_intelligence/jazzer/driver/BUILD.bazel | 19 ++++++++++- .../code_intelligence/jazzer/driver/Driver.java | 25 ++++++++++++++ .../jazzer/driver/FuzzTargetRunner.java | 11 ++---- .../com/code_intelligence/jazzer/driver/Utils.java | 39 ++++++++++++++++++++++ 6 files changed, 94 insertions(+), 19 deletions(-) create mode 100644 driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java create mode 100644 driver/src/main/java/com/code_intelligence/jazzer/driver/Utils.java diff --git a/agent/BUILD.bazel b/agent/BUILD.bazel index f3ce04f8..aedbe424 100644 --- a/agent/BUILD.bazel +++ b/agent/BUILD.bazel @@ -13,7 +13,7 @@ java_binary( ] + [" {}:".format(c) for c in SANITIZER_CLASSES], runtime_deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/agent:agent_lib", - "//driver/src/main/java/com/code_intelligence/jazzer/driver:fuzz_target_runner", + "//driver/src/main/java/com/code_intelligence/jazzer/driver", "//sanitizers", ], ) diff --git a/driver/jazzer_main.cpp b/driver/jazzer_main.cpp index b96c8665..ce8201a3 100644 --- a/driver/jazzer_main.cpp +++ b/driver/jazzer_main.cpp @@ -17,7 +17,7 @@ * 1. defines default settings for ASan and UBSan; * 2. preprocesses the command-line arguments passed to libFuzzer; * 3. starts a JVM; - * 4. passes control to the fuzz target runner. + * 4. passes control to the Java-part of the driver. */ #include @@ -78,8 +78,8 @@ namespace { const std::string kUsageMessage = R"(Test java fuzz targets using libFuzzer. Usage: jazzer --cp= --target_class= )"; -const std::string kFuzzTargetRunnerClassName = - "com/code_intelligence/jazzer/driver/FuzzTargetRunner"; +const std::string kDriverClassName = + "com/code_intelligence/jazzer/driver/Driver"; std::string GetNewTempFilePath() { auto temp_dir = std::filesystem::temp_directory_path(); @@ -100,14 +100,13 @@ std::string GetNewTempFilePath() { int StartLibFuzzer(std::unique_ptr jvm, std::vector argv) { JNIEnv &env = jvm->GetEnv(); - jclass runner = env.FindClass(kFuzzTargetRunnerClassName.c_str()); + jclass runner = env.FindClass(kDriverClassName.c_str()); if (runner == nullptr) { env.ExceptionDescribe(); return 1; } - jmethodID startFuzzer = - env.GetStaticMethodID(runner, "startLibFuzzer", "([[B)I"); - if (startFuzzer == nullptr) { + jmethodID startDriver = env.GetStaticMethodID(runner, "start", "([[B)I"); + if (startDriver == nullptr) { env.ExceptionDescribe(); return 1; } @@ -128,7 +127,7 @@ int StartLibFuzzer(std::unique_ptr jvm, env.ExceptionDescribe(); return 1; } - // startFuzzer expects UTF-8 encoded strings that are not null-terminated. + // startDriver expects UTF-8 encoded strings that are not null-terminated. env.SetByteArrayRegion(arg, 0, len, reinterpret_cast(argv[i].data())); if (env.ExceptionCheck()) { @@ -142,7 +141,7 @@ int StartLibFuzzer(std::unique_ptr jvm, } env.DeleteLocalRef(arg); } - int res = env.CallStaticIntMethod(runner, startFuzzer, args); + int res = env.CallStaticIntMethod(runner, startDriver, args); if (env.ExceptionCheck()) { env.ExceptionDescribe(); return 1; diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel b/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel index c64bbfd5..0b27a1e7 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -1,16 +1,28 @@ load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library") +java_library( + name = "driver", + srcs = [":Driver.java"], + visibility = [ + "//agent:__pkg__", + ], + deps = [ + ":fuzz_target_runner", + ":utils", + ], +) + java_jni_library( name = "fuzz_target_runner", srcs = ["FuzzTargetRunner.java"], visibility = [ - "//agent:__pkg__", "//driver:__pkg__", "//driver/src/test:__subpackages__", ], deps = [ ":opt", ":reproducer_template", + ":utils", "//agent/src/main/java/com/code_intelligence/jazzer/api", "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz", "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor", @@ -33,3 +45,8 @@ java_library( srcs = ["Opt.java"], visibility = ["//driver/src/test/java/com/code_intelligence/jazzer/driver:__pkg__"], ) + +java_library( + name = "utils", + srcs = ["Utils.java"], +) diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java new file mode 100644 index 00000000..058c3e53 --- /dev/null +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java @@ -0,0 +1,25 @@ +/* + * Copyright 2022 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.driver; + +public class Driver { + // Accessed from jazzer_main.cpp. + @SuppressWarnings("unused") + private static int start(byte[][] nativeArgs) { + return FuzzTargetRunner.startLibFuzzer(Utils.fromNativeArgs(nativeArgs)); + } +} diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index 18dda179..02932ec1 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -36,13 +36,12 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigInteger; -import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Set; @@ -209,12 +208,8 @@ public final class FuzzTargetRunner { /* * Starts libFuzzer via LLVMFuzzerRunDriver. */ - static int startLibFuzzer(String[] args) { - // Convert the arguments to UTF8 before passing them on to JNI as there are no JNI functions to - // get (unmodified) UTF-8 out of a jstring. - return startLibFuzzer(Arrays.stream(args) - .map(str -> str.getBytes(StandardCharsets.UTF_8)) - .toArray(byte[][] ::new)); + static int startLibFuzzer(List args) { + return startLibFuzzer(Utils.toNativeArgs(args)); } private static void shutdown() { diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/Utils.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/Utils.java new file mode 100644 index 00000000..37eb1d0f --- /dev/null +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/Utils.java @@ -0,0 +1,39 @@ +/* + * Copyright 2022 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.driver; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +public class Utils { + /** + * Convert the arguments to UTF8 before passing them on to JNI as there are no JNI functions to + * get (unmodified) UTF-8 out of a jstring. + */ + static byte[][] toNativeArgs(Collection args) { + return args.stream().map(str -> str.getBytes(StandardCharsets.UTF_8)).toArray(byte[][] ::new); + } + + static List fromNativeArgs(byte[][] args) { + return Arrays.stream(args) + .map(bytes -> new String(bytes, StandardCharsets.UTF_8)) + .collect(Collectors.toList()); + } +} -- cgit v1.2.3 From 766b77226a38388b772983aafe40c9c230a882a1 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 11 Aug 2022 09:59:31 +0200 Subject: tests: Verify that the driver indirectly sets Jazzer.SEED --- tests/BUILD.bazel | 12 ++++++++++ tests/src/test/java/com/example/SeedFuzzer.java | 30 +++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 tests/src/test/java/com/example/SeedFuzzer.java diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index ee927aef..ba2a8f6d 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -211,3 +211,15 @@ java_fuzz_target_test( ], target_class = "com.example.NoCoverageFuzzer", ) + +java_fuzz_target_test( + name = "SeedFuzzer", + timeout = "short", + srcs = ["src/test/java/com/example/SeedFuzzer.java"], + expect_crash = False, + fuzzer_args = [ + "-runs=0", + "-seed=1234567", + ], + target_class = "com.example.SeedFuzzer", +) diff --git a/tests/src/test/java/com/example/SeedFuzzer.java b/tests/src/test/java/com/example/SeedFuzzer.java new file mode 100644 index 00000000..4d1e4e8b --- /dev/null +++ b/tests/src/test/java/com/example/SeedFuzzer.java @@ -0,0 +1,30 @@ +/* + * Copyright 2022 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.example; + +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow; +import com.code_intelligence.jazzer.api.Jazzer; + +public class SeedFuzzer { + public static void fuzzerInitialize() { + if (Jazzer.SEED != 1234567) { + throw new FuzzerSecurityIssueLow("Expected Jazzer.SEED to be 1234567, got " + Jazzer.SEED); + } + } + + public static void fuzzerTestOneInput(byte[] data) {} +} -- cgit v1.2.3 From 11707bcec3ca41f7957dc3296253a4a431fd2041 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 11 Aug 2022 10:09:34 +0200 Subject: api: Fix paragraphs and links in Javadoc

    is required to begin a new paragraph, empty lines aren't enough. --- .../jazzer/api/FuzzerSecurityIssueCritical.java | 2 +- .../jazzer/api/FuzzerSecurityIssueHigh.java | 2 +- .../jazzer/api/FuzzerSecurityIssueMedium.java | 2 +- .../com/code_intelligence/jazzer/api/Jazzer.java | 38 +++++++++++----------- .../code_intelligence/jazzer/api/MethodHook.java | 2 +- .../jazzer/sanitizers/RegexRoadblocks.java | 2 +- .../test/java/com/example/LongStringFuzzer.java | 8 ++--- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueCritical.java b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueCritical.java index 4402a7f3..fbde853b 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueCritical.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueCritical.java @@ -17,7 +17,7 @@ package com.code_intelligence.jazzer.api; /** * Thrown to indicate that a fuzz target has detected a critical severity security issue rather than * a normal bug. - * + *

    * There is only a semantical but no functional difference between throwing exceptions of this type * or any other. However, automated fuzzing platforms can use the extra information to handle the * detected issues appropriately. diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueHigh.java b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueHigh.java index 4d323e56..05837b0e 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueHigh.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueHigh.java @@ -17,7 +17,7 @@ package com.code_intelligence.jazzer.api; /** * Thrown to indicate that a fuzz target has detected a high severity security issue rather than a * normal bug. - * + *

    * There is only a semantical but no functional difference between throwing exceptions of this type * or any other. However, automated fuzzing platforms can use the extra information to handle the * detected issues appropriately. diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueMedium.java b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueMedium.java index f0de4ce7..be7c8c8f 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueMedium.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueMedium.java @@ -17,7 +17,7 @@ package com.code_intelligence.jazzer.api; /** * Thrown to indicate that a fuzz target has detected a medium severity security issue rather than a * normal bug. - * + *

    * There is only a semantical but no functional difference between throwing exceptions of this type * or any other. However, automated fuzzing platforms can use the extra information to handle the * detected issues appropriately. diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java index 2d2e82b0..97adf578 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java @@ -29,7 +29,7 @@ final public class Jazzer { * between multiple possible mutations they could guide the fuzzer towards. * Hooks must not base the decision whether or not to report a finding * on this number as this will make findings non-reproducible. - * + *

    * This is the same number that libFuzzer uses as a seed internally, which * makes it possible to deterministically reproduce a previous fuzzing run by * supplying the seed value printed by libFuzzer as the value of the @@ -160,7 +160,7 @@ final public class Jazzer { /** * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input * using only public methods available on the classpath. - * + *

    * Note: This function is inherently heuristic and may fail to execute {@code func} in * meaningful ways for a number of reasons. * @@ -190,7 +190,7 @@ final public class Jazzer { /** * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input * using only public methods available on the classpath. - * + *

    * Note: This function is inherently heuristic and may fail to execute {@code func} in * meaningful ways for a number of reasons. * @@ -220,7 +220,7 @@ final public class Jazzer { /** * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input * using only public methods available on the classpath. - * + *

    * Note: This function is inherently heuristic and may fail to execute {@code func} in * meaningful ways for a number of reasons. * @@ -250,7 +250,7 @@ final public class Jazzer { /** * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input * using only public methods available on the classpath. - * + *

    * Note: This function is inherently heuristic and may fail to execute {@code func} in * meaningful ways for a number of reasons. * @@ -281,7 +281,7 @@ final public class Jazzer { /** * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input * using only public methods available on the classpath. - * + *

    * Note: This function is inherently heuristic and may fail to execute {@code func} in * meaningful ways for a number of reasons. * @@ -312,7 +312,7 @@ final public class Jazzer { /** * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input * using only public methods available on the classpath. - * + *

    * Note: This function is inherently heuristic and may fail to execute {@code func} in * meaningful ways for a number of reasons. * @@ -337,7 +337,7 @@ final public class Jazzer { /** * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input * using only public methods available on the classpath. - * + *

    * Note: This function is inherently heuristic and may fail to execute {@code func} in * meaningful ways for a number of reasons. * @@ -362,7 +362,7 @@ final public class Jazzer { /** * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input * using only public methods available on the classpath. - * + *

    * Note: This function is inherently heuristic and may fail to execute {@code func} in * meaningful ways for a number of reasons. * @@ -387,7 +387,7 @@ final public class Jazzer { /** * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input * using only public methods available on the classpath. - * + *

    * Note: This function is inherently heuristic and may fail to execute {@code func} in * meaningful ways for a number of reasons. * @@ -413,7 +413,7 @@ final public class Jazzer { /** * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input * using only public methods available on the classpath. - * + *

    * Note: This function is inherently heuristic and may fail to execute {@code func} in * meaningful ways for a number of reasons. * @@ -439,7 +439,7 @@ final public class Jazzer { /** * Attempts to construct an instance of {@code type} from the fuzzer input using only public * methods available on the classpath. - * + *

    * Note: This function is inherently heuristic and may fail to return meaningful values for * a variety of reasons. * @@ -464,7 +464,7 @@ final public class Jazzer { /** * Instructs the fuzzer to guide its mutations towards making {@code current} equal to {@code * target}. - * + *

    * If the relation between the raw fuzzer input and the value of {@code current} is relatively * complex, running the fuzzer with the argument {@code -use_value_profile=1} may be necessary to * achieve equality. @@ -487,7 +487,7 @@ final public class Jazzer { /** * Instructs the fuzzer to guide its mutations towards making {@code current} equal to {@code * target}. - * + *

    * If the relation between the raw fuzzer input and the value of {@code current} is relatively * complex, running the fuzzer with the argument {@code -use_value_profile=1} may be necessary to * achieve equality. @@ -510,7 +510,7 @@ final public class Jazzer { /** * Instructs the fuzzer to guide its mutations towards making {@code haystack} contain {@code * needle} as a substring. - * + *

    * If the relation between the raw fuzzer input and the value of {@code haystack} is relatively * complex, running the fuzzer with the argument {@code -use_value_profile=1} may be necessary to * satisfy the substring check. @@ -534,12 +534,12 @@ final public class Jazzer { /** * Instructs the fuzzer to attain as many possible values for the absolute value of {@code state} * as possible. - * + *

    * Call this function from a fuzz target or a hook to help the fuzzer track partial progress * (e.g. by passing the length of a common prefix of two lists that should become equal) or * explore different values of state that is not directly related to code coverage (see the * MazeFuzzer example). - * + *

    * Note: This hint only takes effect if the fuzzer is run with the argument * {@code -use_value_profile=1}. * @@ -584,7 +584,7 @@ final public class Jazzer { /** * Make Jazzer report the provided {@link Throwable} as a finding. - * + *

    * Note: This method must only be called from a method hook. In a * fuzz target, simply throw an exception to trigger a finding. * @param finding the finding that Jazzer should report @@ -609,7 +609,7 @@ final public class Jazzer { /** * Register a callback to be executed right before the fuzz target is executed for the first time. - * + *

    * This can be used to disable hooks until after Jazzer has been fully initializing, e.g. to * prevent Jazzer internals from triggering hooks on Java standard library classes. * diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java b/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java index 46c5c112..3a1c5f39 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java @@ -88,7 +88,7 @@ import java.lang.invoke.MethodType; *

    * Return value: the value that should take the role of the value the target * method would have returned - * + *

    *

    {@link HookType#AFTER} *
    *
    {@code
    diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexRoadblocks.java b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexRoadblocks.java
    index f28c9991..1043ac02 100644
    --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexRoadblocks.java
    +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/RegexRoadblocks.java
    @@ -32,7 +32,7 @@ import sun.misc.Unsafe;
     /**
      * The hooks in this class extend the reach of Jazzer's string compare instrumentation to literals
      * (both strings and characters) that are part of regular expression patterns.
    - *
    + * 

    * Internally, the Java standard library represents a compiled regular expression as a graph of * instances of Pattern$Node instances, each of which represents a single unit of the full * expression and provides a `match` function that takes a {@link Matcher}, a {@link CharSequence} diff --git a/tests/src/test/java/com/example/LongStringFuzzer.java b/tests/src/test/java/com/example/LongStringFuzzer.java index 5fecf91f..324764d4 100644 --- a/tests/src/test/java/com/example/LongStringFuzzer.java +++ b/tests/src/test/java/com/example/LongStringFuzzer.java @@ -17,11 +17,11 @@ package com.example; import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow; /** - * Provoke a finding with huge captured data to verify that the generated - * crash reproducer is still compilable. This test uses a huge, predefined - * corpus to speed up finding the issue. + * Provoke a finding with huge captured data to verify that the generated crash reproducer is still + * compilable. This test uses a huge, predefined corpus to speed up finding the issue. *

    - * Reproduces issue #269 (https://github.com/CodeIntelligenceTesting/jazzer/issues/269) + * Reproduces issue #269 (...) */ public class LongStringFuzzer { public static void fuzzerTestOneInput(byte[] data) { -- cgit v1.2.3 From 07ce6176cbd19b3bdd3af413c62b577bd619b9c8 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 11 Aug 2022 10:44:16 +0200 Subject: driver: Attach the agent at runtime This functionality is needed to launch Jazzer in an already running JVM. --- agent/agent_shade_rules | 3 +-- agent/verify_shading.sh | 2 ++ driver/jvm_tooling.cpp | 19 +++---------------- .../com/code_intelligence/jazzer/driver/BUILD.bazel | 3 +++ .../com/code_intelligence/jazzer/driver/Driver.java | 17 ++++++++++++++++- .../jazzer/driver/FuzzTargetRunner.java | 5 ++++- .../java/com/code_intelligence/jazzer/driver/Opt.java | 3 ++- repositories.bzl | 7 +++++++ 8 files changed, 38 insertions(+), 21 deletions(-) diff --git a/agent/agent_shade_rules b/agent/agent_shade_rules index 3f0aff07..91073722 100644 --- a/agent/agent_shade_rules +++ b/agent/agent_shade_rules @@ -1,7 +1,6 @@ rule com.github.** com.code_intelligence.jazzer.third_party.@0 rule io.** com.code_intelligence.jazzer.third_party.@0 rule kotlin.** com.code_intelligence.jazzer.third_party.@0 -rule net.jodah.** com.code_intelligence.jazzer.third_party.@0 -rule net.sf.** com.code_intelligence.jazzer.third_party.@0 +rule net.** com.code_intelligence.jazzer.third_party.@0 rule nonapi.** com.code_intelligence.jazzer.third_party.@0 rule org.objectweb.** com.code_intelligence.jazzer.third_party.@0 diff --git a/agent/verify_shading.sh b/agent/verify_shading.sh index 1f116b87..5742476c 100755 --- a/agent/verify_shading.sh +++ b/agent/verify_shading.sh @@ -23,4 +23,6 @@ -e '^com/code_intelligence/$' \ -e '^com/code_intelligence/jazzer/' \ -e '^jaz/' \ + -e '^win32-x86/' \ + -e '^win32-x86-64/' \ -e '^META-INF/' diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index 1f109949..54eb7d7c 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -227,6 +227,7 @@ std::vector fuzzTargetRunnerFlagsAsDefines() { absl::StrFormat("-Djazzer.autofuzz=%s", FLAGS_autofuzz), absl::StrFormat("-Djazzer.autofuzz_ignore=%s", FLAGS_autofuzz_ignore), absl::StrFormat("-Djazzer.hooks=%s", FLAGS_hooks ? "true" : "false"), + absl::StrFormat("-Djazzer.agent_args=%s", agentArgsFromFlags()), }; } @@ -269,13 +270,8 @@ JVM::JVM(std::string_view executable_path, std::string_view seed) { if (class_path_from_env) { class_path += absl::StrCat(ARG_SEPARATOR, class_path_from_env); } - if (!FLAGS_hooks) { - // A Java agent is implicitly added to the system class loader's classpath, - // so there is no need to add the Jazzer agent here if we are running with - // the agent enabled. - class_path += - absl::StrCat(ARG_SEPARATOR, getInstrumentorAgentPath(executable_path)); - } + class_path += + absl::StrCat(ARG_SEPARATOR, getInstrumentorAgentPath(executable_path)); LOG(INFO) << "got class path " << class_path; std::vector options; @@ -337,15 +333,6 @@ JVM::JVM(std::string_view executable_path, std::string_view seed) { JavaVMOption{.optionString = const_cast(arg.c_str())}); } - std::string agent_jvm_arg; - if (FLAGS_hooks) { - agent_jvm_arg = absl::StrFormat("-javaagent:%s=%s", - getInstrumentorAgentPath(executable_path), - agentArgsFromFlags()); - options.push_back(JavaVMOption{ - .optionString = const_cast(agent_jvm_arg.c_str())}); - } - JavaVMInitArgs jvm_init_args = {.version = JNI_VERSION_1_8, .nOptions = (int)options.size(), .options = options.data(), diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel b/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel index 0b27a1e7..c6588c4b 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -8,7 +8,10 @@ java_library( ], deps = [ ":fuzz_target_runner", + ":opt", ":utils", + "//agent/src/main/java/com/code_intelligence/jazzer/agent:agent_lib", + "@net_bytebuddy_byte_buddy_agent//jar", ], ) diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java index 058c3e53..a4521434 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java @@ -16,10 +16,25 @@ package com.code_intelligence.jazzer.driver; +import static java.lang.System.err; + +import com.code_intelligence.jazzer.agent.Agent; +import java.util.List; +import net.bytebuddy.agent.ByteBuddyAgent; + public class Driver { // Accessed from jazzer_main.cpp. @SuppressWarnings("unused") private static int start(byte[][] nativeArgs) { - return FuzzTargetRunner.startLibFuzzer(Utils.fromNativeArgs(nativeArgs)); + List args = Utils.fromNativeArgs(nativeArgs); + + // Do *not* modify system properties beyond this point - initializing Opt parses them as a side + // effect. + + if (Opt.hooks) { + Agent.premain(Opt.agentArgs, ByteBuddyAgent.install()); + } + + return FuzzTargetRunner.startLibFuzzer(args); } } diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index 02932ec1..e1244ade 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -207,8 +207,11 @@ public final class FuzzTargetRunner { /* * Starts libFuzzer via LLVMFuzzerRunDriver. + * + * Note: Must be public rather than package-private as it is loaded in a different class loader + * than Driver. */ - static int startLibFuzzer(List args) { + public static int startLibFuzzer(List args) { return startLibFuzzer(Utils.toNativeArgs(args)); } diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java index 201b8695..e5b98f61 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java @@ -33,7 +33,8 @@ import java.util.stream.Stream; * *

    Every public field should be deeply immutable. */ -final class Opt { +public final class Opt { + public static final String agentArgs = stringSetting("agent_args", ""); public static final String autofuzz = stringSetting("autofuzz", ""); public static final List autofuzzIgnore = stringListSetting("autofuzz_ignore", ','); public static final String coverageDump = stringSetting("coverage_dump", ""); diff --git a/repositories.bzl b/repositories.bzl index e6347ab1..8e757f15 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -96,6 +96,13 @@ def jazzer_dependencies(): url = "https://github.com/fmeum/rules_jni/archive/refs/tags/v0.5.2.tar.gz", ) + maybe( + http_jar, + name = "net_bytebuddy_byte_buddy_agent", + sha256 = "25eed4301bbde3724a4bac0e7fe4a0b371c64b5fb40160b29480de3afd04efd5", + url = "https://repo1.maven.org/maven2/net/bytebuddy/byte-buddy-agent/1.12.13/byte-buddy-agent-1.12.13.jar", + ) + maybe( http_jar, name = "org_ow2_asm_asm", -- cgit v1.2.3 From 5a2137e1628012bc0911bb3a22b9022d7128ca24 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 11 Aug 2022 10:00:30 +0200 Subject: all: Handle argument pre-processing in the Java driver --- .../com/code_intelligence/jazzer/agent/Agent.kt | 6 +- .../com/code_intelligence/jazzer/agent/BUILD.bazel | 1 + driver/BUILD.bazel | 1 - driver/fuzzed_data_provider_test.cpp | 2 +- driver/jazzer_main.cpp | 102 +-------------------- driver/jvm_tooling.cpp | 16 +--- driver/jvm_tooling.h | 2 +- driver/jvm_tooling_test.cpp | 2 +- .../code_intelligence/jazzer/driver/BUILD.bazel | 5 +- .../code_intelligence/jazzer/driver/Driver.java | 60 +++++++++++- .../com/code_intelligence/jazzer/driver/Opt.java | 1 + 11 files changed, 78 insertions(+), 120 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index d3b4c4dd..3690c5cf 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -16,6 +16,7 @@ package com.code_intelligence.jazzer.agent +import com.code_intelligence.jazzer.driver.Opt import com.code_intelligence.jazzer.instrumentor.CoverageRecorder import com.code_intelligence.jazzer.instrumentor.Hooks import com.code_intelligence.jazzer.instrumentor.InstrumentationType @@ -43,7 +44,6 @@ private val KNOWN_ARGUMENTS = listOf( "trace", "custom_hooks", "disabled_hooks", - "id_sync_file", "dump_classes_dir", ) @@ -140,8 +140,8 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { } } }.toSet() - val idSyncFile = argumentMap["id_sync_file"]?.let { - Paths.get(it.single()).also { path -> + val idSyncFile = Opt.idSyncFile.takeUnless { it.isEmpty() }?.let { + Paths.get(it).also { path -> println("INFO: Synchronizing coverage IDs in ${path.toAbsolutePath()}") } } diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel index 4559dbba..84dd4c19 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel @@ -12,5 +12,6 @@ kt_jvm_library( "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor", "//agent/src/main/java/com/code_intelligence/jazzer/runtime", "//agent/src/main/java/com/code_intelligence/jazzer/runtime:signal_handler", + "//driver/src/main/java/com/code_intelligence/jazzer/driver:opt", ], ) diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index 1d74678d..6de3bf75 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -112,7 +112,6 @@ cc_library( deps = [ ":jvm_tooling_lib", "@com_google_absl//absl/strings", - "@com_google_absl//absl/strings:str_format", "@com_google_glog//:glog", "@fmeum_rules_jni//jni:libjvm", "@jazzer_com_github_gflags_gflags//:gflags", diff --git a/driver/fuzzed_data_provider_test.cpp b/driver/fuzzed_data_provider_test.cpp index 438a90e8..c0a12d70 100644 --- a/driver/fuzzed_data_provider_test.cpp +++ b/driver/fuzzed_data_provider_test.cpp @@ -115,7 +115,7 @@ class FuzzedDataProviderTest : public ::testing::Test { Runfiles* runfiles = Runfiles::CreateForTest(); FLAGS_cp = runfiles->Rlocation(FLAGS_cp); - jvm_ = std::make_unique("test_executable", "1234"); + jvm_ = std::make_unique("test_executable"); } static void TearDownTestCase() { jvm_.reset(nullptr); } diff --git a/driver/jazzer_main.cpp b/driver/jazzer_main.cpp index ce8201a3..0697bd3e 100644 --- a/driver/jazzer_main.cpp +++ b/driver/jazzer_main.cpp @@ -24,15 +24,11 @@ #include #include -#include #include -#include #include #include #include "absl/strings/match.h" -#include "absl/strings/str_format.h" -#include "absl/strings/strip.h" #include "gflags/gflags.h" #include "glog/logging.h" #include "jvm_tooling.h" @@ -42,15 +38,6 @@ using namespace std::string_literals; // Defined by glog DECLARE_bool(log_prefix); -// Defined in jvm_tooling.cpp -DECLARE_string(id_sync_file); - -// Defined in fuzz_target_runner.cpp -DECLARE_string(coverage_report); - -// Defined in fuzz_target_runner.cpp -DECLARE_string(coverage_dump); - namespace { bool is_asan_active = false; } @@ -81,22 +68,6 @@ const std::string kUsageMessage = const std::string kDriverClassName = "com/code_intelligence/jazzer/driver/Driver"; -std::string GetNewTempFilePath() { - auto temp_dir = std::filesystem::temp_directory_path(); - - std::string temp_filename_suffix(32, '\0'); - std::random_device rng; - std::uniform_int_distribution dist(0, 'z' - 'a'); - std::generate_n(temp_filename_suffix.begin(), temp_filename_suffix.length(), - [&rng, &dist] { return static_cast('a' + dist(rng)); }); - - auto temp_path = temp_dir / ("jazzer-" + temp_filename_suffix); - if (std::filesystem::exists(temp_path)) - throw std::runtime_error("Random temp file path exists: " + - temp_path.string()); - return temp_path.string(); -} - int StartLibFuzzer(std::unique_ptr jvm, std::vector argv) { JNIEnv &env = jvm->GetEnv(); @@ -176,81 +147,12 @@ int main(int argc, char **argv) { gflags::ParseCommandLineFlags(&our_argc, &our_argv, false); } - // The potentially modified command line arguments passed to libFuzzer at the - // end of this function. - std::vector modified_argv = - std::vector(argv, argv_end); - - bool spawns_subprocesses = false; - if (std::any_of(argv, argv_end, [](std::string_view arg) { - return absl::StartsWith(arg, "-fork=") || - absl::StartsWith(arg, "-jobs=") || - absl::StartsWith(arg, "-merge="); - })) { - spawns_subprocesses = true; - if (!FLAGS_coverage_report.empty()) { - LOG(WARNING) << "WARN: --coverage_report does not support parallel " - "fuzzing and has been disabled"; - FLAGS_coverage_report = ""; - } - if (!FLAGS_coverage_dump.empty()) { - LOG(WARNING) << "WARN: --coverage_dump does not support parallel " - "fuzzing and has been disabled"; - FLAGS_coverage_dump = ""; - } - if (FLAGS_id_sync_file.empty()) { - // Create an empty temporary file used for coverage ID synchronization and - // pass its path to the agent in every child process. This requires adding - // the argument to argv for it to be picked up by libFuzzer, which then - // forwards it to child processes. - FLAGS_id_sync_file = GetNewTempFilePath(); - modified_argv.emplace_back( - absl::StrFormat("--id_sync_file=%s", FLAGS_id_sync_file)); - } - // Creates the file, truncating it if it exists. - std::ofstream touch_file(FLAGS_id_sync_file, std::ios_base::trunc); - - auto cleanup_fn = [] { - try { - std::filesystem::remove(std::filesystem::path(FLAGS_id_sync_file)); - } catch (...) { - // We should not throw exceptions during shutdown. - } - }; - std::atexit(cleanup_fn); - } - - std::string seed; - // Search for the last occurence of a "-seed" argument as that is the one that - // is used by libFuzzer. - auto seed_pos = std::find_if( - std::reverse_iterator(argv_end), std::reverse_iterator(argv), - [](std::string_view arg) { return absl::StartsWith(arg, "-seed="); }); - if (seed_pos != std::reverse_iterator(argv)) { - // An explicit seed has been provided on the command-line, record its value - // so that it can be forwarded to the agent. - seed = absl::StripPrefix(*seed_pos, "-seed="); - } else { - // No explicit seed has been set. Since Jazzer hooks might still want to use - // a seed and we have to ensure that a fuzzing run can be reproduced by - // setting the seed printed by libFuzzer, we generate a seed for it here so - // that the two stay in sync. - unsigned int random_seed = std::random_device()(); - seed = std::to_string(random_seed); - // Only add the -seed argument to the command line if not running in a mode - // that spawns subprocesses. These would inherit the same seed, which might - // make them less effective. - if (!spawns_subprocesses) { - modified_argv.emplace_back("-seed=" + seed); - } - } - if (is_asan_active) { std::cerr << "WARN: Jazzer is not compatible with LeakSanitizer yet. Leaks " "are not reported." << std::endl; } - return StartLibFuzzer(std::make_unique(argv[0], seed), - modified_argv); + return StartLibFuzzer(std::make_unique(argv[0]), + std::vector(argv, argv_end)); } diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index 54eb7d7c..6ac50405 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -204,7 +204,6 @@ std::string agentArgsFromFlags() { {"custom_hook_includes", FLAGS_custom_hook_includes}, {"custom_hook_excludes", FLAGS_custom_hook_excludes}, {"trace", FLAGS_trace}, - {"id_sync_file", FLAGS_id_sync_file}, {"dump_classes_dir", FLAGS_dump_classes_dir}, }) { if (!flag_pair.second.empty()) { @@ -214,7 +213,7 @@ std::string agentArgsFromFlags() { return absl::StrJoin(args, ","); } -std::vector fuzzTargetRunnerFlagsAsDefines() { +std::vector optsAsDefines() { return { absl::StrFormat("-Djazzer.target_class=%s", FLAGS_target_class), absl::StrFormat("-Djazzer.target_args=%s", FLAGS_target_args), @@ -228,6 +227,7 @@ std::vector fuzzTargetRunnerFlagsAsDefines() { absl::StrFormat("-Djazzer.autofuzz_ignore=%s", FLAGS_autofuzz_ignore), absl::StrFormat("-Djazzer.hooks=%s", FLAGS_hooks ? "true" : "false"), absl::StrFormat("-Djazzer.agent_args=%s", agentArgsFromFlags()), + absl::StrFormat("-Djazzer.id_sync_file=%s", FLAGS_id_sync_file), }; } @@ -262,7 +262,7 @@ std::vector splitEscaped(const std::string &str) { namespace jazzer { -JVM::JVM(std::string_view executable_path, std::string_view seed) { +JVM::JVM(std::string_view executable_path) { // combine class path from command line flags and JAVA_FUZZER_CLASSPATH env // variable std::string class_path = absl::StrFormat("-Djava.class.path=%s", FLAGS_cp); @@ -289,15 +289,9 @@ JVM::JVM(std::string_view executable_path, std::string_view seed) { options.push_back(JavaVMOption{.optionString = (char *)"-XX:+UseParallelGC"}); options.push_back( JavaVMOption{.optionString = (char *)"-XX:+CriticalJNINatives"}); - // Forward libFuzzer's random seed so that Jazzer hooks can base their - // mutations on it. - std::string seed_property = absl::StrFormat("-Djazzer.seed=%s", seed); - options.push_back( - JavaVMOption{.optionString = const_cast(seed_property.c_str())}); - std::vector fuzz_target_runner_defines = - fuzzTargetRunnerFlagsAsDefines(); - for (const auto &define : fuzz_target_runner_defines) { + std::vector opt_defines = optsAsDefines(); + for (const auto &define : opt_defines) { options.push_back( JavaVMOption{.optionString = const_cast(define.c_str())}); } diff --git a/driver/jvm_tooling.h b/driver/jvm_tooling.h index 80595f7a..7edb2c31 100644 --- a/driver/jvm_tooling.h +++ b/driver/jvm_tooling.h @@ -35,7 +35,7 @@ class JVM { public: // Creates a JVM instance with default options + options that were provided as // command line flags. - explicit JVM(std::string_view executable_path, std::string_view seed); + explicit JVM(std::string_view executable_path); // Destroy the running JVM instance. ~JVM(); diff --git a/driver/jvm_tooling_test.cpp b/driver/jvm_tooling_test.cpp index 527ff4fb..916cba6c 100644 --- a/driver/jvm_tooling_test.cpp +++ b/driver/jvm_tooling_test.cpp @@ -43,7 +43,7 @@ class JvmToolingTest : public ::testing::Test { Runfiles *runfiles = Runfiles::CreateForTest(); FLAGS_cp = runfiles->Rlocation(FLAGS_cp); - jvm_ = std::make_unique("test_executable", "1234"); + jvm_ = std::make_unique("test_executable"); } static void TearDownTestCase() { jvm_.reset(nullptr); } diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel b/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel index c6588c4b..1a184d98 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -46,7 +46,10 @@ java_library( java_library( name = "opt", srcs = ["Opt.java"], - visibility = ["//driver/src/test/java/com/code_intelligence/jazzer/driver:__pkg__"], + visibility = [ + "//agent/src/main/java/com/code_intelligence/jazzer:__subpackages__", + "//driver/src/test/java/com/code_intelligence/jazzer/driver:__pkg__", + ], ) java_library( diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java index a4521434..5dd05d36 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java @@ -19,15 +19,73 @@ package com.code_intelligence.jazzer.driver; import static java.lang.System.err; import com.code_intelligence.jazzer.agent.Agent; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.SecureRandom; import java.util.List; import net.bytebuddy.agent.ByteBuddyAgent; public class Driver { // Accessed from jazzer_main.cpp. @SuppressWarnings("unused") - private static int start(byte[][] nativeArgs) { + private static int start(byte[][] nativeArgs) throws IOException { List args = Utils.fromNativeArgs(nativeArgs); + final boolean spawnsSubprocesses = args.stream().anyMatch( + arg -> arg.startsWith("-fork=") || arg.startsWith("-jobs=") || arg.startsWith("-merge=")); + if (spawnsSubprocesses) { + if (!System.getProperty("jazzer.coverage_report", "").isEmpty()) { + err.println( + "WARN: --coverage_report does not support parallel fuzzing and has been disabled"); + System.clearProperty("jazzer.coverage_report"); + } + if (!System.getProperty("jazzer.coverage_dump", "").isEmpty()) { + err.println( + "WARN: --coverage_dump does not support parallel fuzzing and has been disabled"); + System.clearProperty("jazzer.coverage_dump"); + } + + String idSyncFileArg = System.getProperty("jazzer.id_sync_file", ""); + Path idSyncFile; + if (idSyncFileArg.isEmpty()) { + // Create an empty temporary file used for coverage ID synchronization and + // pass its path to the agent in every child process. This requires adding + // the argument to argv for it to be picked up by libFuzzer, which then + // forwards it to child processes. + idSyncFile = Files.createTempFile("jazzer-", ""); + args.add("--id_sync_file=" + idSyncFile.toAbsolutePath()); + } else { + // Creates the file, truncating it if it exists. + idSyncFile = Files.write(Paths.get(idSyncFileArg), new byte[] {}); + } + // This wouldn't run in case we exit the process with _Exit, but the parent process of a -fork + // run is expected to exit with a regular exit(0), which does cause JVM shutdown hooks to run: + // https://github.com/llvm/llvm-project/blob/940e178c0018b32af2f1478d331fc41a92a7dac7/compiler-rt/lib/fuzzer/FuzzerFork.cpp#L491 + idSyncFile.toFile().deleteOnExit(); + } + + // Jazzer's hooks use deterministic randomness and thus require a seed. Search for the last + // occurrence of a "-seed" argument as that is the one that is used by libFuzzer. If none is + // set, generate one and pass it to libFuzzer so that a fuzzing run can be reproduced simply by + // setting the seed printed by libFuzzer. + String seed = + args.stream() + .reduce( + (prev, cur) -> cur.startsWith("-seed=") ? cur.substring("-seed=".length()) : prev) + .orElseGet(() -> { + String newSeed = Integer.toUnsignedString(new SecureRandom().nextInt()); + // Only add the -seed argument to the command line if not running in a mode + // that spawns subprocesses. These would inherit the same seed, which might + // make them less effective. + if (spawnsSubprocesses) { + args.add("-seed=" + newSeed); + } + return newSeed; + }); + System.setProperty("jazzer.seed", seed); + // Do *not* modify system properties beyond this point - initializing Opt parses them as a side // effect. diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java index e5b98f61..509e5084 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java @@ -43,6 +43,7 @@ public final class Opt { // Default to false if hooks is false to mimic the original behavior of the native fuzz target // runner, but still support hooks = false && dedup = true. public static boolean dedup = boolSetting("dedup", hooks); + public static final String idSyncFile = stringSetting("id_sync_file", null); public static final Set ignore = Collections.unmodifiableSet(stringListSetting("ignore", ',') .stream() -- cgit v1.2.3 From 193908f37c4888e1feb931978a37a874b9e3d250 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 11 Aug 2022 11:38:38 +0200 Subject: all: Parse agent arguments in Opt Centralizes all options parsing in Opt. --- README.md | 2 +- .../com/code_intelligence/jazzer/agent/Agent.kt | 54 ++++------------------ driver/jvm_tooling.cpp | 37 ++++++--------- .../code_intelligence/jazzer/driver/Driver.java | 2 +- .../com/code_intelligence/jazzer/driver/Opt.java | 27 +++++++++-- 5 files changed, 48 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index ff5875f2..12137ae7 100644 --- a/README.md +++ b/README.md @@ -429,7 +429,7 @@ The particular instrumentation types to apply can be specified using the `--trac * `indir`: call through `Method#invoke` * `all`: shorthand to apply all available instrumentations (except `gep`) -Multiple instrumentation types can be combined with a colon. +Multiple instrumentation types can be combined with a colon (Linux, macOS) or a semicolon (Windows). ### Value Profile diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index 3690c5cf..f5fee3a2 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -36,17 +36,6 @@ import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.exists import kotlin.io.path.isDirectory -private val KNOWN_ARGUMENTS = listOf( - "instrumentation_includes", - "instrumentation_excludes", - "custom_hook_includes", - "custom_hook_excludes", - "trace", - "custom_hooks", - "disabled_hooks", - "dump_classes_dir", -) - // To be accessible by the agent classes the native library has to be loaded by the same class loader. // premain is executed in the context of the system class loader. At the beginning of premain the agent jar is added to // the bootstrap class loader and all subsequently required agent classes are loaded by it. Hence, it's not possible to @@ -68,10 +57,8 @@ fun jarUriForClass(clazz: Class<*>): URI? { return clazz.protectionDomain?.codeSource?.location?.toURI() } -private val argumentDelimiter = - if (System.getProperty("os.name").startsWith("Windows")) ";" else ":" - @OptIn(ExperimentalPathApi::class) +@Suppress("UNUSED_PARAMETER") fun premain(agentArgs: String?, instrumentation: Instrumentation) { // Add the agent jar (i.e., the jar out of which we are currently executing) to the search path of the bootstrap // class loader to ensure that instrumented classes can find the CoverageMap class regardless of which ClassLoader @@ -83,45 +70,24 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { } NativeLibraryLoader.load() - val argumentMap = (agentArgs ?: "") - .split(',') - .mapNotNull { - val splitArg = it.split('=', limit = 2) - when { - splitArg.size != 2 -> { - if (splitArg[0].isNotEmpty()) - println("WARN: Ignoring argument ${splitArg[0]} without value") - null - } - splitArg[0] !in KNOWN_ARGUMENTS -> { - println("WARN: Ignoring unknown argument ${splitArg[0]}") - null - } - else -> splitArg[0] to splitArg[1].split(argumentDelimiter) - } - }.toMap() val manifestCustomHookNames = ManifestUtils.combineManifestValues(ManifestUtils.HOOK_CLASSES).flatMap { it.split(':') }.filter { it.isNotBlank() } - val allCustomHookNames = manifestCustomHookNames + (argumentMap["custom_hooks"] ?: emptySet()) - val disabledCustomHookNames = argumentMap["disabled_hooks"]?.toSet() ?: emptySet() + val allCustomHookNames = (manifestCustomHookNames + Opt.customHooks).toSet() + val disabledCustomHookNames = Opt.disabledHooks.toSet() val customHookNames = allCustomHookNames - disabledCustomHookNames val disabledCustomHooksToPrint = allCustomHookNames - customHookNames.toSet() if (disabledCustomHooksToPrint.isNotEmpty()) { println("INFO: Not using the following disabled hooks: ${disabledCustomHooksToPrint.joinToString(", ")}") } - val classNameGlobber = ClassNameGlobber( - argumentMap["instrumentation_includes"] ?: emptyList(), - (argumentMap["instrumentation_excludes"] ?: emptyList()) + customHookNames - ) + val classNameGlobber = ClassNameGlobber(Opt.instrumentationIncludes, Opt.instrumentationExcludes + customHookNames) CoverageRecorder.classNameGlobber = classNameGlobber - val customHookClassNameGlobber = ClassNameGlobber( - argumentMap["custom_hook_includes"] ?: emptyList(), - (argumentMap["custom_hook_excludes"] ?: emptyList()) + customHookNames - ) - val instrumentationTypes = (argumentMap["trace"] ?: listOf("all")).flatMap { + val customHookClassNameGlobber = ClassNameGlobber(Opt.customHookIncludes, Opt.customHookExcludes + customHookNames) + // FIXME: Setting trace to the empty string explicitly results in all rather than no trace types + // being applied - this is unintuitive. + val instrumentationTypes = (Opt.trace.takeIf { it.isNotEmpty() } ?: listOf("all")).flatMap { when (it) { "cmp" -> setOf(InstrumentationType.CMP) "cov" -> setOf(InstrumentationType.COV) @@ -145,8 +111,8 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { println("INFO: Synchronizing coverage IDs in ${path.toAbsolutePath()}") } } - val dumpClassesDir = argumentMap["dump_classes_dir"]?.let { - Paths.get(it.single()).toAbsolutePath().also { path -> + val dumpClassesDir = Opt.dumpClassesDir.takeUnless { it.isEmpty() }?.let { + Paths.get(it).toAbsolutePath().also { path -> if (path.exists() && path.isDirectory()) { println("INFO: Dumping instrumented classes into $path") } else { diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index 6ac50405..9afeeb4b 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -72,7 +72,8 @@ DEFINE_string(disabled_hooks, "", "loaded (separator is ':' on Linux/macOS and ';' on Windows)"); DEFINE_string( trace, "", - "list of instrumentation to perform separated by colon \":\". " + "list of instrumentation to perform separated by colon ':' on Linux/macOS " + "and ';' on Windows. " "Available options are cov, cmp, div, gep, all. These options " "correspond to the \"-fsanitize-coverage=trace-*\" flags in clang."); DEFINE_string( @@ -192,27 +193,6 @@ std::string getInstrumentorAgentPath(std::string_view executable_path) { exit(1); } -std::string agentArgsFromFlags() { - std::vector args; - for (const auto &flag_pair : - std::vector>{ - // {, } - {"instrumentation_includes", FLAGS_instrumentation_includes}, - {"instrumentation_excludes", FLAGS_instrumentation_excludes}, - {"custom_hooks", FLAGS_custom_hooks}, - {"disabled_hooks", FLAGS_disabled_hooks}, - {"custom_hook_includes", FLAGS_custom_hook_includes}, - {"custom_hook_excludes", FLAGS_custom_hook_excludes}, - {"trace", FLAGS_trace}, - {"dump_classes_dir", FLAGS_dump_classes_dir}, - }) { - if (!flag_pair.second.empty()) { - args.push_back(flag_pair.first + "=" + flag_pair.second); - } - } - return absl::StrJoin(args, ","); -} - std::vector optsAsDefines() { return { absl::StrFormat("-Djazzer.target_class=%s", FLAGS_target_class), @@ -226,8 +206,19 @@ std::vector optsAsDefines() { absl::StrFormat("-Djazzer.autofuzz=%s", FLAGS_autofuzz), absl::StrFormat("-Djazzer.autofuzz_ignore=%s", FLAGS_autofuzz_ignore), absl::StrFormat("-Djazzer.hooks=%s", FLAGS_hooks ? "true" : "false"), - absl::StrFormat("-Djazzer.agent_args=%s", agentArgsFromFlags()), absl::StrFormat("-Djazzer.id_sync_file=%s", FLAGS_id_sync_file), + absl::StrFormat("-Djazzer.instrumentation_includes=%s", + FLAGS_instrumentation_includes), + absl::StrFormat("-Djazzer.instrumentation_excludes=%s", + FLAGS_instrumentation_excludes), + absl::StrFormat("-Djazzer.custom_hooks=%s", FLAGS_custom_hooks), + absl::StrFormat("-Djazzer.disabled_hooks=%s", FLAGS_disabled_hooks), + absl::StrFormat("-Djazzer.custom_hook_includes=%s", + FLAGS_custom_hook_includes), + absl::StrFormat("-Djazzer.custom_hook_excludes=%s", + FLAGS_custom_hook_excludes), + absl::StrFormat("-Djazzer.trace=%s", FLAGS_trace), + absl::StrFormat("-Djazzer.dump_classes_dir=%s", FLAGS_dump_classes_dir), }; } diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java index 5dd05d36..05e1a582 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java @@ -90,7 +90,7 @@ public class Driver { // effect. if (Opt.hooks) { - Agent.premain(Opt.agentArgs, ByteBuddyAgent.install()); + Agent.premain(null, ByteBuddyAgent.install()); } return FuzzTargetRunner.startLibFuzzer(args); diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java index 509e5084..417bfd5e 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java @@ -34,23 +34,32 @@ import java.util.stream.Stream; *

    Every public field should be deeply immutable. */ public final class Opt { - public static final String agentArgs = stringSetting("agent_args", ""); + private static final char SYSTEM_DELIMITER = + System.getProperty("os.name").startsWith("Windows") ? ';' : ':'; + public static final String autofuzz = stringSetting("autofuzz", ""); public static final List autofuzzIgnore = stringListSetting("autofuzz_ignore", ','); public static final String coverageDump = stringSetting("coverage_dump", ""); public static final String coverageReport = stringSetting("coverage_report", ""); + public static final List customHookIncludes = stringListSetting("custom_hook_includes"); + public static final List customHookExcludes = stringListSetting("custom_hook_excludes"); + public static final List customHooks = stringListSetting("custom_hooks"); + public static final List disabledHooks = stringListSetting("disabled_hooks"); + public static final String dumpClassesDir = stringSetting("dump_classes_dir", ""); public static final boolean hooks = boolSetting("hooks", true); - // Default to false if hooks is false to mimic the original behavior of the native fuzz target - // runner, but still support hooks = false && dedup = true. - public static boolean dedup = boolSetting("dedup", hooks); public static final String idSyncFile = stringSetting("id_sync_file", null); + public static final List instrumentationIncludes = + stringListSetting("instrumentation_includes"); + public static final List instrumentationExcludes = + stringListSetting("instrumentation_excludes"); public static final Set ignore = Collections.unmodifiableSet(stringListSetting("ignore", ',') .stream() .map(Long::parseUnsignedLong) .collect(Collectors.toSet())); - public static final String targetClass = stringSetting("target_class", ""); public static final String reproducerPath = stringSetting("reproducer_path", "."); + public static final String targetClass = stringSetting("target_class", ""); + public static final List trace = stringListSetting("trace"); // The values of these settings depend on autofuzz. public static final List targetArgs = autofuzz.isEmpty() @@ -60,6 +69,10 @@ public final class Opt { public static final long keepGoing = uint32Setting("keep_going", autofuzz.isEmpty() ? 1 : Integer.MIN_VALUE); + // Default to false if hooks is false to mimic the original behavior of the native fuzz target + // runner, but still support hooks = false && dedup = true. + public static boolean dedup = boolSetting("dedup", hooks); + static { if (!targetClass.isEmpty() && !autofuzz.isEmpty()) { err.println("--target_class and --autofuzz cannot be specified together"); @@ -86,6 +99,10 @@ public final class Opt { return System.getProperty(optionsPrefix + name, defaultValue); } + private static List stringListSetting(String name) { + return stringListSetting(name, SYSTEM_DELIMITER); + } + private static List stringListSetting(String name, char separator) { String value = System.getProperty(optionsPrefix + name); if (value == null || value.isEmpty()) { -- cgit v1.2.3 From b89d997b3b449d2d8cbe5d4de2326d75ba46fb79 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 11 Aug 2022 11:41:52 +0200 Subject: all: Move SignalHandler initialization into the driver Ensures that Ctrl+C works even with `--nohooks`. --- agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt | 3 --- agent/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel | 1 - agent/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel | 2 +- driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel | 1 + .../java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java | 2 ++ 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index f5fee3a2..e4591dd6 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -21,7 +21,6 @@ import com.code_intelligence.jazzer.instrumentor.CoverageRecorder import com.code_intelligence.jazzer.instrumentor.Hooks import com.code_intelligence.jazzer.instrumentor.InstrumentationType import com.code_intelligence.jazzer.runtime.NativeLibHooks -import com.code_intelligence.jazzer.runtime.SignalHandler import com.code_intelligence.jazzer.runtime.TraceCmpHooks import com.code_intelligence.jazzer.runtime.TraceDivHooks import com.code_intelligence.jazzer.runtime.TraceIndirHooks @@ -183,6 +182,4 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { println("WARN: ${classesToRetransform.joinToString()}") } } - - SignalHandler.initialize() } diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel index 84dd4c19..db6ae264 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel @@ -11,7 +11,6 @@ kt_jvm_library( deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor", "//agent/src/main/java/com/code_intelligence/jazzer/runtime", - "//agent/src/main/java/com/code_intelligence/jazzer/runtime:signal_handler", "//driver/src/main/java/com/code_intelligence/jazzer/driver:opt", ], ) 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 b6a0ad03..b408e985 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 @@ -44,7 +44,7 @@ jni_headers( java_library( name = "signal_handler", srcs = ["SignalHandler.java"], - visibility = ["//agent/src/main/java/com/code_intelligence/jazzer/agent:__pkg__"], + visibility = ["//driver/src/main/java/com/code_intelligence/jazzer/driver:__pkg__"], ) jni_headers( diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel b/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel index 1a184d98..05e04d02 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -32,6 +32,7 @@ java_jni_library( "//agent/src/main/java/com/code_intelligence/jazzer/runtime", "//agent/src/main/java/com/code_intelligence/jazzer/runtime:coverage_map", "//agent/src/main/java/com/code_intelligence/jazzer/runtime:fuzzed_data_provider", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:signal_handler", "//agent/src/main/java/com/code_intelligence/jazzer/utils", ], ) diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index e1244ade..8e5865db 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -27,6 +27,7 @@ import com.code_intelligence.jazzer.runtime.CoverageMap; import com.code_intelligence.jazzer.runtime.FuzzedDataProviderImpl; import com.code_intelligence.jazzer.runtime.JazzerInternal; import com.code_intelligence.jazzer.runtime.RecordingFuzzedDataProvider; +import com.code_intelligence.jazzer.runtime.SignalHandler; import com.code_intelligence.jazzer.utils.ExceptionUtils; import com.code_intelligence.jazzer.utils.ManifestUtils; import java.io.IOException; @@ -212,6 +213,7 @@ public final class FuzzTargetRunner { * than Driver. */ public static int startLibFuzzer(List args) { + SignalHandler.initialize(); return startLibFuzzer(Utils.toNativeArgs(args)); } -- cgit v1.2.3 From c90c0732a330edca363a2451d7bd9ddc932f3df7 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 11 Aug 2022 12:22:10 +0200 Subject: all: Apply code cleanup fixes --- .../java/com/code_intelligence/jazzer/autofuzz/Meta.java | 10 ++++++---- .../java/com/code_intelligence/jazzer/utils/ManifestUtils.kt | 2 +- .../java/com/code_intelligence/jazzer/tools/JarStripper.java | 12 +++++++----- .../main/java/com/code_intelligence/jazzer/driver/Opt.java | 2 +- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java index e65e4316..3d48017f 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java @@ -51,11 +51,13 @@ import net.jodah.typetools.TypeResolver; import net.jodah.typetools.TypeResolver.Unknown; public class Meta { - static WeakHashMap, List>> implementingClassesCache = new WeakHashMap<>(); - static WeakHashMap, List>> nestedBuilderClassesCache = new WeakHashMap<>(); - static WeakHashMap, List> originalObjectCreationMethodsCache = + static final WeakHashMap, List>> implementingClassesCache = new WeakHashMap<>(); + static final WeakHashMap, List>> nestedBuilderClassesCache = + new WeakHashMap<>(); + static final WeakHashMap, List> originalObjectCreationMethodsCache = + new WeakHashMap<>(); + static final WeakHashMap, List> cascadingBuilderMethodsCache = new WeakHashMap<>(); - static WeakHashMap, List> cascadingBuilderMethodsCache = new WeakHashMap<>(); public static Object autofuzz(FuzzedDataProvider data, Method method) { return autofuzz(data, method, null); diff --git a/agent/src/main/java/com/code_intelligence/jazzer/utils/ManifestUtils.kt b/agent/src/main/java/com/code_intelligence/jazzer/utils/ManifestUtils.kt index 25d0ade9..e7165e55 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/utils/ManifestUtils.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/utils/ManifestUtils.kt @@ -18,7 +18,7 @@ import java.util.jar.Manifest object ManifestUtils { - const val FUZZ_TARGET_CLASS = "Jazzer-Fuzz-Target-Class" + private const val FUZZ_TARGET_CLASS = "Jazzer-Fuzz-Target-Class" const val HOOK_CLASSES = "Jazzer-Hook-Classes" fun combineManifestValues(attribute: String): List { diff --git a/bazel/tools/java/com/code_intelligence/jazzer/tools/JarStripper.java b/bazel/tools/java/com/code_intelligence/jazzer/tools/JarStripper.java index 0bbb1b09..2a567c68 100644 --- a/bazel/tools/java/com/code_intelligence/jazzer/tools/JarStripper.java +++ b/bazel/tools/java/com/code_intelligence/jazzer/tools/JarStripper.java @@ -29,6 +29,7 @@ import java.util.HashMap; import java.util.Map; import java.util.TimeZone; import java.util.stream.Collectors; +import java.util.stream.Stream; public class JarStripper { private static final Map ZIP_FS_PROPERTIES = new HashMap<>(); @@ -77,11 +78,12 @@ public class JarStripper { try (FileSystem zipFs = FileSystems.newFileSystem(outUri, ZIP_FS_PROPERTIES)) { for (String pathToDelete : pathsToDelete) { // Visit files before the directory they are contained in by sorting in reverse order. - Iterable subpaths = Files.walk(zipFs.getPath(pathToDelete)) - .sorted(Comparator.reverseOrder()) - .collect(Collectors.toList()); - for (Path subpath : subpaths) { - Files.delete(subpath); + try (Stream walk = Files.walk(zipFs.getPath(pathToDelete))) { + Iterable subpaths = + walk.sorted(Comparator.reverseOrder()).collect(Collectors.toList()); + for (Path subpath : subpaths) { + Files.delete(subpath); + } } } } catch (IOException e) { diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java index 417bfd5e..377a85df 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java @@ -71,7 +71,7 @@ public final class Opt { // Default to false if hooks is false to mimic the original behavior of the native fuzz target // runner, but still support hooks = false && dedup = true. - public static boolean dedup = boolSetting("dedup", hooks); + public static final boolean dedup = boolSetting("dedup", hooks); static { if (!targetClass.isEmpty() && !autofuzz.isEmpty()) { -- cgit v1.2.3 From 0f3245c411e452803838d5fdfd366e397b5f3696 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 11 Aug 2022 12:44:44 +0200 Subject: driver: Enable assertions in Java --- driver/jvm_tooling.cpp | 1 - .../main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index 9afeeb4b..74bc6cfe 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -271,7 +271,6 @@ JVM::JVM(std::string_view executable_path) { // Set the maximum heap size to a value that is slightly smaller than // libFuzzer's default rss_limit_mb. This prevents erroneous oom reports. options.push_back(JavaVMOption{.optionString = (char *)"-Xmx1800m"}); - options.push_back(JavaVMOption{.optionString = (char *)"-enableassertions"}); // Preserve and emit stack trace information even on hot paths. // This may hurt performance, but also helps find flaky bugs. options.push_back( diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index 8e5865db..40ee938d 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -80,6 +80,7 @@ public final class FuzzTargetRunner { if (notBootstrapLoader == null) { notBootstrapLoader = ClassLoader.getSystemClassLoader(); } + notBootstrapLoader.setDefaultAssertionStatus(true); fuzzTargetClass = Class.forName(targetClassName, false, notBootstrapLoader); } catch (ClassNotFoundException e) { err.print("ERROR: "); -- cgit v1.2.3 From 8659be88166c3c2a7ef9da5b735b6a647e6014c8 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 15 Aug 2022 10:28:29 +0200 Subject: driver: Set a default -rss_limit_mb This is necessary for a pure Java driver as we can no longer set -Xmx in that situation. It is also much cleaner than hand-tuning -Xmx, but we still keep the max heap size in the native driver for backwards compatibility with existing crashing inputs. --- .../java/com/code_intelligence/jazzer/driver/Driver.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java index 05e1a582..462b7023 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java @@ -86,6 +86,10 @@ public class Driver { }); System.setProperty("jazzer.seed", seed); + if (args.stream().noneMatch(arg -> arg.startsWith("-rss_limit_mb="))) { + args.add(getDefaultRssLimitMbArg()); + } + // Do *not* modify system properties beyond this point - initializing Opt parses them as a side // effect. @@ -95,4 +99,15 @@ public class Driver { return FuzzTargetRunner.startLibFuzzer(args); } + + private static String getDefaultRssLimitMbArg() { + // Java OutOfMemoryErrors are strictly more informative than libFuzzer's out of memory crashes. + // We thus want to scale the default libFuzzer memory limit, which includes all memory used by + // the process including Jazzer's native and non-native memory footprint, such that: + // 1. we never reach it purely by allocating memory on the Java heap; + // 2. it is still reached if the fuzz target allocates excessively on the native heap. + // As a heuristic, we set the overall memory limit to 2 * the maximum size of the Java heap. + long maxHeapInBytes = Runtime.getRuntime().maxMemory(); + return "-rss_limit_mb=" + (2 * maxHeapInBytes / (1024 * 1024)); + } } -- cgit v1.2.3 From dccc3ae7e8e213164dc2c8df36088a862878f9ba Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 16 Aug 2022 17:33:24 +0200 Subject: examples: Speculative fix for Windows CI failure --- examples/src/main/native/com/example/BUILD.bazel | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examples/src/main/native/com/example/BUILD.bazel b/examples/src/main/native/com/example/BUILD.bazel index 7f23f75e..4c44327a 100644 --- a/examples/src/main/native/com/example/BUILD.bazel +++ b/examples/src/main/native/com/example/BUILD.bazel @@ -9,6 +9,13 @@ cc_jni_library( "-fsanitize=fuzzer-no-link,address", "-fno-sanitize-blacklist", ], + defines = [ + # Workaround for Windows build failures with VS 2022: + # "lld-link: error: /INFERASANLIBS is not allowed in .drectve" + # https://github.com/llvm/llvm-project/issues/56300#issuecomment-1214313292 + "_DISABLE_STRING_ANNOTATION=1", + "_DISABLE_VECTOR_ANNOTATION=1", + ], linkopts = select({ "//:clang_on_linux": ["-fuse-ld=lld"], "@platforms//os:windows": [ -- cgit v1.2.3 From a9b21fbf119ab42003dad9d2810cfa99f3f97b02 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 17 Aug 2022 09:58:40 +0200 Subject: driver: Fix condition for adding explicit -seed flag The condition was inverted. --- driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java index 462b7023..75dec5e5 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java @@ -79,7 +79,7 @@ public class Driver { // Only add the -seed argument to the command line if not running in a mode // that spawns subprocesses. These would inherit the same seed, which might // make them less effective. - if (spawnsSubprocesses) { + if (!spawnsSubprocesses) { args.add("-seed=" + newSeed); } return newSeed; -- cgit v1.2.3 From 8c4f8830a5e69421d7ab835a5409bc41efca61b8 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 17 Aug 2022 10:20:45 +0200 Subject: driver: Fix driver crash when not supplying -seed The reduce function was misused - prev was always the first CLI argument, not an empty Optional. --- .../jazzer/tools/FuzzTargetTestWrapper.java | 28 +++++++++++------- .../code_intelligence/jazzer/driver/Driver.java | 25 +++++++--------- tests/BUILD.bazel | 14 +++++++++ tests/src/test/java/com/example/NoSeedFuzzer.java | 34 ++++++++++++++++++++++ 4 files changed, 76 insertions(+), 25 deletions(-) create mode 100644 tests/src/test/java/com/example/NoSeedFuzzer.java diff --git a/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java b/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java index f4ca0ce7..107d8526 100644 --- a/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java +++ b/bazel/tools/java/com/code_intelligence/jazzer/tools/FuzzTargetTestWrapper.java @@ -23,13 +23,13 @@ import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; import javax.tools.JavaFileObject; @@ -48,7 +48,7 @@ public class FuzzTargetTestWrapper { boolean verifyCrashReproducer; boolean expectCrash; Set expectedFindings; - Stream arguments; + List arguments; try { runfiles = Runfiles.create(); driverActualPath = lookUpRunfile(runfiles, args[0]); @@ -60,8 +60,11 @@ public class FuzzTargetTestWrapper { expectedFindings = Arrays.stream(args[6].split(",")).filter(s -> !s.isEmpty()).collect(Collectors.toSet()); // Map all files/dirs to real location - arguments = Arrays.stream(args).skip(7).map( - arg -> arg.startsWith("-") ? arg : lookUpRunfileWithFallback(runfiles, arg)); + arguments = + Arrays.stream(args) + .skip(7) + .map(arg -> arg.startsWith("-") ? arg : lookUpRunfileWithFallback(runfiles, arg)) + .collect(Collectors.toList()); } catch (IOException | ArrayIndexOutOfBoundsException e) { e.printStackTrace(); System.exit(1); @@ -77,13 +80,16 @@ public class FuzzTargetTestWrapper { // so this is only useful for examples. String outputDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR"); - List command = - Stream - .concat(Stream.of(driverActualPath, String.format("-artifact_prefix=%s/", outputDir), - String.format("--reproducer_path=%s", outputDir), "-seed=2735196724", - String.format("--cp=%s", jarActualPath)), - arguments) - .collect(Collectors.toList()); + List command = new ArrayList<>(); + command.add(driverActualPath); + command.add(String.format("-artifact_prefix=%s/", outputDir)); + command.add(String.format("--reproducer_path=%s", outputDir)); + command.add(String.format("--cp=%s", jarActualPath)); + if (System.getenv("JAZZER_NO_EXPLICIT_SEED") == null) { + command.add("-seed=2735196724"); + } + command.addAll(arguments); + processBuilder.inheritIO(); if (JAZZER_CI) { // Make JVM error reports available in test outputs. diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java index 75dec5e5..45e7b72e 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java @@ -70,20 +70,17 @@ public class Driver { // occurrence of a "-seed" argument as that is the one that is used by libFuzzer. If none is // set, generate one and pass it to libFuzzer so that a fuzzing run can be reproduced simply by // setting the seed printed by libFuzzer. - String seed = - args.stream() - .reduce( - (prev, cur) -> cur.startsWith("-seed=") ? cur.substring("-seed=".length()) : prev) - .orElseGet(() -> { - String newSeed = Integer.toUnsignedString(new SecureRandom().nextInt()); - // Only add the -seed argument to the command line if not running in a mode - // that spawns subprocesses. These would inherit the same seed, which might - // make them less effective. - if (!spawnsSubprocesses) { - args.add("-seed=" + newSeed); - } - return newSeed; - }); + String seed = args.stream().reduce( + null, (prev, cur) -> cur.startsWith("-seed=") ? cur.substring("-seed=".length()) : prev); + if (seed == null) { + seed = Integer.toUnsignedString(new SecureRandom().nextInt()); + // Only add the -seed argument to the command line if not running in a mode + // that spawns subprocesses. These would inherit the same seed, which might + // make them less effective. + if (!spawnsSubprocesses) { + args.add("-seed=" + seed); + } + } System.setProperty("jazzer.seed", seed); if (args.stream().noneMatch(arg -> arg.startsWith("-rss_limit_mb="))) { diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index ba2a8f6d..c32d0b6a 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -223,3 +223,17 @@ java_fuzz_target_test( ], target_class = "com.example.SeedFuzzer", ) + +java_fuzz_target_test( + name = "NoSeedFuzzer", + timeout = "short", + srcs = ["src/test/java/com/example/NoSeedFuzzer.java"], + env = { + "JAZZER_NO_EXPLICIT_SEED": "1", + }, + expect_crash = False, + fuzzer_args = [ + "-runs=0", + ], + target_class = "com.example.NoSeedFuzzer", +) diff --git a/tests/src/test/java/com/example/NoSeedFuzzer.java b/tests/src/test/java/com/example/NoSeedFuzzer.java new file mode 100644 index 00000000..bf1c1103 --- /dev/null +++ b/tests/src/test/java/com/example/NoSeedFuzzer.java @@ -0,0 +1,34 @@ +/* + * Copyright 2022 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.example; + +import com.code_intelligence.jazzer.api.Jazzer; + +public class NoSeedFuzzer { + public static void fuzzerInitialize() { + // Verify that the seed was randomly generated and not taken to be the fixed + // one set in FuzzTargetTestWrapper. This has a 1 / INT_MAX chance to be + // flaky, which is acceptable. + if (Jazzer.SEED == (int) 2735196724L) { + System.err.println( + "Jazzer.SEED should not equal the fixed seed set in FuzzTargetTestWrapper"); + System.exit(1); + } + } + + public static void fuzzerTestOneInput(byte[] data) {} +} -- cgit v1.2.3 From 1b6b88353423c15aee62b8f3d1a081ad67f5a66e Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 17 Aug 2022 18:26:21 +0200 Subject: driver: Increase default -rss_limit_mb With -Xmx512m, the ExampleOutOfMemoryFuzzer ran into this failure on macOS: ==19173== ERROR: libFuzzer: out-of-memory (used: 961Mb; limit: 911Mb) --- driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java index 45e7b72e..5b107ad8 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/Driver.java @@ -103,8 +103,9 @@ public class Driver { // the process including Jazzer's native and non-native memory footprint, such that: // 1. we never reach it purely by allocating memory on the Java heap; // 2. it is still reached if the fuzz target allocates excessively on the native heap. - // As a heuristic, we set the overall memory limit to 2 * the maximum size of the Java heap. + // As a heuristic, we set the overall memory limit to 2 * the maximum size of the Java heap and + // add a fixed 1 GiB on top for the fuzzer's own memory usage. long maxHeapInBytes = Runtime.getRuntime().maxMemory(); - return "-rss_limit_mb=" + (2 * maxHeapInBytes / (1024 * 1024)); + return "-rss_limit_mb=" + ((2 * maxHeapInBytes / (1024 * 1024)) + 1024); } } -- cgit v1.2.3 From c003a889813199b83372e9df642456c473a7b10b Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 3 Aug 2022 12:08:05 +0200 Subject: ci: Use recommended BuildBuddy settings --- .bazelrc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.bazelrc b/.bazelrc index 2484b419..ac14c0d6 100644 --- a/.bazelrc +++ b/.bazelrc @@ -41,8 +41,8 @@ test --test_env=JAZZER_REFLECTION_DEBUG # CI tests (not using the toolchain to test OSS-Fuzz & local compatibility) test:ci --test_env=JAZZER_CI=1 build:ci --bes_results_url=https://app.buildbuddy.io/invocation/ -build:ci --bes_backend=grpcs://cloud.buildbuddy.io -build:ci --remote_cache=grpcs://cloud.buildbuddy.io +build:ci --bes_backend=grpcs://remote.buildbuddy.io +build:ci --remote_cache=grpcs://remote.buildbuddy.io build:ci --remote_timeout=3600 # Maven publishing (local only, requires GPG signature) -- cgit v1.2.3 From fc9311777d141b9010232c2fefbff190beb5b35b Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Wed, 3 Aug 2022 15:55:01 +0200 Subject: deps: Update Bazel to 5.3.0rc1 --- .bazelversion | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bazelversion b/.bazelversion index 91ff5727..bddfde6e 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -5.2.0 +5.3.0rc1 -- cgit v1.2.3 From 6ee1c1c39fe00ad36aacff32b526bca8eacd800f Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 18 Aug 2022 10:11:50 +0200 Subject: driver: Fix --keep_going not enabled implicitly for Autofuzz Makes `--keep_going` a proper uint64 flag as it was before the Java rewrite and ensures that the default value defined in Java is honored. --- driver/jvm_tooling.cpp | 8 ++++++-- .../com/code_intelligence/jazzer/driver/FuzzTargetRunner.java | 2 +- driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java | 6 +++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index 74bc6cfe..c084c344 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -194,10 +194,9 @@ std::string getInstrumentorAgentPath(std::string_view executable_path) { } std::vector optsAsDefines() { - return { + std::vector defines{ absl::StrFormat("-Djazzer.target_class=%s", FLAGS_target_class), absl::StrFormat("-Djazzer.target_args=%s", FLAGS_target_args), - absl::StrFormat("-Djazzer.keep_going=%d", FLAGS_keep_going), absl::StrFormat("-Djazzer.dedup=%s", FLAGS_dedup ? "true" : "false"), absl::StrFormat("-Djazzer.ignore=%s", FLAGS_ignore), absl::StrFormat("-Djazzer.reproducer_path=%s", FLAGS_reproducer_path), @@ -220,6 +219,11 @@ std::vector optsAsDefines() { absl::StrFormat("-Djazzer.trace=%s", FLAGS_trace), absl::StrFormat("-Djazzer.dump_classes_dir=%s", FLAGS_dump_classes_dir), }; + if (!gflags::GetCommandLineFlagInfoOrDie("keep_going").is_default) { + defines.emplace_back( + absl::StrFormat("-Djazzer.keep_going=%d", FLAGS_keep_going)); + } + return defines; } // Splits a string at the ARG_SEPARATOR unless it is escaped with a backslash. diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index 40ee938d..fd7a3255 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -197,7 +197,7 @@ public final class FuzzTargetRunner { // target. dumpReproducer(data); - if (Opt.keepGoing == 1 || ignoredTokens.size() >= Opt.keepGoing) { + if (Opt.keepGoing == 1 || Long.compareUnsigned(ignoredTokens.size(), Opt.keepGoing) >= 0) { // Reached the maximum amount of findings to keep going for, crash after shutdown. We use // _Exit rather than System.exit to not trigger libFuzzer's exit handlers. shutdown(); diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java index 377a85df..53179b79 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java @@ -67,7 +67,7 @@ public final class Opt { : Collections.unmodifiableList( Stream.concat(Stream.of(autofuzz), autofuzzIgnore.stream()).collect(Collectors.toList())); public static final long keepGoing = - uint32Setting("keep_going", autofuzz.isEmpty() ? 1 : Integer.MIN_VALUE); + uint64Setting("keep_going", autofuzz.isEmpty() ? 1 : Long.MAX_VALUE); // Default to false if hooks is false to mimic the original behavior of the native fuzz target // runner, but still support hooks = false && dedup = true. @@ -119,12 +119,12 @@ public final class Opt { return Boolean.parseBoolean(value); } - private static long uint32Setting(String name, int defaultValue) { + private static long uint64Setting(String name, long defaultValue) { String value = System.getProperty(optionsPrefix + name); if (value == null) { return defaultValue; } - return Integer.parseUnsignedInt(value, 10); + return Long.parseUnsignedLong(value, 10); } /** -- cgit v1.2.3 From ee34e9fb291f03c7a90795a1b5f91a0ec7fba90f Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 16 Aug 2022 14:42:39 +0200 Subject: driver: Load fuzz target with Driver's class loader This ensures that the fuzz target is found even if Driver is loaded by a custom class loader. --- .../jazzer/driver/FuzzTargetRunner.java | 61 ++++++++++++++++++---- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index fd7a3255..731cd82a 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -39,6 +39,7 @@ import java.lang.reflect.Modifier; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.HashSet; @@ -70,18 +71,58 @@ public final class FuzzTargetRunner { static { String targetClassName = determineFuzzTargetClassName(); + + // FuzzTargetRunner is loaded by the bootstrap class loader since Driver installs the agent + // before invoking FuzzTargetRunner.startLibFuzzer. We can't load the fuzz target with that + // class loader - we have to use the class loader that loaded Driver. This would be + // straightforward to do in Java 9+, but requires the use of reflection to maintain + // compatibility with Java 8, which doesn't have StackWalker. + // + // Note that we can't just move the agent initialization so that FuzzTargetRunner is loaded by + // Driver's class loader: The agent and FuzzTargetRunner have to share the native library that + // contains libFuzzer and that library needs to be available in the bootstrap class loader + // since instrumentation applied to Java standard library classes still needs to be able to call + // libFuzzer hooks. A fundamental JNI restriction is that a native library can't be shared + // between two different class loaders, so FuzzTargetRunner is thus forced to be loaded in the + // bootstrap class loader, which makes this ugly code block necessary. + // We also can't use the system class loader since Driver may be loaded by a custom class loader + // if not invoked from the native driver. + Class driverClass; try { - // When running with the agent, the JAR containing the agent and the driver has been added to - // the bootstrap class loader path at the time the native driver can use FindClass to load the - // Java fuzz target runner. As a result, FuzzTargetRunner's class loader will be the bootstrap - // class loader, which doesn't have the fuzz target on its classpath. We thus have to - // explicitly use the system class loader in this case. - ClassLoader notBootstrapLoader = FuzzTargetRunner.class.getClassLoader(); - if (notBootstrapLoader == null) { - notBootstrapLoader = ClassLoader.getSystemClassLoader(); + Class reflectionClass = Class.forName("sun.reflect.Reflection"); + try { + driverClass = + (Class) reflectionClass.getMethod("getCallerClass", int.class).invoke(null, 2); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new IllegalStateException(e); + } + } catch (ClassNotFoundException e) { + // sun.reflect.Reflection is no longer available after Java 8, use StackWalker. + try { + Class stackWalker = Class.forName("java.lang.StackWalker"); + Class> stackWalkerOption = + (Class>) Class.forName("java.lang.StackWalker$Option"); + Enum retainClassReferences = + Arrays.stream(stackWalkerOption.getEnumConstants()) + .filter(v -> v.name().equals("RETAIN_CLASS_REFERENCE")) + .findFirst() + .orElseThrow(() + -> new IllegalStateException( + "No RETAIN_CLASS_REFERENCE in java.lang.StackWalker$Option")); + Object stackWalkerInstance = stackWalker.getMethod("getInstance", stackWalkerOption) + .invoke(null, retainClassReferences); + Method stackWalkerGetCallerClass = stackWalker.getMethod("getCallerClass"); + driverClass = (Class) stackWalkerGetCallerClass.invoke(stackWalkerInstance); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException + | InvocationTargetException ex) { + throw new IllegalStateException(ex); } - notBootstrapLoader.setDefaultAssertionStatus(true); - fuzzTargetClass = Class.forName(targetClassName, false, notBootstrapLoader); + } + + try { + ClassLoader driverClassLoader = driverClass.getClassLoader(); + driverClassLoader.setDefaultAssertionStatus(true); + fuzzTargetClass = Class.forName(targetClassName, false, driverClassLoader); } catch (ClassNotFoundException e) { err.print("ERROR: "); e.printStackTrace(err); -- cgit v1.2.3 From 16b8e6bc5ac3b47e930c0d37b3ec7461ae21ee1e Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 16 Aug 2022 14:43:56 +0200 Subject: driver: Document that Opt is loaded in two class loaders --- driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java index 53179b79..477c7d38 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/Opt.java @@ -32,6 +32,12 @@ import java.util.stream.Stream; *

    Each option corresponds to a command-line argument of the driver of the same name. * *

    Every public field should be deeply immutable. + * + *

    This class is loaded twice: As it is used in {@link FuzzTargetRunner}, it is loaded in the + * class loader that loads {@link Driver}. It is also used in + * {@link com.code_intelligence.jazzer.agent.Agent} after the agent JAR has been added to the + * bootstrap classpath and thus is loaded again in the bootstrap loader. This is not a problem since + * it only provides immutable fields and has no non-fatal side effects. */ public final class Opt { private static final char SYSTEM_DELIMITER = -- cgit v1.2.3 From de57b39db8ed362fe2e93100de74597b453eeb02 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Mon, 15 Aug 2022 16:37:51 +0200 Subject: tests: Verify that value profiling works in native libraries --- tests/BUILD.bazel | 19 +++++++++++ .../java/com/example/NativeValueProfileFuzzer.java | 38 ++++++++++++++++++++++ tests/src/test/native/com/example/BUILD.bazel | 15 +++++++++ .../com/example/native_value_profile_fuzzer.cpp | 35 ++++++++++++++++++++ 4 files changed, 107 insertions(+) create mode 100644 tests/src/test/java/com/example/NativeValueProfileFuzzer.java create mode 100644 tests/src/test/native/com/example/BUILD.bazel create mode 100644 tests/src/test/native/com/example/native_value_profile_fuzzer.cpp diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index c32d0b6a..cc0814e9 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -1,3 +1,4 @@ +load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library") load("//bazel:compat.bzl", "SKIP_ON_MACOS", "SKIP_ON_WINDOWS") load("//bazel:fuzz_target.bzl", "java_fuzz_target_test") @@ -237,3 +238,21 @@ java_fuzz_target_test( ], target_class = "com.example.NoSeedFuzzer", ) + +java_jni_library( + name = "native_value_profile_fuzzer", + srcs = ["src/test/java/com/example/NativeValueProfileFuzzer.java"], + native_libs = ["//tests/src/test/native/com/example:native_value_profile_fuzzer"], + visibility = ["//tests/src/test/native/com/example:__pkg__"], + deps = ["//agent:jazzer_api_compile_only"], +) + +java_fuzz_target_test( + name = "NativeValueProfileFuzzer", + expected_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow"], + fuzzer_args = ["-use_value_profile=1"], + sanitizer = "address", + target_class = "com.example.NativeValueProfileFuzzer", + verify_crash_reproducer = False, + runtime_deps = [":native_value_profile_fuzzer"], +) diff --git a/tests/src/test/java/com/example/NativeValueProfileFuzzer.java b/tests/src/test/java/com/example/NativeValueProfileFuzzer.java new file mode 100644 index 00000000..1085a953 --- /dev/null +++ b/tests/src/test/java/com/example/NativeValueProfileFuzzer.java @@ -0,0 +1,38 @@ +/* + * Copyright 2022 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.example; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow; +import com.github.fmeum.rules_jni.RulesJni; + +public class NativeValueProfileFuzzer { + public static void fuzzerInitialize() { + RulesJni.loadLibrary("native_value_profile_fuzzer", NativeValueProfileFuzzer.class); + } + + public static void fuzzerTestOneInput(FuzzedDataProvider data) { + long[] blocks = data.consumeLongs(2); + if (blocks.length != 2) + return; + if (checkAccess(blocks[0], blocks[1])) { + throw new FuzzerSecurityIssueLow("Security breached"); + } + } + + private static native boolean checkAccess(long block1, long block2); +} diff --git a/tests/src/test/native/com/example/BUILD.bazel b/tests/src/test/native/com/example/BUILD.bazel new file mode 100644 index 00000000..cce29a07 --- /dev/null +++ b/tests/src/test/native/com/example/BUILD.bazel @@ -0,0 +1,15 @@ +load("@fmeum_rules_jni//jni:defs.bzl", "cc_jni_library") + +cc_jni_library( + name = "native_value_profile_fuzzer", + srcs = ["native_value_profile_fuzzer.cpp"], + copts = [ + "-fsanitize=fuzzer-no-link", + ], + linkopts = select({ + "//:clang_on_linux": ["-fuse-ld=lld"], + "//conditions:default": [], + }), + visibility = ["//tests:__pkg__"], + deps = ["//tests:native_value_profile_fuzzer.hdrs"], +) diff --git a/tests/src/test/native/com/example/native_value_profile_fuzzer.cpp b/tests/src/test/native/com/example/native_value_profile_fuzzer.cpp new file mode 100644 index 00000000..2edcc269 --- /dev/null +++ b/tests/src/test/native/com/example/native_value_profile_fuzzer.cpp @@ -0,0 +1,35 @@ +// Copyright 2022 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 +#include + +#include "com_example_NativeValueProfileFuzzer.h" + +// Prevent the compiler from inlining the secret all the way into checkAccess, +// which would make it trivial for the fuzzer to pass the checks. +volatile uint64_t secret = 0xefe4eb93215cb6b0L; + +static uint64_t insecureEncrypt(uint64_t input) { return input ^ secret; } + +jboolean Java_com_example_NativeValueProfileFuzzer_checkAccess(JNIEnv *, jclass, + jlong block1, + jlong block2) { + if (insecureEncrypt(block1) == 0x9fc48ee64d3dc090L) { + if (insecureEncrypt(block2) == 0x888a82ff483ad9c2L) { + return true; + } + } + return false; +} -- cgit v1.2.3 From 042308b75d51b6e69eff7c17edc9573f53df3ae9 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 16 Aug 2022 21:04:51 +0200 Subject: tests: Temporarily disable ASan tests on Windows Implementing the hook forwarding required for native library fuzzing on Windows requires quite a bit of effort - it seems reasonable to focus on the test runner integration first. --- examples/BUILD.bazel | 1 + tests/BUILD.bazel | 1 + 2 files changed, 2 insertions(+) diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index 8042cdd7..599b8261 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -47,6 +47,7 @@ java_fuzz_target_test( fuzzer_args = ["--jvm_args=-Djazzer.native_lib=native_asan"], sanitizer = "address", target_class = "com.example.ExampleFuzzerWithNative", + target_compatible_with = SKIP_ON_WINDOWS, verify_crash_reproducer = False, runtime_deps = [ ":example_fuzzer_with_native_lib", diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index cc0814e9..cbc77434 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -253,6 +253,7 @@ java_fuzz_target_test( fuzzer_args = ["-use_value_profile=1"], sanitizer = "address", target_class = "com.example.NativeValueProfileFuzzer", + target_compatible_with = SKIP_ON_WINDOWS, verify_crash_reproducer = False, runtime_deps = [":native_value_profile_fuzzer"], ) -- cgit v1.2.3 From b5aab47852542bc4ba55c61127f645bde69c569f Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 9 Aug 2022 11:30:08 +0200 Subject: all: Refactor Jazzer driver into a JNI shared library For the case of Java-only fuzzing, which only requires the JNI shared library and no driver binary, this change is a pure refactoring. Fuzzing native libraries requires some structural changes since loading libFuzzer from a shared library has implications on the behavior of the dynamic linker: 1. __sanitizer_set_death_callback now has to be looked up via dlsym since it isn't contained in the same object as libFuzzer itself anymore. 2. All sanitizer hooks and libc functions to hook have to be defined in the driver executable. They delegate to the real hooks defined in the shared library as soon as it has been loaded. --- .../jazzer/instrumentor/BUILD.bazel | 3 +- .../instrumentor/EdgeCoverageInstrumentation.java | 8 - .../code_intelligence/jazzer/runtime/BUILD.bazel | 2 +- .../jazzer/runtime/fuzzer_callbacks.cpp | 8 +- .../com/code_intelligence/jazzer/agent/Agent.kt | 14 - .../code_intelligence/jazzer/replay/Replayer.java | 1 + .../code_intelligence/jazzer/runtime/BUILD.bazel | 49 +- .../jazzer/runtime/CoverageMap.java | 5 + .../jazzer/runtime/FuzzedDataProviderImpl.java | 16 +- .../jazzer/runtime/SignalHandler.java | 3 +- .../runtime/TraceDataFlowNativeCallbacks.java | 5 +- .../code_intelligence/jazzer/runtime/BUILD.bazel | 22 +- .../jazzer/runtime/jazzer_fuzzer_callbacks.cpp | 193 ------ .../jazzer/runtime/signal_handler.cpp | 40 ++ .../java/com/code_intelligence/jazzer/BUILD.bazel | 12 - .../com/code_intelligence/jazzer/MockDriver.java | 23 - .../code_intelligence/jazzer/api/AutofuzzTest.java | 6 - .../com/code_intelligence/jazzer/api/BUILD.bazel | 2 +- .../code_intelligence/jazzer/runtime/BUILD.bazel | 5 +- .../jazzer/runtime/FuzzedDataProviderImplTest.java | 11 - .../jazzer/runtime/TraceCmpHooksTest.java | 6 - driver/BUILD.bazel | 252 ++----- driver/coverage_tracker.cpp | 116 ---- driver/coverage_tracker.h | 42 -- driver/fuzz_target_runner.cpp | 195 ------ driver/fuzz_target_runner.h | 37 - driver/fuzzed_data_provider.cpp | 748 --------------------- driver/fuzzed_data_provider.h | 41 -- driver/fuzzed_data_provider_test.cpp | 2 +- driver/jvm_tooling.cpp | 5 - driver/libfuzzer_callbacks.cpp | 128 ---- driver/native_fuzzer_hooks.c | 527 +++++++++++++++ driver/rtld_global_hack.cpp | 40 -- driver/sanitizer_hooks_with_pc.h | 45 -- driver/sanitizer_symbols.cpp | 29 - driver/sanitizer_symbols_for_tests.cpp | 51 -- driver/signal_handler.cpp | 40 -- .../code_intelligence/jazzer/driver/BUILD.bazel | 6 +- .../jazzer/driver/FuzzTargetRunner.java | 5 + .../code_intelligence/jazzer/driver/BUILD.bazel | 123 +++- .../jazzer/driver/coverage_tracker.cpp | 116 ++++ .../jazzer/driver/coverage_tracker.h | 42 ++ .../jazzer/driver/fuzz_target_runner.cpp | 199 ++++++ .../jazzer/driver/fuzz_target_runner.h | 37 + .../jazzer/driver/fuzzed_data_provider.cpp | 748 +++++++++++++++++++++ .../jazzer/driver/fuzzed_data_provider.h | 41 ++ .../jazzer/driver/jazzer_fuzzer_callbacks.cpp | 193 ++++++ .../jazzer/driver/libfuzzer_callbacks.cpp | 128 ++++ .../jazzer/driver/sanitizer_hooks_with_pc.h | 49 ++ .../jazzer/driver/sanitizer_symbols.cpp | 26 + .../jazzer/driver/trigger_driver_hooks_load.cpp | 50 ++ .../code_intelligence/jazzer/driver/BUILD.bazel | 3 - .../jazzer/driver/FuzzTargetRunnerTest.java | 7 - .../code_intelligence/jazzer/driver/BUILD.bazel | 11 - .../jazzer/driver/fuzz_target_runner_mock.cpp | 25 - format.sh | 2 +- tests/src/test/native/com/example/BUILD.bazel | 15 +- 57 files changed, 2453 insertions(+), 2105 deletions(-) delete mode 100644 agent/src/main/native/com/code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp create mode 100644 agent/src/main/native/com/code_intelligence/jazzer/runtime/signal_handler.cpp delete mode 100644 agent/src/test/java/com/code_intelligence/jazzer/BUILD.bazel delete mode 100644 agent/src/test/java/com/code_intelligence/jazzer/MockDriver.java delete mode 100644 driver/coverage_tracker.cpp delete mode 100644 driver/coverage_tracker.h delete mode 100644 driver/fuzz_target_runner.cpp delete mode 100644 driver/fuzz_target_runner.h delete mode 100644 driver/fuzzed_data_provider.cpp delete mode 100644 driver/fuzzed_data_provider.h delete mode 100644 driver/libfuzzer_callbacks.cpp create mode 100644 driver/native_fuzzer_hooks.c delete mode 100644 driver/rtld_global_hack.cpp delete mode 100644 driver/sanitizer_hooks_with_pc.h delete mode 100644 driver/sanitizer_symbols.cpp delete mode 100644 driver/sanitizer_symbols_for_tests.cpp delete mode 100644 driver/signal_handler.cpp create mode 100644 driver/src/main/native/com/code_intelligence/jazzer/driver/coverage_tracker.cpp create mode 100644 driver/src/main/native/com/code_intelligence/jazzer/driver/coverage_tracker.h create mode 100644 driver/src/main/native/com/code_intelligence/jazzer/driver/fuzz_target_runner.cpp create mode 100644 driver/src/main/native/com/code_intelligence/jazzer/driver/fuzz_target_runner.h create mode 100644 driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.cpp create mode 100644 driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.h create mode 100644 driver/src/main/native/com/code_intelligence/jazzer/driver/jazzer_fuzzer_callbacks.cpp create mode 100644 driver/src/main/native/com/code_intelligence/jazzer/driver/libfuzzer_callbacks.cpp create mode 100644 driver/src/main/native/com/code_intelligence/jazzer/driver/sanitizer_hooks_with_pc.h create mode 100644 driver/src/main/native/com/code_intelligence/jazzer/driver/sanitizer_symbols.cpp create mode 100644 driver/src/main/native/com/code_intelligence/jazzer/driver/trigger_driver_hooks_load.cpp delete mode 100644 driver/src/test/native/com/code_intelligence/jazzer/driver/BUILD.bazel delete mode 100644 driver/src/test/native/com/code_intelligence/jazzer/driver/fuzz_target_runner_mock.cpp diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel index ada825f1..fe68f903 100644 --- a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -91,12 +91,11 @@ java_jni_library( "EdgeCoverageInstrumentation.java", "EdgeCoverageTarget.java", ], - native_libs = ["//driver:coverage_tracker_jni"], + native_libs = ["//driver/src/main/native/com/code_intelligence/jazzer/driver:jazzer_driver"], plugins = ["//agent/src/jmh/java/com/code_intelligence/jazzer:JmhGeneratorAnnotationProcessor"], deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor", "//agent/src/main/java/com/code_intelligence/jazzer/runtime:coverage_map", - "//agent/src/test/java/com/code_intelligence/jazzer:MockDriver", "//agent/src/test/java/com/code_intelligence/jazzer/instrumentor:patch_test_utils", "@maven//:org_openjdk_jmh_jmh_core", ], diff --git a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentation.java b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentation.java index c2c2697d..e2eeadd3 100644 --- a/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentation.java +++ b/agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentation.java @@ -18,10 +18,7 @@ import static com.code_intelligence.jazzer.instrumentor.PatchTestUtils.*; import static java.lang.invoke.MethodHandles.lookup; import static java.lang.invoke.MethodType.methodType; -import com.code_intelligence.jazzer.MockDriver; import com.code_intelligence.jazzer.runtime.CoverageMap; -import com.github.fmeum.rules_jni.RulesJni; -import java.io.*; import java.lang.invoke.*; import java.nio.file.Files; import java.util.List; @@ -38,11 +35,6 @@ import org.openjdk.jmh.annotations.*; public class EdgeCoverageInstrumentation { private MethodHandle exampleMethod; - static { - MockDriver.load(); - RulesJni.loadLibrary("coverage_tracker_jni", "/driver"); - } - @Setup public void setupInstrumentation() throws Throwable { String outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR"); diff --git a/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/BUILD.bazel b/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/BUILD.bazel index 083979a1..33a03036 100644 --- a/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/BUILD.bazel +++ b/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/BUILD.bazel @@ -6,7 +6,7 @@ cc_jni_library( visibility = ["//agent/src/jmh/java/com/code_intelligence/jazzer/runtime:__pkg__"], deps = [ "//agent/src/jmh/java/com/code_intelligence/jazzer/runtime:fuzzer_callbacks.hdrs", - "//driver:sanitizer_hooks_with_pc", + "//driver/src/main/native/com/code_intelligence/jazzer/driver:sanitizer_hooks_with_pc", "@jazzer_libfuzzer//:libfuzzer_no_main", ], ) diff --git a/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/fuzzer_callbacks.cpp b/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/fuzzer_callbacks.cpp index 718a3924..2562db1f 100644 --- a/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/fuzzer_callbacks.cpp +++ b/agent/src/jmh/native/com/code_intelligence/jazzer/runtime/fuzzer_callbacks.cpp @@ -21,7 +21,7 @@ #include "com_code_intelligence_jazzer_runtime_FuzzerCallbacksOptimizedCritical.h" #include "com_code_intelligence_jazzer_runtime_FuzzerCallbacksOptimizedNonCritical.h" #include "com_code_intelligence_jazzer_runtime_FuzzerCallbacksWithPc.h" -#include "driver/sanitizer_hooks_with_pc.h" +#include "driver/src/main/native/com/code_intelligence/jazzer/driver/sanitizer_hooks_with_pc.h" extern "C" { void __sanitizer_weak_hook_compare_bytes(void *caller_pc, const void *s1, @@ -41,12 +41,6 @@ void __sanitizer_cov_trace_div4(uint32_t val); void __sanitizer_cov_trace_div8(uint64_t val); void __sanitizer_cov_trace_gep(uintptr_t idx); - -// Not called but required to link against libFuzzer. -[[maybe_unused]] int LLVMFuzzerTestOneInput(const uint8_t *data, - std::size_t size) { - return 0; -} } inline __attribute__((always_inline)) void *idToPc(jint id) { diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt index e4591dd6..f9b026f1 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt +++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt @@ -35,19 +35,6 @@ import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.exists import kotlin.io.path.isDirectory -// To be accessible by the agent classes the native library has to be loaded by the same class loader. -// premain is executed in the context of the system class loader. At the beginning of premain the agent jar is added to -// the bootstrap class loader and all subsequently required agent classes are loaded by it. Hence, it's not possible to -// load the native library directly in premain by the system class loader, instead it's delegated to NativeLibraryLoader -// loaded by the bootstrap class loader. -internal object NativeLibraryLoader { - fun load() { - // Calls JNI_OnLoad_jazzer_initialize in the driver, which ensures that dynamically - // linked JNI methods are resolved against it. - System.loadLibrary("jazzer_initialize") - } -} - private object AgentJarFinder { val agentJarFile = jarUriForClass(AgentJarFinder::class.java)?.let { JarFile(File(it)) } } @@ -67,7 +54,6 @@ fun premain(agentArgs: String?, instrumentation: Instrumentation) { } else { println("WARN: Failed to add agent JAR to bootstrap class loader search path") } - NativeLibraryLoader.load() val manifestCustomHookNames = ManifestUtils.combineManifestValues(ManifestUtils.HOOK_CLASSES).flatMap { 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 index ae509dad..80e372f3 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer.java @@ -29,6 +29,7 @@ public class Replayer { public static final int STATUS_OTHER_ERROR = 1; static { + System.setProperty("jazzer.is_replayer", "true"); try { RulesJni.loadLibrary( "fuzzed_data_provider_standalone", "/com/code_intelligence/jazzer/driver"); 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 b408e985..23c26ed9 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,6 +1,6 @@ -load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library", "jni_headers") +load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library") -java_library( +java_jni_library( name = "fuzzed_data_provider", srcs = [ "FuzzedDataProviderImpl.java", @@ -9,66 +9,43 @@ java_library( "//agent/src/main/java/com/code_intelligence/jazzer/replay:__pkg__", "//agent/src/test/java/com/code_intelligence/jazzer/runtime:__pkg__", "//driver/src/main/java/com/code_intelligence/jazzer/driver:__pkg__", + "//driver/src/main/native/com/code_intelligence/jazzer/driver:__pkg__", ], deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/api", ], ) -jni_headers( - name = "fuzzed_data_provider.hdrs", - lib = ":fuzzed_data_provider", - visibility = ["//driver:__pkg__"], -) - -java_library( +java_jni_library( name = "coverage_map", srcs = ["CoverageMap.java"], visibility = [ "//agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor:__pkg__", + "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:__pkg__", "//driver/src/main/java/com/code_intelligence/jazzer/driver:__pkg__", + "//driver/src/main/native/com/code_intelligence/jazzer/driver:__pkg__", "//driver/src/test:__subpackages__", - "//driver/testdata:__pkg__", ], deps = [ ":unsafe_provider", ], ) -jni_headers( - name = "coverage_map.hdrs", - lib = ":coverage_map", - visibility = ["//driver:__pkg__"], -) - -java_library( +java_jni_library( name = "signal_handler", srcs = ["SignalHandler.java"], - visibility = ["//driver/src/main/java/com/code_intelligence/jazzer/driver:__pkg__"], -) - -jni_headers( - name = "signal_handler.hdrs", - lib = ":signal_handler", - visibility = ["//driver:__pkg__"], + native_libs = ["//agent/src/main/native/com/code_intelligence/jazzer/runtime:jazzer_signal_handler"], + visibility = [ + "//agent/src/main/native/com/code_intelligence/jazzer/runtime:__pkg__", + "//driver/src/main/java/com/code_intelligence/jazzer/driver:__pkg__", + ], ) java_jni_library( name = "trace_data_flow_native_callbacks", srcs = ["TraceDataFlowNativeCallbacks.java"], - native_libs = select({ - # On Windows, the fuzzer callbacks are statically linked into the driver instead (see - # //driver:jazzer_driver). - "@platforms//os:windows": [], - "//conditions:default": [ - "//agent/src/main/native/com/code_intelligence/jazzer/runtime:jazzer_fuzzer_callbacks", - ], - }), visibility = [ - # For libFuzzer callbacks. - "//agent/src/main/native/com/code_intelligence/jazzer/runtime:__pkg__", - # For handleLibraryLoad. - "//driver:__pkg__", + "//driver/src/main/native/com/code_intelligence/jazzer/driver:__pkg__", ], deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/utils", diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java index fdc83791..4069d25a 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java @@ -14,6 +14,7 @@ package com.code_intelligence.jazzer.runtime; +import com.github.fmeum.rules_jni.RulesJni; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -24,6 +25,10 @@ import sun.misc.Unsafe; * the counters are shared directly with native code. */ final public class CoverageMap { + static { + RulesJni.loadLibrary("jazzer_driver", "/com/code_intelligence/jazzer/driver"); + } + private static final String ENV_MAX_NUM_COUNTERS = "JAZZER_MAX_NUM_COUNTERS"; private static final int MAX_NUM_COUNTERS = System.getenv(ENV_MAX_NUM_COUNTERS) != null 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 4b8ec7f2..567b02d3 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 @@ -15,21 +15,21 @@ package com.code_intelligence.jazzer.runtime; import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import com.github.fmeum.rules_jni.RulesJni; public class FuzzedDataProviderImpl implements FuzzedDataProvider { - public FuzzedDataProviderImpl() {} - - private static native void nativeInit(); - static { - try { - System.loadLibrary("jazzer_initialize"); - } catch (UnsatisfiedLinkError ignored) { - // Reached when loaded from the replayer. + // The replayer loads a standalone version of the FuzzedDataProvider. + if (System.getProperty("jazzer.is_replayer") == null) { + RulesJni.loadLibrary("jazzer_driver", "/com/code_intelligence/jazzer/driver"); } nativeInit(); } + public FuzzedDataProviderImpl() {} + + private static native void nativeInit(); + // Resets the FuzzedDataProvider state to read from the beginning to the end of the last fuzzer // input. public static native void reset(); diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java index fede3a8a..49ee80c8 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java @@ -14,11 +14,12 @@ package com.code_intelligence.jazzer.runtime; +import com.github.fmeum.rules_jni.RulesJni; import sun.misc.Signal; public final class SignalHandler { static { - System.loadLibrary("jazzer_initialize"); + RulesJni.loadLibrary("jazzer_signal_handler", SignalHandler.class); Signal.handle(new Signal("INT"), sig -> handleInterrupt()); } diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java index cf9b0c55..821ade0d 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java @@ -22,10 +22,7 @@ import java.nio.charset.Charset; @SuppressWarnings("unused") final public class TraceDataFlowNativeCallbacks { static { - // On Windows, we instead statically link the fuzzer callbacks into the driver. - if (!System.getProperty("os.name").startsWith("Windows")) { - RulesJni.loadLibrary("jazzer_fuzzer_callbacks", TraceDataFlowNativeCallbacks.class); - } + RulesJni.loadLibrary("jazzer_driver", "/com/code_intelligence/jazzer/driver"); } // Note that we are not encoding as modified UTF-8 here: The FuzzedDataProvider transparently diff --git a/agent/src/main/native/com/code_intelligence/jazzer/runtime/BUILD.bazel b/agent/src/main/native/com/code_intelligence/jazzer/runtime/BUILD.bazel index f18afe15..7d910474 100644 --- a/agent/src/main/native/com/code_intelligence/jazzer/runtime/BUILD.bazel +++ b/agent/src/main/native/com/code_intelligence/jazzer/runtime/BUILD.bazel @@ -1,24 +1,8 @@ load("@fmeum_rules_jni//jni:defs.bzl", "cc_jni_library") cc_jni_library( - name = "jazzer_fuzzer_callbacks", - # Not needed (and broken) on Windows. - tags = ["manual"], + name = "jazzer_signal_handler", + srcs = ["signal_handler.cpp"], visibility = ["//agent/src/main/java/com/code_intelligence/jazzer/runtime:__pkg__"], - deps = [ - ":jazzer_fuzzer_callbacks_for_static_linking", - "//agent/src/main/java/com/code_intelligence/jazzer/runtime:trace_data_flow_native_callbacks.hdrs", - "//driver:sanitizer_hooks_with_pc", - ], -) - -cc_library( - name = "jazzer_fuzzer_callbacks_for_static_linking", - srcs = ["jazzer_fuzzer_callbacks.cpp"], - visibility = ["//driver:__pkg__"], - deps = [ - "//agent/src/main/java/com/code_intelligence/jazzer/runtime:trace_data_flow_native_callbacks.hdrs", - "//driver:sanitizer_hooks_with_pc", - ], - alwayslink = True, + deps = ["//agent/src/main/java/com/code_intelligence/jazzer/runtime:signal_handler.hdrs"], ) diff --git a/agent/src/main/native/com/code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp b/agent/src/main/native/com/code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp deleted file mode 100644 index a4b0214a..00000000 --- a/agent/src/main/native/com/code_intelligence/jazzer/runtime/jazzer_fuzzer_callbacks.cpp +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2022 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 - -#include -#include - -#include "com_code_intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks.h" -#include "driver/sanitizer_hooks_with_pc.h" - -namespace { - -extern "C" { -void __sanitizer_weak_hook_compare_bytes(void *caller_pc, const void *s1, - const void *s2, std::size_t n1, - std::size_t n2, int result); -void __sanitizer_weak_hook_memmem(void *called_pc, const void *s1, size_t len1, - const void *s2, size_t len2, void *result); -void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2); -void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2); - -void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases); - -void __sanitizer_cov_trace_div4(uint32_t val); -void __sanitizer_cov_trace_div8(uint64_t val); - -void __sanitizer_cov_trace_gep(uintptr_t idx); -} - -inline __attribute__((always_inline)) void *idToPc(jint id) { - return reinterpret_cast(static_cast(id)); -} -} // namespace - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceStrstr0( - JNIEnv *env, jclass cls, jbyteArray needle, jint id) { - jint needle_length = env->GetArrayLength(needle); - auto *needle_native = - static_cast(env->GetPrimitiveArrayCritical(needle, nullptr)); - __sanitizer_weak_hook_memmem(idToPc(id), nullptr, 0, needle_native, - needle_length, nullptr); - env->ReleasePrimitiveArrayCritical(needle, needle_native, JNI_ABORT); -} - -extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceStrstr0( - jint needle_length, jbyte *needle_native, jint id) { - __sanitizer_weak_hook_memmem(idToPc(id), nullptr, 0, needle_native, - needle_length, nullptr); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceMemcmp( - JNIEnv *env, jclass cls, jbyteArray b1, jbyteArray b2, jint result, - jint id) { - jint b1_length = env->GetArrayLength(b1); - jint b2_length = env->GetArrayLength(b2); - auto *b1_native = - static_cast(env->GetPrimitiveArrayCritical(b1, nullptr)); - auto *b2_native = - static_cast(env->GetPrimitiveArrayCritical(b2, nullptr)); - __sanitizer_weak_hook_compare_bytes(idToPc(id), b1_native, b2_native, - b1_length, b2_length, result); - env->ReleasePrimitiveArrayCritical(b1, b1_native, JNI_ABORT); - env->ReleasePrimitiveArrayCritical(b2, b2_native, JNI_ABORT); -} - -extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceMemcmp( - jint b1_length, jbyte *b1, jint b2_length, jbyte *b2, jint result, - jint id) { - __sanitizer_weak_hook_compare_bytes(idToPc(id), b1, b2, b1_length, b2_length, - result); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpLong( - JNIEnv *env, jclass cls, jlong value1, jlong value2, jint id) { - __sanitizer_cov_trace_cmp8_with_pc(idToPc(id), value1, value2); -} - -extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpLong( - jlong value1, jlong value2, jint id) { - __sanitizer_cov_trace_cmp8_with_pc(idToPc(id), value1, value2); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpInt( - JNIEnv *env, jclass cls, jint value1, jint value2, jint id) { - __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); -} - -extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpInt( - jint value1, jint value2, jint id) { - __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceConstCmpInt( - JNIEnv *env, jclass cls, jint value1, jint value2, jint id) { - __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); -} - -extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceConstCmpInt( - jint value1, jint value2, jint id) { - __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitch( - JNIEnv *env, jclass cls, jlong switch_value, - jlongArray libfuzzer_case_values, jint id) { - auto *case_values = static_cast( - env->GetPrimitiveArrayCritical(libfuzzer_case_values, nullptr)); - __sanitizer_cov_trace_switch_with_pc( - idToPc(id), switch_value, reinterpret_cast(case_values)); - env->ReleasePrimitiveArrayCritical(libfuzzer_case_values, case_values, - JNI_ABORT); -} - -extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitch( - jlong switch_value, jint libfuzzer_case_values_length, jlong *case_values, - jint id) { - __sanitizer_cov_trace_switch_with_pc( - idToPc(id), switch_value, reinterpret_cast(case_values)); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivLong( - JNIEnv *env, jclass cls, jlong value, jint id) { - __sanitizer_cov_trace_div8_with_pc(idToPc(id), value); -} - -extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivLong( - jlong value, jint id) { - __sanitizer_cov_trace_div8_with_pc(idToPc(id), value); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivInt( - JNIEnv *env, jclass cls, jint value, jint id) { - __sanitizer_cov_trace_div4_with_pc(idToPc(id), value); -} - -extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivInt( - jint value, jint id) { - __sanitizer_cov_trace_div4_with_pc(idToPc(id), value); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceGep( - JNIEnv *env, jclass cls, jlong idx, jint id) { - __sanitizer_cov_trace_gep_with_pc(idToPc(id), static_cast(idx)); -} - -extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceGep( - jlong idx, jint id) { - __sanitizer_cov_trace_gep_with_pc(idToPc(id), static_cast(idx)); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_tracePcIndir( - JNIEnv *env, jclass cls, jint caller_id, jint callee_id) { - __sanitizer_cov_trace_pc_indir_with_pc(idToPc(caller_id), - static_cast(callee_id)); -} - -extern "C" [[maybe_unused]] JNIEXPORT void JNICALL -JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_tracePcIndir( - jint caller_id, jint callee_id) { - __sanitizer_cov_trace_pc_indir_with_pc(idToPc(caller_id), - static_cast(callee_id)); -} diff --git a/agent/src/main/native/com/code_intelligence/jazzer/runtime/signal_handler.cpp b/agent/src/main/native/com/code_intelligence/jazzer/runtime/signal_handler.cpp new file mode 100644 index 00000000..2600a53a --- /dev/null +++ b/agent/src/main/native/com/code_intelligence/jazzer/runtime/signal_handler.cpp @@ -0,0 +1,40 @@ +// 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 + +#include +#include + +#include "com_code_intelligence_jazzer_runtime_SignalHandler.h" + +#ifdef _WIN32 +// Windows does not have SIGUSR1, which triggers a graceful exit of libFuzzer. +// Instead, trigger a hard exit. +#define SIGUSR1 SIGTERM +#endif + +// Handles SIGINT raised while running Java code. +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_SignalHandler_handleInterrupt( + JNIEnv *, jclass) { + static std::atomic already_exiting{false}; + if (!already_exiting.exchange(true)) { + // Let libFuzzer exit gracefully when the JVM received SIGINT. + raise(SIGUSR1); + } else { + // Exit libFuzzer forcefully on repeated SIGINTs. + raise(SIGTERM); + } +} diff --git a/agent/src/test/java/com/code_intelligence/jazzer/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/BUILD.bazel deleted file mode 100644 index 52ddf679..00000000 --- a/agent/src/test/java/com/code_intelligence/jazzer/BUILD.bazel +++ /dev/null @@ -1,12 +0,0 @@ -load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library") - -java_jni_library( - name = "MockDriver", - srcs = ["MockDriver.java"], - native_libs = ["//driver:mock_driver"], - visibility = [ - "//agent/src/jmh/java/com/code_intelligence/jazzer:__subpackages__", - "//agent/src/test/java/com/code_intelligence/jazzer:__subpackages__", - "//driver/src/test:__subpackages__", - ], -) diff --git a/agent/src/test/java/com/code_intelligence/jazzer/MockDriver.java b/agent/src/test/java/com/code_intelligence/jazzer/MockDriver.java deleted file mode 100644 index 82590b7d..00000000 --- a/agent/src/test/java/com/code_intelligence/jazzer/MockDriver.java +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2022 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; - -import com.github.fmeum.rules_jni.RulesJni; - -public final class MockDriver { - public static void load() { - RulesJni.loadLibrary("mock_driver", "/driver"); - } -} diff --git a/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java b/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java index ea6025b6..59ef238d 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java @@ -19,7 +19,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; -import com.code_intelligence.jazzer.MockDriver; import java.util.Arrays; import java.util.Collections; import org.junit.BeforeClass; @@ -46,11 +45,6 @@ public class AutofuzzTest { } } - @BeforeClass - public static void loadMockDriver() { - MockDriver.load(); - } - @Test public void testConsume() { FuzzedDataProvider data = CannedFuzzedDataProvider.create( diff --git a/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel index 52e5e661..f2537b73 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel +++ b/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel @@ -16,7 +16,7 @@ java_test( ], deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/api", - "//agent/src/test/java/com/code_intelligence/jazzer:MockDriver", + "//driver/src/main/native/com/code_intelligence/jazzer/driver:jazzer_driver", "@maven//:junit_junit", ], ) diff --git a/agent/src/test/java/com/code_intelligence/jazzer/runtime/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/runtime/BUILD.bazel index 4fa0df37..97ac4f62 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/runtime/BUILD.bazel +++ b/agent/src/test/java/com/code_intelligence/jazzer/runtime/BUILD.bazel @@ -7,8 +7,7 @@ java_test( deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/api", "//agent/src/main/java/com/code_intelligence/jazzer/runtime:fuzzed_data_provider", - "//driver/src/main/native/com/code_intelligence/jazzer/driver:fuzzed_data_provider_standalone", - "@fmeum_rules_jni//jni/tools/native_loader", + "//driver/src/main/native/com/code_intelligence/jazzer/driver:jazzer_driver", ], ) @@ -33,7 +32,7 @@ java_test( target_compatible_with = SKIP_ON_WINDOWS, deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/runtime", - "//agent/src/test/java/com/code_intelligence/jazzer:MockDriver", + "//driver/src/main/native/com/code_intelligence/jazzer/driver:jazzer_driver", "@maven//:junit_junit", ], ) diff --git a/agent/src/test/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImplTest.java b/agent/src/test/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImplTest.java index 53f28d8b..31099066 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImplTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImplTest.java @@ -15,21 +15,10 @@ package com.code_intelligence.jazzer.runtime; import com.code_intelligence.jazzer.api.FuzzedDataProvider; -import com.github.fmeum.rules_jni.RulesJni; import java.util.Arrays; import java.util.stream.Collectors; public class FuzzedDataProviderImplTest { - static { - try { - RulesJni.loadLibrary( - "fuzzed_data_provider_standalone", "/com/code_intelligence/jazzer/driver"); - } catch (Throwable t) { - t.printStackTrace(); - System.exit(1); - } - } - public static void main(String[] args) { FuzzedDataProviderImpl fuzzedDataProvider = new FuzzedDataProviderImpl(); FuzzedDataProviderImpl.feed(INPUT_BYTES); diff --git a/agent/src/test/java/com/code_intelligence/jazzer/runtime/TraceCmpHooksTest.java b/agent/src/test/java/com/code_intelligence/jazzer/runtime/TraceCmpHooksTest.java index 0e838386..9275ca30 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/runtime/TraceCmpHooksTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/runtime/TraceCmpHooksTest.java @@ -16,7 +16,6 @@ package com.code_intelligence.jazzer.runtime; -import com.code_intelligence.jazzer.MockDriver; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; @@ -29,11 +28,6 @@ import org.junit.Test; public class TraceCmpHooksTest { private static final ExecutorService ES = Executors.newFixedThreadPool(5); - @BeforeClass - public static void setup() { - MockDriver.load(); - } - @Test public void cmpHookShouldHandleConcurrentModifications() throws InterruptedException { String arg = "test"; diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index 6de3bf75..7c4f0964 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -1,114 +1,10 @@ load("@fmeum_rules_jni//jni:defs.bzl", "cc_jni_library") load("//bazel:cc.bzl", "cc_17_library") - -cc_library( - name = "sanitizer_hooks_with_pc", - hdrs = ["sanitizer_hooks_with_pc.h"], - visibility = [ - "//agent/src/jmh/native/com/code_intelligence/jazzer/runtime:__pkg__", - "//agent/src/main/native/com/code_intelligence/jazzer/runtime:__pkg__", - "//driver/src/main/native/com/code_intelligence/jazzer/driver:__pkg__", - ], -) - -cc_library( - name = "fuzzed_data_provider", - srcs = [ - "fuzzed_data_provider.cpp", - ], - hdrs = [ - "fuzzed_data_provider.h", - ], - visibility = [ - "//driver/src/main/native/com/code_intelligence/jazzer/driver:__pkg__", - "//driver/src/test:__subpackages__", - ], - deps = [ - "//agent/src/main/java/com/code_intelligence/jazzer/runtime:fuzzed_data_provider.hdrs", - "@com_google_absl//absl/strings:str_format", - "@fmeum_rules_jni//jni", - ], - # Symbols may only be referenced dynamically via JNI. - alwayslink = True, -) - -cc_library( - name = "signal_handler", - srcs = ["signal_handler.cpp"], - linkstatic = True, - visibility = [ - "//driver/src/main/native/com/code_intelligence/jazzer/driver:__pkg__", - ], - deps = [ - "//agent/src/main/java/com/code_intelligence/jazzer/runtime:signal_handler.hdrs", - "@fmeum_rules_jni//jni", - ], - # Symbols are only referenced dynamically via JNI. - alwayslink = True, -) - -cc_jni_library( - name = "coverage_tracker_jni", - visibility = ["//agent/src/jmh/java/com/code_intelligence/jazzer/instrumentor:__pkg__"], - deps = [ - ":coverage_tracker", - ":sanitizer_symbols_for_tests", - ], -) - -cc_library( - name = "coverage_tracker", - srcs = ["coverage_tracker.cpp"], - hdrs = ["coverage_tracker.h"], - linkstatic = True, - visibility = [ - "//driver/src/main/native/com/code_intelligence/jazzer/driver:__pkg__", - "//driver/src/test:__subpackages__", - ], - deps = [ - "//agent/src/main/java/com/code_intelligence/jazzer/runtime:coverage_map.hdrs", - ], - # Symbols are only referenced dynamically via JNI. - alwayslink = True, -) - -cc_library( - name = "libfuzzer_callbacks", - srcs = ["libfuzzer_callbacks.cpp"], - linkstatic = True, - visibility = [ - "//driver/src/main/native/com/code_intelligence/jazzer/driver:__pkg__", - ], - deps = [ - "//agent/src/main/java/com/code_intelligence/jazzer/runtime:trace_data_flow_native_callbacks.hdrs", - "@com_google_absl//absl/strings", - "@fmeum_rules_jni//jni", - ] + select({ - # We statically link the fuzzer callbacks on Windows since linking a shared library against - # symbols exported by a binary is difficult: We would need a .def file to export the symbols - # and create an interface library from the binary that is then linked into the shared - # library. This creates a conceptual (and real) cyclic dependency between the binary and the - # shared library since the binary data-depends on the agent and thus on the shared library. - # Since we only have to link the fuzzer callbacks dynamically to work around a JDK bug in an - # obsoleted feature (critical JNI natives) that is only meant to improve performance, we - # don't worry about this too much. - "@platforms//os:windows": [ - "//agent/src/main/native/com/code_intelligence/jazzer/runtime:jazzer_fuzzer_callbacks_for_static_linking", - ], - # On other platforms, dynamic linking is easy, so we load the fuzzer callbacks from a shared - # library at runtime. This is needed to let the JVM's JavaCritical_* lookup succeed, which - # does not correctly load statically linked symbols. - # See //agent/src/main/native/com/code_intelligence/jazzer/runtime:jazzer_fuzzer_callbacks - # for the place this is linked into the agent instead. - "//conditions:default": [], - }), - # Symbols are only referenced dynamically via JNI. - alwayslink = True, -) +load("//bazel:compat.bzl", "SKIP_ON_WINDOWS") cc_library( name = "jazzer_main", - srcs = [":jazzer_main.cpp"], + srcs = ["jazzer_main.cpp"], deps = [ ":jvm_tooling_lib", "@com_google_absl//absl/strings", @@ -118,26 +14,10 @@ cc_library( ], ) -cc_library( - name = "fuzz_target_runner", - srcs = ["fuzz_target_runner.cpp"], - hdrs = ["fuzz_target_runner.h"], - deps = [ - ":fuzzed_data_provider", - "//driver/src/main/java/com/code_intelligence/jazzer/driver:fuzz_target_runner.hdrs", - "@fmeum_rules_jni//jni", - ], - # With sanitizers, symbols are only referenced dynamically via JNI. - alwayslink = True, -) - cc_library( name = "jvm_tooling_lib", srcs = ["jvm_tooling.cpp"], hdrs = ["jvm_tooling.h"], - # Needs to be linked statically for JNI_OnLoad_jazzer_initialize to be found - # by the JVM. - linkstatic = True, tags = [ # Should be built through the cc_17_library driver_lib. "manual", @@ -152,46 +32,81 @@ cc_library( ], ) +DYNAMIC_SYMBOLS_TO_EXPORT = [ + "__sanitizer_cov_8bit_counters_init", + "__sanitizer_cov_pcs_init", + "__sanitizer_cov_trace_cmp1", + "__sanitizer_cov_trace_cmp4", + "__sanitizer_cov_trace_cmp4", + "__sanitizer_cov_trace_cmp8", + "__sanitizer_cov_trace_const_cmp1", + "__sanitizer_cov_trace_const_cmp4", + "__sanitizer_cov_trace_const_cmp4", + "__sanitizer_cov_trace_const_cmp8", + "__sanitizer_cov_trace_div4", + "__sanitizer_cov_trace_div8", + "__sanitizer_cov_trace_gep", + "__sanitizer_cov_trace_pc_indir", + "__sanitizer_cov_trace_switch", + "__sanitizer_weak_hook_memcmp", + "__sanitizer_weak_hook_memmem", + "__sanitizer_weak_hook_strcasecmp", + "__sanitizer_weak_hook_strcasestr", + "__sanitizer_weak_hook_strcmp", + "__sanitizer_weak_hook_strncasecmp", + "__sanitizer_weak_hook_strncmp", + "__sanitizer_weak_hook_strstr", + "bcmp", + "jazzer_initialize_native_hooks", + "memcmp", + "memmem", + "strcasecmp", + "strcasestr", + "strcmp", + "strncasecmp", + "strncmp", + "strstr", +] + +cc_library( + name = "native_fuzzer_hooks", + srcs = ["native_fuzzer_hooks.c"], + linkopts = select({ + "@platforms//os:linux": [ + "-Wl,--export-dynamic-symbol=" + symbol + for symbol in DYNAMIC_SYMBOLS_TO_EXPORT + ] + [ + "-ldl", + ], + "@platforms//os:macos": [ + "-rdynamic", + "-ldl", + ], + "//conditions:default": [], + }), + target_compatible_with = SKIP_ON_WINDOWS, + deps = ["//driver/src/main/native/com/code_intelligence/jazzer/driver:sanitizer_hooks_with_pc"], + alwayslink = True, +) + cc_17_library( - name = "driver_lib", - linkstatic = True, - deps = [ - # This includes an explicit list of all cc_library targets providing - # symbols for JNI dynamic linking. - ":coverage_tracker", - ":fuzz_target_runner", - ":fuzzed_data_provider", - ":jazzer_main", - ":libfuzzer_callbacks", - ":signal_handler", - "@jazzer_libfuzzer//:libfuzzer_no_main", - ], + name = "jazzer_main_lib", + deps = [":jazzer_main"], alwayslink = True, ) cc_binary( name = "jazzer_driver", - srcs = [ - # Defines symbols otherwise defined by sanitizers to prevent linker - # errors and print JVM stack traces. - # Windows-compatible replacement for __attribute__((weak)). - "sanitizer_symbols.cpp", - ], data = [ "//agent:jazzer_agent_deploy", ], linkopts = select({ - "@platforms//os:windows": [], - "//conditions:default": [ - "-rdynamic", - ], - }) + select({ "//:clang_on_linux": ["-fuse-ld=lld"], "//conditions:default": [], }), linkstatic = True, visibility = ["//visibility:public"], - deps = [":driver_lib"], + deps = [":jazzer_main_lib"], ) alias( @@ -209,8 +124,7 @@ cc_binary( data = [ "//agent:jazzer_agent_deploy", ], - linkopts = [ - ] + select({ + linkopts = select({ "@platforms//os:windows": [ # Sanitizer runtimes have to be linked manually on Windows: # https://devblogs.microsoft.com/cppblog/addresssanitizer-asan-for-windows-with-msvc/ @@ -220,7 +134,6 @@ cc_binary( "//conditions:default": [ "-fsanitize=address", "-static-libsan", - "-rdynamic", ], }) + select({ "//:clang_on_linux": ["-fuse-ld=lld"], @@ -228,11 +141,14 @@ cc_binary( }), linkstatic = True, visibility = ["//visibility:public"], - deps = [":driver_lib"] + select({ + deps = [":jazzer_main_lib"] + select({ # There is no static ASan runtime on macOS, so link to the dynamic # runtime library if on macOS and using the toolchain. ":using_toolchain_on_osx": ["@llvm_toolchain_llvm//:macos_asan_dynamic"], "//conditions:default": [], + }) + select({ + "@platforms//os:windows": [], + "//conditions:default": [":native_fuzzer_hooks"], }), ) @@ -241,8 +157,7 @@ cc_binary( data = [ "//agent:jazzer_agent_deploy", ], - linkopts = [ - ] + select({ + linkopts = select({ "@platforms//os:windows": [ # Sanitizer runtimes have to be linked manually on Windows: # https://devblogs.microsoft.com/cppblog/addresssanitizer-asan-for-windows-with-msvc/ @@ -254,7 +169,6 @@ cc_binary( # Link UBSan statically, even on macOS. "-static-libsan", "-fsanitize-link-c++-runtime", - "-rdynamic", ], }) + select({ "//:clang_on_linux": ["-fuse-ld=lld"], @@ -262,30 +176,12 @@ cc_binary( }), linkstatic = True, visibility = ["//visibility:public"], - deps = [":driver_lib"], -) - -cc_library( - name = "sanitizer_symbols_for_tests", - srcs = ["sanitizer_symbols_for_tests.cpp"], - visibility = ["//driver/src/test:__subpackages__"], - alwayslink = True, -) - -# This JNI library can be loaded by Java-only tests to provide mock definitions -# of the symbols exported by the real jazzer_driver. -cc_jni_library( - name = "mock_driver", - srcs = select({ + deps = [ + ":jazzer_main_lib", + ] + select({ "@platforms//os:windows": [], - "//conditions:default": ["rtld_global_hack.cpp"], + "//conditions:default": [":native_fuzzer_hooks"], }), - visibility = ["//agent/src/test/java:__subpackages__"], - deps = [ - ":coverage_tracker", - ":fuzzed_data_provider", - ":sanitizer_symbols_for_tests", - ], ) cc_test( @@ -302,7 +198,6 @@ cc_test( includes = ["."], deps = [ ":jvm_tooling_lib", - ":sanitizer_symbols_for_tests", ":test_main", "@bazel_tools//tools/cpp/runfiles", "@googletest//:gtest", @@ -323,10 +218,9 @@ cc_test( ], includes = ["."], deps = [ - ":fuzzed_data_provider", ":jvm_tooling_lib", - ":sanitizer_symbols_for_tests", ":test_main", + "//driver/src/main/native/com/code_intelligence/jazzer/driver:fuzzed_data_provider", "@bazel_tools//tools/cpp/runfiles", "@googletest//:gtest", "@jazzer_com_github_gflags_gflags//:gflags", diff --git a/driver/coverage_tracker.cpp b/driver/coverage_tracker.cpp deleted file mode 100644 index 47872fa9..00000000 --- a/driver/coverage_tracker.cpp +++ /dev/null @@ -1,116 +0,0 @@ -// 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 "coverage_tracker.h" - -#include - -#include -#include -#include -#include - -#include "com_code_intelligence_jazzer_runtime_CoverageMap.h" - -extern "C" void __sanitizer_cov_8bit_counters_init(uint8_t *start, - uint8_t *end); -extern "C" void __sanitizer_cov_pcs_init(const uintptr_t *pcs_beg, - const uintptr_t *pcs_end); -extern "C" size_t __sanitizer_cov_get_observed_pcs(uintptr_t **pc_entries); - -namespace { -void AssertNoException(JNIEnv &env) { - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - throw std::runtime_error( - "Java exception occurred in CoverageTracker JNI code"); - } -} -} // namespace - -namespace jazzer { - -uint8_t *CoverageTracker::counters_ = nullptr; -PCTableEntry *CoverageTracker::pc_entries_ = nullptr; - -void CoverageTracker::Initialize(JNIEnv &env, jlong counters) { - if (counters_ != nullptr) { - throw std::runtime_error( - "CoverageTracker::Initialize must not be called more than once"); - } - counters_ = reinterpret_cast(static_cast(counters)); -} - -void CoverageTracker::RegisterNewCounters(JNIEnv &env, jint old_num_counters, - jint new_num_counters) { - if (counters_ == nullptr) { - throw std::runtime_error( - "CoverageTracker::Initialize should have been called first"); - } - if (new_num_counters < old_num_counters) { - throw std::runtime_error( - "new_num_counters must not be smaller than old_num_counters"); - } - if (new_num_counters == old_num_counters) { - return; - } - std::size_t diff_num_counters = new_num_counters - old_num_counters; - // libFuzzer requires an array containing the instruction addresses associated - // with the coverage counters registered above. This is required to report how - // many edges have been covered. However, libFuzzer only checks these - // addresses when the corresponding flag is set to 1. Therefore, it is safe to - // set the all PC entries to any value as long as the corresponding flag is - // set to zero. We set the value of each PC to the index of the corresponding - // edge ID. This facilitates finding the edge ID of each covered PC reported - // by libFuzzer. - pc_entries_ = new PCTableEntry[diff_num_counters]; - for (std::size_t i = 0; i < diff_num_counters; ++i) { - pc_entries_[i] = {i, 0}; - } - __sanitizer_cov_8bit_counters_init(counters_ + old_num_counters, - counters_ + new_num_counters); - __sanitizer_cov_pcs_init((uintptr_t *)(pc_entries_), - (uintptr_t *)(pc_entries_ + diff_num_counters)); -} -} // namespace jazzer - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_CoverageMap_initialize( - JNIEnv *env, jclass cls, jlong counters) { - ::jazzer::CoverageTracker::Initialize(*env, counters); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_CoverageMap_registerNewCounters( - JNIEnv *env, jclass cls, jint old_num_counters, jint new_num_counters) { - ::jazzer::CoverageTracker::RegisterNewCounters(*env, old_num_counters, - new_num_counters); -} - -[[maybe_unused]] jintArray -Java_com_code_1intelligence_jazzer_runtime_CoverageMap_getEverCoveredIds( - JNIEnv *env, jclass) { - uintptr_t *covered_pcs; - jint num_covered_pcs = __sanitizer_cov_get_observed_pcs(&covered_pcs); - std::vector covered_edge_ids(covered_pcs, - covered_pcs + num_covered_pcs); - delete[] covered_pcs; - - jintArray covered_edge_ids_jni = env->NewIntArray(num_covered_pcs); - AssertNoException(*env); - env->SetIntArrayRegion(covered_edge_ids_jni, 0, num_covered_pcs, - covered_edge_ids.data()); - AssertNoException(*env); - return covered_edge_ids_jni; -} diff --git a/driver/coverage_tracker.h b/driver/coverage_tracker.h deleted file mode 100644 index 8cceceed..00000000 --- a/driver/coverage_tracker.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include - -#include - -namespace jazzer { - -// The members of this struct are only accessed by libFuzzer. -struct __attribute__((packed)) PCTableEntry { - [[maybe_unused]] uintptr_t PC, PCFlags; -}; - -// CoverageTracker registers an array of 8-bit coverage counters with -// libFuzzer. The array is populated from Java using Unsafe. -class CoverageTracker { - private: - static uint8_t *counters_; - static PCTableEntry *pc_entries_; - - public: - static void Initialize(JNIEnv &env, jlong counters); - static void RegisterNewCounters(JNIEnv &env, jint old_num_counters, - jint new_num_counters); -}; -} // namespace jazzer diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp deleted file mode 100644 index 0edafd9b..00000000 --- a/driver/fuzz_target_runner.cpp +++ /dev/null @@ -1,195 +0,0 @@ -// 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. - -/** - * A native wrapper around the FuzzTargetRunner Java class that executes it as a - * libFuzzer fuzz target. - */ - -#include "fuzz_target_runner.h" - -#include - -#include -#include -#include - -#include "com_code_intelligence_jazzer_driver_FuzzTargetRunner.h" -#include "driver/fuzzed_data_provider.h" - -extern "C" int LLVMFuzzerRunDriver(int *argc, char ***argv, - int (*UserCb)(const uint8_t *Data, - size_t Size)); - -namespace { -bool gUseFuzzedDataProvider; -jclass gRunner; -jmethodID gRunOneId; -JNIEnv *gEnv; - -// A libFuzzer-registered callback that outputs the crashing input, but does -// not include a stack trace. -void (*gLibfuzzerPrintCrashingInput)() = nullptr; - -int testOneInput(const uint8_t *data, const std::size_t size) { - JNIEnv &env = *gEnv; - jint jsize = - std::min(size, static_cast(std::numeric_limits::max())); - int res; - if (gUseFuzzedDataProvider) { - ::jazzer::FeedFuzzedDataProvider(data, size); - res = env.CallStaticIntMethod(gRunner, gRunOneId, nullptr); - } else { - jbyteArray input = env.NewByteArray(jsize); - env.SetByteArrayRegion(input, 0, jsize, - reinterpret_cast(data)); - res = env.CallStaticIntMethod(gRunner, gRunOneId, input); - env.DeleteLocalRef(input); - } - if (env.ExceptionCheck()) { - env.ExceptionDescribe(); - _Exit(1); - } - return res; -} -} // namespace - -namespace jazzer { -void DumpJvmStackTraces() { - JavaVM *vm; - jsize num_vms; - JNI_GetCreatedJavaVMs(&vm, 1, &num_vms); - if (num_vms != 1) { - return; - } - JNIEnv *env = nullptr; - if (vm->AttachCurrentThread(reinterpret_cast(&env), nullptr) != - JNI_OK) { - std::cerr << "WARN: AttachCurrentThread failed in DumpJvmStackTraces" - << std::endl; - return; - } - jmethodID dumpStack = - env->GetStaticMethodID(gRunner, "dumpAllStackTraces", "()V"); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - return; - } - env->CallStaticVoidMethod(gRunner, dumpStack); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - return; - } - // Do not detach as we may be the main thread (but the JVM exits anyway). -} -} // namespace jazzer - -[[maybe_unused]] jint -Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner_startLibFuzzer( - JNIEnv *env, jclass runner, jobjectArray args) { - gEnv = env; - gRunner = reinterpret_cast(env->NewGlobalRef(runner)); - gRunOneId = env->GetStaticMethodID(runner, "runOne", "([B)I"); - if (gRunOneId == nullptr) { - env->ExceptionDescribe(); - _Exit(1); - } - jfieldID use_fuzzed_data_provider_id = - env->GetStaticFieldID(runner, "useFuzzedDataProvider", "Z"); - if (use_fuzzed_data_provider_id == nullptr) { - env->ExceptionDescribe(); - _Exit(1); - } - gUseFuzzedDataProvider = - env->GetStaticBooleanField(runner, use_fuzzed_data_provider_id); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - _Exit(1); - } - - int argc = env->GetArrayLength(args); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - _Exit(1); - } - std::vector argv_strings; - std::vector argv_c; - for (jsize i = 0; i < argc; i++) { - auto arg_jni = - reinterpret_cast(env->GetObjectArrayElement(args, i)); - if (arg_jni == nullptr) { - env->ExceptionDescribe(); - _Exit(1); - } - jbyte *arg_c = env->GetByteArrayElements(arg_jni, nullptr); - if (arg_c == nullptr) { - env->ExceptionDescribe(); - _Exit(1); - } - std::size_t arg_size = env->GetArrayLength(arg_jni); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - _Exit(1); - } - argv_strings.emplace_back(reinterpret_cast(arg_c), arg_size); - env->ReleaseByteArrayElements(arg_jni, arg_c, JNI_ABORT); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - _Exit(1); - } - } - for (jsize i = 0; i < argc; i++) { - argv_c.emplace_back(argv_strings[i].c_str()); - } - // Null-terminate argv. - argv_c.emplace_back(nullptr); - - const char **argv = argv_c.data(); - return LLVMFuzzerRunDriver(&argc, const_cast(&argv), testOneInput); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner_printCrashingInput( - JNIEnv *, jclass) { - gLibfuzzerPrintCrashingInput(); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner__1Exit( - JNIEnv *, jclass, jint exit_code) { - _Exit(exit_code); -} - -// This symbol is defined by sanitizers if linked into Jazzer or in -// sanitizer_symbols.cpp if no sanitizer is used. -extern "C" void __sanitizer_set_death_callback(void (*)()); - -// We apply a patch to libFuzzer to make it call this function instead of -// __sanitizer_set_death_callback to pass us the death callback. -extern "C" [[maybe_unused]] void __jazzer_set_death_callback( - void (*callback)()) { - gLibfuzzerPrintCrashingInput = callback; - __sanitizer_set_death_callback([]() { - ::jazzer::DumpJvmStackTraces(); - gLibfuzzerPrintCrashingInput(); - // Ideally, we would be able to call driver_cleanup here to perform a - // graceful shutdown of the JVM. However, doing this directly results in a - // nested bug report by ASan or UBSan, likely because something about the - // stack/thread context in which they generate reports is incompatible with - // the JVM shutdown process. use_sigaltstack=0 does not help though, so this - // might be on us. The alternative of calling driver_cleanup in a new thread - // and joining on it results in an endless wait in DestroyJavaVM, even when - // the main thread is detached beforehand - it is not clear why. - }); -} diff --git a/driver/fuzz_target_runner.h b/driver/fuzz_target_runner.h deleted file mode 100644 index fbc3f1e6..00000000 --- a/driver/fuzz_target_runner.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include - -#include -#include - -namespace jazzer { -/* - * Starts libFuzzer with the provided command-line arguments and runs the - * FuzzTargetRunner Java class in the provided JVM. - */ -int StartFuzzer(JNIEnv *env, int argc, char **argv); - -/* - * Print the stack traces of all active JVM threads. - * - * This function can be called from any thread. - */ -void DumpJvmStackTraces(); -} // namespace jazzer diff --git a/driver/fuzzed_data_provider.cpp b/driver/fuzzed_data_provider.cpp deleted file mode 100644 index f4956ac7..00000000 --- a/driver/fuzzed_data_provider.cpp +++ /dev/null @@ -1,748 +0,0 @@ -// 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. -// -// Modified from -// https://raw.githubusercontent.com/google/atheris/034284dc4bb1ad4f4ab6ba5d34fb4dca7c633660/fuzzed_data_provider.cc -// -// Original license and copyright notices: -// -// 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 -// -// 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. -// -// Modified from -// https://github.com/llvm/llvm-project/blob/70de7e0d9a95b7fcd7c105b06bd90fdf4e01f563/compiler-rt/include/fuzzer/FuzzedDataProvider.h -// -// Original license and copyright notices: -// -//===- FuzzedDataProvider.h - Utility header for fuzz targets ---*- C++ -* ===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// - -#include "fuzzed_data_provider.h" - -#include -#include -#include -#include -#include - -#include "absl/strings/str_format.h" -#include "com_code_intelligence_jazzer_runtime_FuzzedDataProviderImpl.h" - -namespace { - -// The current position in the fuzzer input. -const uint8_t *gDataPtr = nullptr; -// The remaining unconsumed bytes at the current position in the fuzzer input. -std::size_t gRemainingBytes = 0; - -const uint8_t *gFuzzerInputStart = nullptr; -std::size_t gFuzzerInputSize = 0; - -// Advance by `bytes` bytes in the buffer or stay at the end if it has been -// consumed. -void Advance(const std::size_t bytes) { - if (bytes > gRemainingBytes) { - gRemainingBytes = 0; - } else { - gDataPtr += bytes; - gRemainingBytes -= bytes; - } -} - -void ThrowIllegalArgumentException(JNIEnv &env, const std::string &message) { - jclass illegal_argument_exception = - env.FindClass("java/lang/IllegalArgumentException"); - env.ThrowNew(illegal_argument_exception, message.c_str()); -} - -template -struct JniArrayType {}; - -#define JNI_ARRAY_TYPE(lower_case, sentence_case) \ - template <> \ - struct JniArrayType { \ - typedef j##lower_case type; \ - typedef j##lower_case##Array array_type; \ - static constexpr array_type (JNIEnv::*kNewArrayFunc)(jsize) = \ - &JNIEnv::New##sentence_case##Array; \ - static constexpr void (JNIEnv::*kSetArrayRegionFunc)( \ - array_type array, jsize start, jsize len, \ - const type *buf) = &JNIEnv::Set##sentence_case##ArrayRegion; \ - }; - -JNI_ARRAY_TYPE(boolean, Boolean); -JNI_ARRAY_TYPE(byte, Byte); -JNI_ARRAY_TYPE(short, Short); -JNI_ARRAY_TYPE(int, Int); -JNI_ARRAY_TYPE(long, Long); - -template -typename JniArrayType::array_type JNICALL -ConsumeIntegralArray(JNIEnv &env, jobject self, jint max_length) { - if (max_length < 0) { - ThrowIllegalArgumentException(env, "maxLength must not be negative"); - return nullptr; - } - // Arrays of integral types are considered data and thus consumed from the - // beginning of the buffer. - std::size_t max_num_bytes = std::min(sizeof(T) * max_length, gRemainingBytes); - jsize actual_length = max_num_bytes / sizeof(T); - std::size_t actual_num_bytes = sizeof(T) * actual_length; - auto array = (env.*(JniArrayType::kNewArrayFunc))(actual_length); - (env.*(JniArrayType::kSetArrayRegionFunc))( - array, 0, actual_length, reinterpret_cast(gDataPtr)); - Advance(actual_num_bytes); - return array; -} - -template -jbyteArray JNICALL ConsumeRemainingAsArray(JNIEnv &env, jobject self) { - return ConsumeIntegralArray(env, self, std::numeric_limits::max()); -} - -template -T JNICALL ConsumeIntegralInRange(JNIEnv &env, jobject self, T min, T max) { - if (min > max) { - ThrowIllegalArgumentException( - env, absl::StrFormat( - "Consume*InRange: min must be <= max (got min: %d, max: %d)", - min, max)); - return 0; - } - - uint64_t range = static_cast(max) - min; - uint64_t result = 0; - std::size_t offset = 0; - - while (offset < 8 * sizeof(T) && (range >> offset) > 0 && - gRemainingBytes != 0) { - --gRemainingBytes; - result = (result << 8u) | gDataPtr[gRemainingBytes]; - offset += 8; - } - - if (range != std::numeric_limits::max()) - // We accept modulo bias in favor of reading a dynamic number of bytes as - // this would make it harder for the fuzzer to mutate towards values from - // the table of recent compares. - result = result % (range + 1); - - return static_cast(min + result); -} - -template -T JNICALL ConsumeIntegral(JNIEnv &env, jobject self) { - // First generate an unsigned value and then (safely) cast it to a signed - // integral type. By doing this rather than calling ConsumeIntegralInRange - // with bounds [signed_min, signed_max], we ensure that there is a direct - // correspondence between the consumed raw bytes and the result (e.g., 0 - // corresponds to 0 and not to signed_min). This should help mutating - // towards entries of the table of recent compares. - using UnsignedT = typename std::make_unsigned::type; - static_assert( - std::numeric_limits::is_modulo, - "Unsigned to signed conversion requires modulo-based overflow handling"); - return static_cast(ConsumeIntegralInRange( - env, self, 0, std::numeric_limits::max())); -} - -bool JNICALL ConsumeBool(JNIEnv &env, jobject self) { - return ConsumeIntegral(env, self) & 1u; -} - -jchar ConsumeCharInternal(JNIEnv &env, jobject self, bool filter_surrogates) { - auto raw_codepoint = ConsumeIntegral(env, self); - if (filter_surrogates && raw_codepoint >= 0xd800 && raw_codepoint < 0xe000) - raw_codepoint -= 0xd800; - return raw_codepoint; -} - -jchar JNICALL ConsumeChar(JNIEnv &env, jobject self) { - return ConsumeCharInternal(env, self, false); -} - -jchar JNICALL ConsumeCharNoSurrogates(JNIEnv &env, jobject self) { - return ConsumeCharInternal(env, self, true); -} - -template -T JNICALL ConsumeProbability(JNIEnv &env, jobject self) { - using IntegralType = - typename std::conditional<(sizeof(T) <= sizeof(uint32_t)), uint32_t, - uint64_t>::type; - T result = static_cast(ConsumeIntegral(env, self)); - result /= static_cast(std::numeric_limits::max()); - return result; -} - -template -T JNICALL ConsumeFloatInRange(JNIEnv &env, jobject self, T min, T max) { - if (min > max) { - ThrowIllegalArgumentException( - env, absl::StrFormat( - "Consume*InRange: min must be <= max (got min: %f, max: %f)", - min, max)); - return 0.0; - } - - T range; - T result = min; - - // Deal with overflow, in the event min and max are very far apart - if (min < 0 && max > 0 && min + std::numeric_limits::max() < max) { - range = (max / 2) - (min / 2); - if (ConsumeBool(env, self)) { - result += range; - } - } else { - range = max - min; - } - - T probability = ConsumeProbability(env, self); - return result + range * probability; -} - -template -T JNICALL ConsumeRegularFloat(JNIEnv &env, jobject self) { - return ConsumeFloatInRange(env, self, std::numeric_limits::lowest(), - std::numeric_limits::max()); -} - -template -T JNICALL ConsumeFloat(JNIEnv &env, jobject self) { - if (!gRemainingBytes) return 0.0; - - auto type_val = ConsumeIntegral(env, self); - - if (type_val <= 10) { - // Consume the same amount of bytes as for a regular float/double - ConsumeRegularFloat(env, self); - - switch (type_val) { - case 0: - return 0.0; - case 1: - return -0.0; - case 2: - return std::numeric_limits::infinity(); - case 3: - return -std::numeric_limits::infinity(); - case 4: - return std::numeric_limits::quiet_NaN(); - case 5: - return std::numeric_limits::denorm_min(); - case 6: - return -std::numeric_limits::denorm_min(); - case 7: - return std::numeric_limits::min(); - case 8: - return -std::numeric_limits::min(); - case 9: - return std::numeric_limits::max(); - case 10: - return -std::numeric_limits::max(); - default: - abort(); - } - } - - T regular = ConsumeRegularFloat(env, self); - return regular; -} - -// Polyfill for C++20 std::countl_one, which counts the number of leading ones -// in an unsigned integer. -inline __attribute__((always_inline)) uint8_t countl_one(uint8_t byte) { - // The result of __builtin_clz is undefined for 0. - if (byte == 0xFF) return 8; - return __builtin_clz(static_cast(~byte)) - 24; -} - -// Forces a byte to be a valid UTF-8 continuation byte. -inline __attribute__((always_inline)) void ForceContinuationByte( - uint8_t &byte) { - byte = (byte | (1u << 7u)) & ~(1u << 6u); -} - -constexpr uint8_t kTwoByteZeroLeadingByte = 0b11000000; -constexpr uint8_t kTwoByteZeroContinuationByte = 0b10000000; -constexpr uint8_t kThreeByteLowLeadingByte = 0b11100000; -constexpr uint8_t kSurrogateLeadingByte = 0b11101101; - -enum class Utf8GenerationState { - LeadingByte_Generic, - LeadingByte_AfterBackslash, - ContinuationByte_Generic, - ContinuationByte_LowLeadingByte, - FirstContinuationByte_LowLeadingByte, - FirstContinuationByte_SurrogateLeadingByte, - FirstContinuationByte_Generic, - SecondContinuationByte_Generic, - LeadingByte_LowSurrogate, - FirstContinuationByte_LowSurrogate, - SecondContinuationByte_HighSurrogate, - SecondContinuationByte_LowSurrogate, -}; - -// Consumes up to `max_bytes` arbitrary bytes pointed to by `ptr` and returns a -// valid "modified UTF-8" string of length at most `max_length` that resembles -// the input bytes as closely as possible as well as the number of consumed -// bytes. If `stop_on_slash` is true, then the string will end on the first -// single consumed '\'. -// -// "Modified UTF-8" is the string encoding used by the JNI. It is the same as -// the legacy encoding CESU-8, but with `\0` coded on two bytes. In these -// encodings, code points requiring 4 bytes in modern UTF-8 are represented as -// two surrogates, each of which is coded on 3 bytes. -// -// This function has been designed with the following goals in mind: -// 1. The generated string should be biased towards containing ASCII characters -// as these are often the ones that affect control flow directly. -// 2. Correctly encoded data (e.g. taken from the table of recent compares) -// should be emitted unchanged. -// 3. The raw fuzzer input should be preserved as far as possible, but the -// output must always be correctly encoded. -// -// The JVM accepts string in two encodings: UTF-16 and modified UTF-8. -// Generating UTF-16 would make it harder to fulfill the first design goal and -// would potentially hinder compatibility with corpora using the much more -// widely used UTF-8 encoding, which is reasonably similar to modified UTF-8. As -// a result, this function uses modified UTF-8. -// -// See Algorithm 1 of https://arxiv.org/pdf/2010.03090.pdf for more details on -// the individual cases involved in determining the validity of a UTF-8 string. -template -std::pair FixUpModifiedUtf8(const uint8_t *data, - std::size_t max_bytes, - jint max_length) { - std::string str; - // Every character in modified UTF-8 is coded on at most six bytes. Every - // consumed byte is transformed into at most one code unit, except for the - // case of a zero byte which requires two bytes. - if (max_bytes > std::numeric_limits::max() / 2) - max_bytes = std::numeric_limits::max() / 2; - if (ascii_only) { - str.reserve( - std::min(2 * static_cast(max_length), 2 * max_bytes)); - } else { - str.reserve( - std::min(6 * static_cast(max_length), 2 * max_bytes)); - } - - Utf8GenerationState state = Utf8GenerationState::LeadingByte_Generic; - const uint8_t *pos = data; - const auto data_end = data + max_bytes; - for (std::size_t length = 0; length < max_length && pos != data_end; ++pos) { - uint8_t c = *pos; - if (ascii_only) { - // Clamp to 7-bit ASCII range. - c &= 0x7Fu; - } - // Fix up c or previously read bytes according to the value of c and the - // current state. In the end, add the fixed up code unit c to the string. - // Exception: The zero character has to be coded on two bytes and is the - // only case in which an iteration of the loop adds two code units. - switch (state) { - case Utf8GenerationState::LeadingByte_Generic: { - switch (ascii_only ? 0 : countl_one(c)) { - case 0: { - // valid - 1-byte code point (ASCII) - // The zero character has to be coded on two bytes in modified - // UTF-8. - if (c == 0) { - str += static_cast(kTwoByteZeroLeadingByte); - c = kTwoByteZeroContinuationByte; - } else if (stop_on_backslash && c == '\\') { - state = Utf8GenerationState::LeadingByte_AfterBackslash; - // The slash either signals the end of the string or is skipped, - // so don't append anything. - continue; - } - // Remain in state LeadingByte. - ++length; - break; - } - case 1: { - // invalid - continuation byte at leader byte position - // Fix it up to be of the form 0b110XXXXX and fall through to the - // case of a 2-byte sequence. - c |= 1u << 6u; - c &= ~(1u << 5u); - [[fallthrough]]; - } - case 2: { - // (most likely) valid - start of a 2-byte sequence - // ASCII characters must be coded on a single byte, so we must - // ensure that the lower two bits combined with the six non-header - // bits of the following byte do not form a 7-bit ASCII value. This - // could only be the case if at most the lowest bit is set. - if ((c & 0b00011110u) == 0) { - state = Utf8GenerationState::ContinuationByte_LowLeadingByte; - } else { - state = Utf8GenerationState::ContinuationByte_Generic; - } - break; - } - // The default case falls through to the case of three leading ones - // coming right after. - default: { - // invalid - at least four leading ones - // In the case of exactly four leading ones, this would be valid - // UTF-8, but is not valid in the JVM's modified UTF-8 encoding. - // Fix it up by clearing the fourth leading one and falling through - // to the 3-byte case. - c &= ~(1u << 4u); - [[fallthrough]]; - } - case 3: { - // valid - start of a 3-byte sequence - if (c == kThreeByteLowLeadingByte) { - state = Utf8GenerationState::FirstContinuationByte_LowLeadingByte; - } else if (c == kSurrogateLeadingByte) { - state = Utf8GenerationState:: - FirstContinuationByte_SurrogateLeadingByte; - } else { - state = Utf8GenerationState::FirstContinuationByte_Generic; - } - break; - } - } - break; - } - case Utf8GenerationState::LeadingByte_AfterBackslash: { - if (c != '\\') { - // Mark the current byte as consumed. - ++pos; - goto done; - } - // A double backslash is consumed as a single one. As we skipped the - // first one, emit the second one as usual. - state = Utf8GenerationState::LeadingByte_Generic; - ++length; - break; - } - case Utf8GenerationState::ContinuationByte_LowLeadingByte: { - ForceContinuationByte(c); - // Preserve the zero character, which is coded on two bytes in modified - // UTF-8. In all other cases ensure that we are not incorrectly encoding - // an ASCII character on two bytes by setting the eigth least - // significant bit of the encoded value (second least significant bit of - // the leading byte). - auto previous_c = static_cast(str.back()); - if (previous_c != kTwoByteZeroLeadingByte || - c != kTwoByteZeroContinuationByte) { - str.back() = static_cast(previous_c | (1u << 1u)); - } - state = Utf8GenerationState::LeadingByte_Generic; - ++length; - break; - } - case Utf8GenerationState::ContinuationByte_Generic: { - ForceContinuationByte(c); - state = Utf8GenerationState::LeadingByte_Generic; - ++length; - break; - } - case Utf8GenerationState::FirstContinuationByte_LowLeadingByte: { - ForceContinuationByte(c); - // Ensure that the current code point could not have been coded on two - // bytes. As two bytes encode up to 11 bits and three bytes encode up - // to 16 bits, we thus have to make it such that the five highest bits - // are not all zero. Four of these bits are the non-header bits of the - // leader byte. Thus, set the highest non-header bit in this byte (fifth - // highest in the encoded value). - c |= 1u << 5u; - state = Utf8GenerationState::SecondContinuationByte_Generic; - break; - } - case Utf8GenerationState::FirstContinuationByte_SurrogateLeadingByte: { - ForceContinuationByte(c); - if (c & (1u << 5u)) { - // Start with a high surrogate (0xD800-0xDBFF). c contains the second - // byte and the first two bits of the third byte. The first two bits - // of this second byte are fixed to 10 (in 0x8-0xB). - c |= 1u << 5u; - c &= ~(1u << 4u); - // The high surrogate must be followed by a low surrogate. - state = Utf8GenerationState::SecondContinuationByte_HighSurrogate; - } else { - state = Utf8GenerationState::SecondContinuationByte_Generic; - } - break; - } - case Utf8GenerationState::FirstContinuationByte_Generic: { - ForceContinuationByte(c); - state = Utf8GenerationState::SecondContinuationByte_Generic; - break; - } - case Utf8GenerationState::SecondContinuationByte_HighSurrogate: { - ForceContinuationByte(c); - state = Utf8GenerationState::LeadingByte_LowSurrogate; - ++length; - break; - } - case Utf8GenerationState::SecondContinuationByte_LowSurrogate: - case Utf8GenerationState::SecondContinuationByte_Generic: { - ForceContinuationByte(c); - state = Utf8GenerationState::LeadingByte_Generic; - ++length; - break; - } - case Utf8GenerationState::LeadingByte_LowSurrogate: { - // We have to emit a low surrogate leading byte, which is a fixed value. - // We still consume a byte from the input to make fuzzer changes more - // stable and preserve valid surrogate pairs picked up from e.g. the - // table of recent compares. - c = kSurrogateLeadingByte; - state = Utf8GenerationState::FirstContinuationByte_LowSurrogate; - break; - } - case Utf8GenerationState::FirstContinuationByte_LowSurrogate: { - ForceContinuationByte(c); - // Low surrogates are code points in the range 0xDC00-0xDFFF. c contains - // the second byte and the first two bits of the third byte. The first - // two bits of this second byte are fixed to 11 (in 0xC-0xF). - c |= (1u << 5u) | (1u << 4u); - // The second continuation byte of a low surrogate is not restricted, - // but we need to track it differently to allow for correct backtracking - // if it isn't completed. - state = Utf8GenerationState::SecondContinuationByte_LowSurrogate; - break; - } - } - str += static_cast(c); - } - - // Backtrack the current incomplete character. - switch (state) { - case Utf8GenerationState::SecondContinuationByte_LowSurrogate: - str.pop_back(); - [[fallthrough]]; - case Utf8GenerationState::FirstContinuationByte_LowSurrogate: - str.pop_back(); - [[fallthrough]]; - case Utf8GenerationState::LeadingByte_LowSurrogate: - str.pop_back(); - [[fallthrough]]; - case Utf8GenerationState::SecondContinuationByte_Generic: - case Utf8GenerationState::SecondContinuationByte_HighSurrogate: - str.pop_back(); - [[fallthrough]]; - case Utf8GenerationState::ContinuationByte_Generic: - case Utf8GenerationState::ContinuationByte_LowLeadingByte: - case Utf8GenerationState::FirstContinuationByte_Generic: - case Utf8GenerationState::FirstContinuationByte_LowLeadingByte: - case Utf8GenerationState::FirstContinuationByte_SurrogateLeadingByte: - str.pop_back(); - [[fallthrough]]; - case Utf8GenerationState::LeadingByte_Generic: - case Utf8GenerationState::LeadingByte_AfterBackslash: - // No backtracking required. - break; - } - -done: - return std::make_pair(str, pos - data); -} -} // namespace - -namespace jazzer { -// Exposed for testing only. -std::pair FixUpModifiedUtf8(const uint8_t *data, - std::size_t max_bytes, - jint max_length, - bool ascii_only, - bool stop_on_backslash) { - if (ascii_only) { - if (stop_on_backslash) { - return ::FixUpModifiedUtf8(data, max_bytes, max_length); - } else { - return ::FixUpModifiedUtf8(data, max_bytes, max_length); - } - } else { - if (stop_on_backslash) { - return ::FixUpModifiedUtf8(data, max_bytes, max_length); - } else { - return ::FixUpModifiedUtf8(data, max_bytes, max_length); - } - } -} -} // namespace jazzer - -namespace { -jstring ConsumeStringInternal(JNIEnv &env, jint max_length, bool ascii_only, - bool stop_on_backslash) { - if (max_length < 0) { - ThrowIllegalArgumentException(env, "maxLength must not be negative"); - return nullptr; - } - - if (max_length == 0 || gRemainingBytes == 0) return env.NewStringUTF(""); - - if (gRemainingBytes == 1) { - Advance(1); - return env.NewStringUTF(""); - } - - std::size_t max_bytes = gRemainingBytes; - std::string str; - std::size_t consumed_bytes; - std::tie(str, consumed_bytes) = jazzer::FixUpModifiedUtf8( - gDataPtr, max_bytes, max_length, ascii_only, stop_on_backslash); - Advance(consumed_bytes); - return env.NewStringUTF(str.c_str()); -} - -jstring JNICALL ConsumeAsciiString(JNIEnv &env, jobject self, jint max_length) { - return ConsumeStringInternal(env, max_length, true, true); -} - -jstring JNICALL ConsumeString(JNIEnv &env, jobject self, jint max_length) { - return ConsumeStringInternal(env, max_length, false, true); -} - -jstring JNICALL ConsumeRemainingAsAsciiString(JNIEnv &env, jobject self) { - return ConsumeStringInternal(env, std::numeric_limits::max(), true, - false); -} - -jstring JNICALL ConsumeRemainingAsString(JNIEnv &env, jobject self) { - return ConsumeStringInternal(env, std::numeric_limits::max(), false, - false); -} - -std::size_t RemainingBytes(JNIEnv &env, jobject self) { - return gRemainingBytes; -} - -const JNINativeMethod kFuzzedDataMethods[]{ - {(char *)"consumeBoolean", (char *)"()Z", (void *)&ConsumeBool}, - {(char *)"consumeByte", (char *)"()B", (void *)&ConsumeIntegral}, - {(char *)"consumeByte", (char *)"(BB)B", - (void *)&ConsumeIntegralInRange}, - {(char *)"consumeShort", (char *)"()S", (void *)&ConsumeIntegral}, - {(char *)"consumeShort", (char *)"(SS)S", - (void *)&ConsumeIntegralInRange}, - {(char *)"consumeInt", (char *)"()I", (void *)&ConsumeIntegral}, - {(char *)"consumeInt", (char *)"(II)I", - (void *)&ConsumeIntegralInRange}, - {(char *)"consumeLong", (char *)"()J", (void *)&ConsumeIntegral}, - {(char *)"consumeLong", (char *)"(JJ)J", - (void *)&ConsumeIntegralInRange}, - {(char *)"consumeFloat", (char *)"()F", (void *)&ConsumeFloat}, - {(char *)"consumeRegularFloat", (char *)"()F", - (void *)&ConsumeRegularFloat}, - {(char *)"consumeRegularFloat", (char *)"(FF)F", - (void *)&ConsumeFloatInRange}, - {(char *)"consumeProbabilityFloat", (char *)"()F", - (void *)&ConsumeProbability}, - {(char *)"consumeDouble", (char *)"()D", (void *)&ConsumeFloat}, - {(char *)"consumeRegularDouble", (char *)"()D", - (void *)&ConsumeRegularFloat}, - {(char *)"consumeRegularDouble", (char *)"(DD)D", - (void *)&ConsumeFloatInRange}, - {(char *)"consumeProbabilityDouble", (char *)"()D", - (void *)&ConsumeProbability}, - {(char *)"consumeChar", (char *)"()C", (void *)&ConsumeChar}, - {(char *)"consumeChar", (char *)"(CC)C", - (void *)&ConsumeIntegralInRange}, - {(char *)"consumeCharNoSurrogates", (char *)"()C", - (void *)&ConsumeCharNoSurrogates}, - {(char *)"consumeAsciiString", (char *)"(I)Ljava/lang/String;", - (void *)&ConsumeAsciiString}, - {(char *)"consumeRemainingAsAsciiString", (char *)"()Ljava/lang/String;", - (void *)&ConsumeRemainingAsAsciiString}, - {(char *)"consumeString", (char *)"(I)Ljava/lang/String;", - (void *)&ConsumeString}, - {(char *)"consumeRemainingAsString", (char *)"()Ljava/lang/String;", - (void *)&ConsumeRemainingAsString}, - {(char *)"consumeBooleans", (char *)"(I)[Z", - (void *)&ConsumeIntegralArray}, - {(char *)"consumeBytes", (char *)"(I)[B", - (void *)&ConsumeIntegralArray}, - {(char *)"consumeShorts", (char *)"(I)[S", - (void *)&ConsumeIntegralArray}, - {(char *)"consumeInts", (char *)"(I)[I", - (void *)&ConsumeIntegralArray}, - {(char *)"consumeLongs", (char *)"(I)[J", - (void *)&ConsumeIntegralArray}, - {(char *)"consumeRemainingAsBytes", (char *)"()[B", - (void *)&ConsumeRemainingAsArray}, - {(char *)"remainingBytes", (char *)"()I", (void *)&RemainingBytes}, -}; -const jint kNumFuzzedDataMethods = - sizeof(kFuzzedDataMethods) / sizeof(kFuzzedDataMethods[0]); -} // namespace - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_FuzzedDataProviderImpl_nativeInit( - JNIEnv *env, jclass clazz) { - env->RegisterNatives(clazz, kFuzzedDataMethods, kNumFuzzedDataMethods); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_FuzzedDataProviderImpl_reset( - JNIEnv *env, jclass clazz) { - gDataPtr = gFuzzerInputStart; - gRemainingBytes = gFuzzerInputSize; -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_FuzzedDataProviderImpl_feed( - JNIEnv *env, jclass, jbyteArray input) { - // This line is why this function must not be used if FeedFuzzedDataProvider - // is also called from native code. - delete[] gFuzzerInputStart; - - std::size_t size = env->GetArrayLength(input); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->FatalError("Failed to get length of input"); - } - auto *data = static_cast(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(data)); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->FatalError("Failed to copy input"); - } - jazzer::FeedFuzzedDataProvider(data, size); -} - -namespace jazzer { -void FeedFuzzedDataProvider(const uint8_t *data, std::size_t size) { - gDataPtr = data; - gRemainingBytes = size; - - gFuzzerInputStart = data; - gFuzzerInputSize = size; -} -} // namespace jazzer diff --git a/driver/fuzzed_data_provider.h b/driver/fuzzed_data_provider.h deleted file mode 100644 index c9b72546..00000000 --- a/driver/fuzzed_data_provider.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace jazzer { - -constexpr char kFuzzedDataProviderImplClass[] = - "com/code_intelligence/jazzer/runtime/FuzzedDataProviderImpl"; - -// Feed the FuzzedDataProvider with a new data buffer. The buffer is accessed -// by native code and not copied into the JVM, so this is cheap to call. -void FeedFuzzedDataProvider(const uint8_t *data, std::size_t size); -} // namespace jazzer diff --git a/driver/fuzzed_data_provider_test.cpp b/driver/fuzzed_data_provider_test.cpp index c0a12d70..85cf87e2 100644 --- a/driver/fuzzed_data_provider_test.cpp +++ b/driver/fuzzed_data_provider_test.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "fuzzed_data_provider.h" +#include "driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.h" #include #include diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index c084c344..580a4b6a 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -140,11 +140,6 @@ constexpr auto kPathSeparator = '\\'; constexpr auto kPathSeparator = '/'; #endif -extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL -JNI_OnLoad_jazzer_initialize(JavaVM *vm, void *) { - return JNI_VERSION_1_8; -} - namespace { constexpr auto kAgentBazelRunfilesPath = "jazzer/agent/jazzer_agent_deploy.jar"; constexpr auto kAgentFileName = "jazzer_agent_deploy.jar"; diff --git a/driver/libfuzzer_callbacks.cpp b/driver/libfuzzer_callbacks.cpp deleted file mode 100644 index a6df806b..00000000 --- a/driver/libfuzzer_callbacks.cpp +++ /dev/null @@ -1,128 +0,0 @@ -// 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 - -#include -#include -#include -#include -#include - -#include "absl/strings/match.h" -#include "absl/strings/str_split.h" -#include "com_code_intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks.h" - -namespace { -bool is_using_native_libraries = false; -std::once_flag ignore_list_flag; -std::vector> ignore_for_interception_ranges; - -/** - * Adds the address ranges of executable segmentes of the library lib_name to - * the ignorelist for C standard library function interception (strcmp, memcmp, - * ...). - */ -void ignoreLibraryForInterception(const std::string &lib_name) { - std::ifstream loaded_libs("/proc/self/maps"); - if (!loaded_libs) { - // This early exit is taken e.g. on macOS, where /proc does not exist. - return; - } - std::string line; - while (std::getline(loaded_libs, line)) { - if (!absl::StrContains(line, lib_name)) continue; - // clang-format off - // A typical line looks as follows: - // 7f15356c9000-7f1536367000 r-xp 0020d000 fd:01 19275673 /usr/lib/jvm/java-15-openjdk-amd64/lib/server/libjvm.so - // clang-format on - std::vector parts = - absl::StrSplit(line, ' ', absl::SkipEmpty()); - if (parts.size() != 6) { - std::cout << "ERROR: Invalid format for /proc/self/maps\n" - << line << std::endl; - exit(1); - } - // Skip non-executable address rang"s. - if (!absl::StrContains(parts[1], "x")) continue; - std::string_view range_str = parts[0]; - std::vector range = absl::StrSplit(range_str, "-"); - if (range.size() != 2) { - std::cout - << "ERROR: Unexpected address range format in /proc/self/maps line: " - << range_str << std::endl; - exit(1); - } - std::size_t pos; - auto start = std::stoull(range[0], &pos, 16); - if (pos != range[0].size()) { - std::cout - << "ERROR: Unexpected address range format in /proc/self/maps line: " - << range_str << std::endl; - exit(1); - } - auto end = std::stoull(range[1], &pos, 16); - if (pos != range[0].size()) { - std::cout - << "ERROR: Unexpected address range format in /proc/self/maps line: " - << range_str << std::endl; - exit(1); - } - ignore_for_interception_ranges.emplace_back(start, end); - } -} - -const std::vector kLibrariesToIgnoreForInterception = { - // The driver executable itself can be treated just like a library. - "jazzer_driver", "libinstrument.so", "libjava.so", - "libjimage.so", "libjli.so", "libjvm.so", - "libnet.so", "libverify.so", "libzip.so", -}; -} // namespace - -extern "C" [[maybe_unused]] bool __sanitizer_weak_is_relevant_pc( - void *caller_pc) { - // If the fuzz target is not using native libraries, calls to strcmp, memcmp, - // etc. should never be intercepted. The values reported if they were at best - // duplicate the values received from our bytecode instrumentation and at - // worst pollute the table of recent compares with string internal to the JDK. - if (!is_using_native_libraries) return false; - // If the fuzz target is using native libraries, intercept calls only if they - // don't originate from those address ranges that are known to belong to the - // JDK. - return std::none_of(ignore_for_interception_ranges.cbegin(), - ignore_for_interception_ranges.cend(), - [caller_pc](const auto &range) { - uintptr_t start; - uintptr_t end; - std::tie(start, end) = range; - auto address = reinterpret_cast(caller_pc); - return start <= address && address <= end; - }); -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_handleLibraryLoad( - JNIEnv *env, jclass cls) { - std::call_once(ignore_list_flag, [] { - std::cout << "INFO: detected a native library load, enabling interception " - "for libc functions" - << std::endl; - for (const auto &lib_name : kLibrariesToIgnoreForInterception) - ignoreLibraryForInterception(lib_name); - // Enable the ignore list after it has been populated since vector is not - // thread-safe with respect to concurrent writes and reads. - is_using_native_libraries = true; - }); -} diff --git a/driver/native_fuzzer_hooks.c b/driver/native_fuzzer_hooks.c new file mode 100644 index 00000000..4b581887 --- /dev/null +++ b/driver/native_fuzzer_hooks.c @@ -0,0 +1,527 @@ +// Copyright 2022 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. + +/* + * Dynamically exported definitions of fuzzer hooks and libc functions that + * forward to the symbols provided by the Jazzer driver JNI library once it has + * been loaded. + * + * Native libraries instrumented for fuzzing include references to fuzzer hooks + * that are resolved by the dynamic linker. Sanitizers such as ASan provide weak + * definitions of these symbols, but the dynamic linker doesn't distinguish + * between weak and strong symbols and thus wouldn't ever resolve them against + * the strong definitions provided by the Jazzer driver JNI library. + * Furthermore, libc functions can only be overridden in the native driver + * executable, which is the only binary that comes before the actual libc in the + * dynamic linker search order. + */ + +#define _GNU_SOURCE // for RTLD_NEXT +#include +#include +#include +#include + +#define GET_CALLER_PC() __builtin_return_address(0) +#define LIKELY(x) __builtin_expect(!!(x), 1) +#define UNLIKELY(x) __builtin_expect(!!(x), 0) + +typedef int (*bcmp_t)(const void *, const void *, size_t); +static _Atomic bcmp_t bcmp_real; +typedef void (*bcmp_hook_t)(void *, const void *, const void *, size_t, int); +static _Atomic bcmp_hook_t bcmp_hook; + +typedef int (*memcmp_t)(const void *, const void *, size_t); +static _Atomic memcmp_t memcmp_real; +typedef void (*memcmp_hook_t)(void *, const void *, const void *, size_t, int); +static _Atomic memcmp_hook_t memcmp_hook; + +typedef int (*strncmp_t)(const char *, const char *, size_t); +static _Atomic strncmp_t strncmp_real; +typedef void (*strncmp_hook_t)(void *, const char *, const char *, size_t, int); +static _Atomic strncmp_hook_t strncmp_hook; + +typedef int (*strcmp_t)(const char *, const char *); +static _Atomic strcmp_t strcmp_real; +typedef void (*strcmp_hook_t)(void *, const char *, const char *, int); +static _Atomic strcmp_hook_t strcmp_hook; + +typedef int (*strncasecmp_t)(const char *, const char *, size_t); +static _Atomic strncasecmp_t strncasecmp_real; +typedef void (*strncasecmp_hook_t)(void *, const char *, const char *, size_t, + int); +static _Atomic strncasecmp_hook_t strncasecmp_hook; + +typedef int (*strcasecmp_t)(const char *, const char *); +static _Atomic strcasecmp_t strcasecmp_real; +typedef void (*strcasecmp_hook_t)(void *, const char *, const char *, int); +static _Atomic strcasecmp_hook_t strcasecmp_hook; + +typedef char *(*strstr_t)(const char *, const char *); +static _Atomic strstr_t strstr_real; +typedef void (*strstr_hook_t)(void *, const char *, const char *, char *); +static _Atomic strstr_hook_t strstr_hook; + +typedef char *(*strcasestr_t)(const char *, const char *); +static _Atomic strcasestr_t strcasestr_real; +typedef void (*strcasestr_hook_t)(void *, const char *, const char *, char *); +static _Atomic strcasestr_hook_t strcasestr_hook; + +typedef void *(*memmem_t)(const void *, size_t, const void *, size_t); +static _Atomic memmem_t memmem_real; +typedef void (*memmem_hook_t)(void *, const void *, size_t, const void *, + size_t, void *); +static _Atomic memmem_hook_t memmem_hook; + +typedef void (*cov_8bit_counters_init_t)(uint8_t *, uint8_t *); +static _Atomic cov_8bit_counters_init_t cov_8bit_counters_init; +typedef void (*cov_pcs_init_t)(const uintptr_t *, const uintptr_t *); +static _Atomic cov_pcs_init_t cov_pcs_init; + +typedef void (*trace_cmp1_t)(void *, uint8_t, uint8_t); +static _Atomic trace_cmp1_t trace_cmp1_with_pc; +typedef void (*trace_cmp2_t)(void *, uint16_t, uint16_t); +static _Atomic trace_cmp2_t trace_cmp2_with_pc; +typedef void (*trace_cmp4_t)(void *, uint32_t, uint32_t); +static _Atomic trace_cmp4_t trace_cmp4_with_pc; +typedef void (*trace_cmp8_t)(void *, uint64_t, uint64_t); +static _Atomic trace_cmp8_t trace_cmp8_with_pc; + +typedef void (*trace_const_cmp1_t)(void *, uint8_t, uint8_t); +static _Atomic trace_const_cmp1_t trace_const_cmp1_with_pc; +typedef void (*trace_const_cmp2_t)(void *, uint16_t, uint16_t); +static _Atomic trace_const_cmp2_t trace_const_cmp2_with_pc; +typedef void (*trace_const_cmp4_t)(void *, uint32_t, uint32_t); +static _Atomic trace_const_cmp4_t trace_const_cmp4_with_pc; +typedef void (*trace_const_cmp8_t)(void *, uint64_t, uint64_t); +static _Atomic trace_const_cmp8_t trace_const_cmp8_with_pc; + +typedef void (*trace_switch_t)(void *, uint64_t, uint64_t *); +static _Atomic trace_switch_t trace_switch_with_pc; + +typedef void (*trace_div4_t)(void *, uint32_t); +static _Atomic trace_div4_t trace_div4_with_pc; +typedef void (*trace_div8_t)(void *, uint64_t); +static _Atomic trace_div8_t trace_div8_with_pc; + +typedef void (*trace_gep_t)(void *, uintptr_t); +static _Atomic trace_gep_t trace_gep_with_pc; + +typedef void (*trace_pc_indir_t)(void *, uintptr_t); +static _Atomic trace_pc_indir_t trace_pc_indir_with_pc; + +__attribute__((visibility("default"))) void jazzer_initialize_native_hooks( + void *handle) { + atomic_store(&bcmp_hook, dlsym(handle, "__sanitizer_weak_hook_bcmp")); + atomic_store(&memcmp_hook, dlsym(handle, "__sanitizer_weak_hook_memcmp")); + atomic_store(&strncmp_hook, dlsym(handle, "__sanitizer_weak_hook_strncmp")); + atomic_store(&strcmp_hook, dlsym(handle, "__sanitizer_weak_hook_strcmp")); + atomic_store(&strncasecmp_hook, + dlsym(handle, "__sanitizer_weak_hook_strncasecmp")); + atomic_store(&strcasecmp_hook, + dlsym(handle, "__sanitizer_weak_hook_strcasecmp")); + atomic_store(&strstr_hook, dlsym(handle, "__sanitizer_weak_hook_strstr")); + atomic_store(&strcasestr_hook, + dlsym(handle, "__sanitizer_weak_hook_strcasestr")); + atomic_store(&memmem_hook, dlsym(handle, "__sanitizer_weak_hook_memmem")); + + atomic_store(&cov_8bit_counters_init, + dlsym(handle, "__sanitizer_cov_8bit_counters_init")); + atomic_store(&cov_pcs_init, dlsym(handle, "__sanitizer_cov_pcs_init")); + + atomic_store(&trace_cmp1_with_pc, + dlsym(handle, "__sanitizer_cov_trace_cmp1_with_pc")); + atomic_store(&trace_cmp2_with_pc, + dlsym(handle, "__sanitizer_cov_trace_cmp2_with_pc")); + atomic_store(&trace_cmp4_with_pc, + dlsym(handle, "__sanitizer_cov_trace_cmp4_with_pc")); + atomic_store(&trace_cmp8_with_pc, + dlsym(handle, "__sanitizer_cov_trace_cmp8_with_pc")); + + atomic_store(&trace_const_cmp1_with_pc, + dlsym(handle, "__sanitizer_cov_trace_const_cmp1_with_pc")); + atomic_store(&trace_const_cmp2_with_pc, + dlsym(handle, "__sanitizer_cov_trace_const_cmp2_with_pc")); + atomic_store(&trace_const_cmp4_with_pc, + dlsym(handle, "__sanitizer_cov_trace_const_cmp4_with_pc")); + atomic_store(&trace_const_cmp8_with_pc, + dlsym(handle, "__sanitizer_cov_trace_const_cmp8_with_pc")); + + atomic_store(&trace_switch_with_pc, + dlsym(handle, "__sanitizer_cov_trace_switch_with_pc")); + + atomic_store(&trace_div4_with_pc, + dlsym(handle, "__sanitizer_cov_trace_div4_with_pc")); + atomic_store(&trace_div8_with_pc, + dlsym(handle, "__sanitizer_cov_trace_div8_with_pc")); + + atomic_store(&trace_gep_with_pc, + dlsym(handle, "__sanitizer_cov_trace_gep_with_pc")); + + atomic_store(&trace_pc_indir_with_pc, + dlsym(handle, "__sanitizer_cov_trace_pc_indir_with_pc")); +} + +// Alternate definitions for libc functions mimicking those that libFuzzer would +// provide if it were part of the native driver executable. All these functions +// invoke the real libc function loaded from the next library in search order +// (usually libc itself). +// Function pointers have to be loaded and stored atomically even if libc +// functions are invoked from different threads, but we do not need any +// synchronization guarantees - in the worst case, we will non-deterministically +// lose a few hook invocations. + +__attribute__((visibility("default"))) int bcmp(const void *s1, const void *s2, + size_t n) { + bcmp_t bcmp_real_local = + atomic_load_explicit(&bcmp_real, memory_order_relaxed); + if (UNLIKELY(bcmp_real_local == NULL)) { + bcmp_real_local = dlsym(RTLD_NEXT, "bcmp"); + atomic_store_explicit(&bcmp_real, bcmp_real_local, memory_order_relaxed); + } + + int result = bcmp_real_local(s1, s2, n); + bcmp_hook_t hook = atomic_load_explicit(&bcmp_hook, memory_order_relaxed); + if (LIKELY(hook != NULL)) { + hook(GET_CALLER_PC(), s1, s2, n, result); + } + return result; +} + +__attribute__((visibility("default"))) int memcmp(const void *s1, + const void *s2, size_t n) { + memcmp_t memcmp_real_local = + atomic_load_explicit(&memcmp_real, memory_order_relaxed); + if (UNLIKELY(memcmp_real_local == NULL)) { + memcmp_real_local = dlsym(RTLD_NEXT, "memcmp"); + atomic_store_explicit(&memcmp_real, memcmp_real_local, + memory_order_relaxed); + } + + int result = memcmp_real_local(s1, s2, n); + memcmp_hook_t hook = atomic_load_explicit(&memcmp_hook, memory_order_relaxed); + if (LIKELY(hook != NULL)) { + hook(GET_CALLER_PC(), s1, s2, n, result); + } + return result; +} + +__attribute__((visibility("default"))) int strncmp(const char *s1, + const char *s2, size_t n) { + strncmp_t strncmp_real_local = + atomic_load_explicit(&strncmp_real, memory_order_relaxed); + if (UNLIKELY(strncmp_real_local == NULL)) { + strncmp_real_local = dlsym(RTLD_NEXT, "strncmp"); + atomic_store_explicit(&strncmp_real, strncmp_real_local, + memory_order_relaxed); + } + + int result = strncmp_real_local(s1, s2, n); + strncmp_hook_t hook = + atomic_load_explicit(&strncmp_hook, memory_order_relaxed); + if (LIKELY(hook != NULL)) { + hook(GET_CALLER_PC(), s1, s2, n, result); + } + return result; +} + +__attribute__((visibility("default"))) int strncasecmp(const char *s1, + const char *s2, + size_t n) { + strncasecmp_t strncasecmp_real_local = + atomic_load_explicit(&strncasecmp_real, memory_order_relaxed); + if (UNLIKELY(strncasecmp_real_local == NULL)) { + strncasecmp_real_local = dlsym(RTLD_NEXT, "strncasecmp"); + atomic_store_explicit(&strncasecmp_real, strncasecmp_real_local, + memory_order_relaxed); + } + + int result = strncasecmp_real_local(s1, s2, n); + strncasecmp_hook_t hook = + atomic_load_explicit(&strncasecmp_hook, memory_order_relaxed); + if (LIKELY(hook != NULL)) { + hook(GET_CALLER_PC(), s1, s2, n, result); + } + return result; +} + +__attribute__((visibility("default"))) int strcmp(const char *s1, + const char *s2) { + strcmp_t strcmp_real_local = + atomic_load_explicit(&strcmp_real, memory_order_relaxed); + if (UNLIKELY(strcmp_real_local == NULL)) { + strcmp_real_local = dlsym(RTLD_NEXT, "strcmp"); + atomic_store_explicit(&strcmp_real, strcmp_real_local, + memory_order_relaxed); + } + + int result = strcmp_real_local(s1, s2); + strcmp_hook_t hook = atomic_load_explicit(&strcmp_hook, memory_order_relaxed); + if (LIKELY(hook != NULL)) { + hook(GET_CALLER_PC(), s1, s2, result); + } + return result; +} + +__attribute__((visibility("default"))) int strcasecmp(const char *s1, + const char *s2) { + strcasecmp_t strcasecmp_real_local = + atomic_load_explicit(&strcasecmp_real, memory_order_relaxed); + if (UNLIKELY(strcasecmp_real_local == NULL)) { + strcasecmp_real_local = dlsym(RTLD_NEXT, "strcasecmp"); + atomic_store_explicit(&strcasecmp_real, strcasecmp_real_local, + memory_order_relaxed); + } + + int result = strcasecmp_real_local(s1, s2); + strcasecmp_hook_t hook = + atomic_load_explicit(&strcasecmp_hook, memory_order_relaxed); + if (LIKELY(hook != NULL)) { + hook(GET_CALLER_PC(), s1, s2, result); + } + return result; +} + +__attribute__((visibility("default"))) char *strstr(const char *s1, + const char *s2) { + strstr_t strstr_real_local = + atomic_load_explicit(&strstr_real, memory_order_relaxed); + if (UNLIKELY(strstr_real_local == NULL)) { + strstr_real_local = dlsym(RTLD_NEXT, "strstr"); + atomic_store_explicit(&strstr_real, strstr_real_local, + memory_order_relaxed); + } + + char *result = strstr_real_local(s1, s2); + strstr_hook_t hook = atomic_load_explicit(&strstr_hook, memory_order_relaxed); + if (LIKELY(hook != NULL)) { + hook(GET_CALLER_PC(), s1, s2, result); + } + return result; +} + +__attribute__((visibility("default"))) char *strcasestr(const char *s1, + const char *s2) { + strcasestr_t strcasestr_real_local = + atomic_load_explicit(&strcasestr_real, memory_order_relaxed); + if (UNLIKELY(strcasestr_real_local == NULL)) { + strcasestr_real_local = dlsym(RTLD_NEXT, "strcasestr"); + atomic_store_explicit(&strcasestr_real, strcasestr_real_local, + memory_order_relaxed); + } + + char *result = strcasestr_real_local(s1, s2); + strcasestr_hook_t hook = + atomic_load_explicit(&strcasestr_hook, memory_order_relaxed); + if (LIKELY(hook != NULL)) { + hook(GET_CALLER_PC(), s1, s2, result); + } + return result; +} + +__attribute__((visibility("default"))) void *memmem(const void *s1, size_t n1, + const void *s2, size_t n2) { + memmem_t memmem_real_local = + atomic_load_explicit(&memmem_real, memory_order_relaxed); + if (UNLIKELY(memmem_real_local == NULL)) { + memmem_real_local = dlsym(RTLD_NEXT, "memmem"); + atomic_store_explicit(&memmem_real, memmem_real_local, + memory_order_relaxed); + } + + void *result = memmem_real_local(s1, n1, s2, n2); + memmem_hook_t hook = atomic_load_explicit(&memmem_hook, memory_order_relaxed); + if (LIKELY(hook != NULL)) { + hook(GET_CALLER_PC(), s1, n1, s2, n2, result); + } + return result; +} + +// The __sanitizer_cov_trace_* family of functions is only invoked from code +// compiled with -fsanitize=fuzzer. We can assume that the Jazzer JNI library +// has been loaded before any such code, which necessarily belongs to the fuzz +// target, is executed and thus don't need NULL checks. + +__attribute__((visibility("default"))) void __sanitizer_cov_trace_cmp1( + uint8_t arg1, uint8_t arg2) { + trace_cmp1_t hook = + atomic_load_explicit(&trace_cmp1_with_pc, memory_order_relaxed); + hook(GET_CALLER_PC(), arg1, arg2); +} + +__attribute__((visibility("default"))) void __sanitizer_cov_trace_cmp2( + uint16_t arg1, uint16_t arg2) { + trace_cmp2_t hook = + atomic_load_explicit(&trace_cmp2_with_pc, memory_order_relaxed); + hook(GET_CALLER_PC(), arg1, arg2); +} + +__attribute__((visibility("default"))) void __sanitizer_cov_trace_cmp4( + uint32_t arg1, uint32_t arg2) { + trace_cmp4_t hook = + atomic_load_explicit(&trace_cmp4_with_pc, memory_order_relaxed); + hook(GET_CALLER_PC(), arg1, arg2); +} + +__attribute__((visibility("default"))) void __sanitizer_cov_trace_cmp8( + uint64_t arg1, uint64_t arg2) { + trace_cmp8_t hook = + atomic_load_explicit(&trace_cmp8_with_pc, memory_order_relaxed); + hook(GET_CALLER_PC(), arg1, arg2); +} + +__attribute__((visibility("default"))) void __sanitizer_cov_trace_const_cmp1( + uint8_t arg1, uint8_t arg2) { + trace_const_cmp1_t hook = + atomic_load_explicit(&trace_const_cmp1_with_pc, memory_order_relaxed); + hook(GET_CALLER_PC(), arg1, arg2); +} + +__attribute__((visibility("default"))) void __sanitizer_cov_trace_const_cmp2( + uint16_t arg1, uint16_t arg2) { + trace_const_cmp2_t hook = + atomic_load_explicit(&trace_const_cmp2_with_pc, memory_order_relaxed); + hook(GET_CALLER_PC(), arg1, arg2); +} + +__attribute__((visibility("default"))) void __sanitizer_cov_trace_const_cmp4( + uint32_t arg1, uint32_t arg2) { + trace_const_cmp4_t hook = + atomic_load_explicit(&trace_const_cmp4_with_pc, memory_order_relaxed); + hook(GET_CALLER_PC(), arg1, arg2); +} + +__attribute__((visibility("default"))) void __sanitizer_cov_trace_const_cmp8( + uint64_t arg1, uint64_t arg2) { + trace_const_cmp8_t hook = + atomic_load_explicit(&trace_const_cmp8_with_pc, memory_order_relaxed); + hook(GET_CALLER_PC(), arg1, arg2); +} + +__attribute__((visibility("default"))) void __sanitizer_cov_trace_switch( + uint64_t val, uint64_t *cases) { + trace_switch_t hook = + atomic_load_explicit(&trace_switch_with_pc, memory_order_relaxed); + hook(GET_CALLER_PC(), val, cases); +} + +__attribute__((visibility("default"))) void __sanitizer_cov_trace_div4( + uint32_t val) { + trace_div4_t hook = + atomic_load_explicit(&trace_div4_with_pc, memory_order_relaxed); + hook(GET_CALLER_PC(), val); +} + +__attribute__((visibility("default"))) void __sanitizer_cov_trace_div8( + uint64_t val) { + trace_div8_t hook = + atomic_load_explicit(&trace_div8_with_pc, memory_order_relaxed); + hook(GET_CALLER_PC(), val); +} + +__attribute__((visibility("default"))) void __sanitizer_cov_trace_gep( + uintptr_t idx) { + trace_gep_t hook = + atomic_load_explicit(&trace_gep_with_pc, memory_order_relaxed); + hook(GET_CALLER_PC(), idx); +} + +__attribute__((visibility("default"))) void __sanitizer_cov_trace_pc_indir( + uintptr_t callee) { + trace_pc_indir_t hook = + atomic_load_explicit(&trace_pc_indir_with_pc, memory_order_relaxed); + hook(GET_CALLER_PC(), callee); +} + +__attribute__((visibility("default"))) void __sanitizer_cov_8bit_counters_init( + uint8_t *start, uint8_t *end) { + cov_8bit_counters_init_t init = + atomic_load_explicit(&cov_8bit_counters_init, memory_order_relaxed); + init(start, end); +} + +__attribute__((visibility("default"))) void __sanitizer_cov_pcs_init( + const uintptr_t *pcs_beg, const uintptr_t *pcs_end) { + cov_pcs_init_t init = + atomic_load_explicit(&cov_pcs_init, memory_order_relaxed); + init(pcs_beg, pcs_end); +} + +// The __sanitizer_weak_hook_* family of functions can be invoked early on macOS +// and thus requires NULL checks. + +__attribute__((visibility("default"))) void __sanitizer_weak_hook_memcmp( + void *called_pc, const void *s1, const void *s2, size_t n, int result) { + memcmp_hook_t hook = atomic_load_explicit(&memcmp_hook, memory_order_relaxed); + if (LIKELY(hook != NULL)) { + hook(called_pc, s1, s2, n, result); + } +} + +__attribute__((visibility("default"))) void __sanitizer_weak_hook_strncmp( + void *called_pc, const void *s1, const void *s2, size_t n, int result) { + strncmp_hook_t hook = + atomic_load_explicit(&strncmp_hook, memory_order_relaxed); + if (LIKELY(hook != NULL)) { + hook(called_pc, s1, s2, n, result); + } +} + +__attribute__((visibility("default"))) void __sanitizer_weak_hook_strcmp( + void *called_pc, const void *s1, const void *s2, int result) { + strcmp_hook_t hook = atomic_load_explicit(&strcmp_hook, memory_order_relaxed); + if (LIKELY(hook != NULL)) { + hook(called_pc, s1, s2, result); + } +} + +__attribute__((visibility("default"))) void __sanitizer_weak_hook_strncasecmp( + void *called_pc, const void *s1, const void *s2, size_t n, int result) { + strncasecmp_hook_t hook = + atomic_load_explicit(&strncasecmp_hook, memory_order_relaxed); + if (LIKELY(hook != NULL)) { + hook(called_pc, s1, s2, n, result); + } +} + +__attribute__((visibility("default"))) void __sanitizer_weak_hook_strcasecmp( + void *called_pc, const void *s1, const void *s2, int result) { + strcasecmp_hook_t hook = + atomic_load_explicit(&strcasecmp_hook, memory_order_relaxed); + if (LIKELY(hook != NULL)) { + hook(called_pc, s1, s2, result); + } +} + +__attribute__((visibility("default"))) void __sanitizer_weak_hook_strstr( + void *called_pc, const void *s1, const void *s2, char *result) { + strstr_hook_t hook = atomic_load_explicit(&strstr_hook, memory_order_relaxed); + if (LIKELY(hook != NULL)) { + hook(called_pc, s1, s2, result); + } +} + +__attribute__((visibility("default"))) void __sanitizer_weak_hook_strcasestr( + void *called_pc, const void *s1, const void *s2, char *result) { + strcasestr_hook_t hook = + atomic_load_explicit(&strstr_hook, memory_order_relaxed); + hook(called_pc, s1, s2, result); +} + +__attribute__((visibility("default"))) void __sanitizer_weak_hook_memmem( + void *called_pc, const void *s1, size_t len1, const void *s2, size_t len2, + void *result) { + memmem_hook_t hook = atomic_load_explicit(&memmem_hook, memory_order_relaxed); + hook(called_pc, s1, len1, s2, len2, result); +} diff --git a/driver/rtld_global_hack.cpp b/driver/rtld_global_hack.cpp deleted file mode 100644 index 539f9d3e..00000000 --- a/driver/rtld_global_hack.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2022 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 -#include - -#include - -// Upgrades the current shared library to RTLD_GLOBAL so that its exported -// symbols are used to resolve unresolved symbols in shared libraries loaded -// afterwards. -jint JNI_OnLoad(JavaVM *vm, void *reserved) { - Dl_info info; - - if (!dladdr(reinterpret_cast(&JNI_OnLoad), &info) || - !info.dli_fname) { - fprintf(stderr, "Failed to determine our dli_fname\n"); - abort(); - } - - void *handle = dlopen(info.dli_fname, RTLD_NOLOAD | RTLD_GLOBAL | RTLD_NOW); - if (handle == nullptr) { - fprintf(stderr, "Failed to upgrade self to RTLD_GLOBAL: %s", dlerror()); - abort(); - } - dlclose(handle); - - return JNI_VERSION_1_8; -} diff --git a/driver/sanitizer_hooks_with_pc.h b/driver/sanitizer_hooks_with_pc.h deleted file mode 100644 index 60340371..00000000 --- a/driver/sanitizer_hooks_with_pc.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include - -// This file declares variants of the libFuzzer compare, division, switch and -// gep hooks that accept an additional caller_pc argument that can be used to -// pass a custom value that is recorded as the caller's instruction pointer -// ("program counter"). This allows synthetic program counters obtained from -// Java coverage information to be used with libFuzzer's value profile, with -// which it records detailed information about the result of compares and -// associates it with particular coverage locations. -// -// Note: Only the lower 9 bits of the caller_pc argument are used by libFuzzer. -extern "C" { -void __sanitizer_cov_trace_cmp4_with_pc(void *caller_pc, uint32_t arg1, - uint32_t arg2); -void __sanitizer_cov_trace_cmp8_with_pc(void *caller_pc, uint64_t arg1, - uint64_t arg2); - -void __sanitizer_cov_trace_switch_with_pc(void *caller_pc, uint64_t val, - uint64_t *cases); - -void __sanitizer_cov_trace_div4_with_pc(void *caller_pc, uint32_t val); -void __sanitizer_cov_trace_div8_with_pc(void *caller_pc, uint64_t val); - -void __sanitizer_cov_trace_gep_with_pc(void *caller_pc, uintptr_t idx); - -void __sanitizer_cov_trace_pc_indir_with_pc(void *caller_pc, uintptr_t callee); -} diff --git a/driver/sanitizer_symbols.cpp b/driver/sanitizer_symbols.cpp deleted file mode 100644 index bd28d104..00000000 --- a/driver/sanitizer_symbols.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// 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. - -// Called in fuzz_target_runner.cpp. -extern "C" void __sanitizer_set_death_callback(void (*)()) {} - -// Suppress libFuzzer warnings about missing sanitizer methods in non-sanitizer -// builds. -extern "C" [[maybe_unused]] int __sanitizer_acquire_crash_state() { return 1; } - -namespace jazzer { -void DumpJvmStackTraces(); -} - -// Dump a JVM stack trace on timeouts. -extern "C" [[maybe_unused]] void __sanitizer_print_stack_trace() { - jazzer::DumpJvmStackTraces(); -} diff --git a/driver/sanitizer_symbols_for_tests.cpp b/driver/sanitizer_symbols_for_tests.cpp deleted file mode 100644 index ab18dbfc..00000000 --- a/driver/sanitizer_symbols_for_tests.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// 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 -#include - -// Symbols exported by libFuzzer that are required by libfuzzer_callbacks and -// coverage_tracker and fuzz_target_runner. -extern "C" { -void __sanitizer_cov_8bit_counters_init(uint8_t *start, uint8_t *end) {} -void __sanitizer_cov_pcs_init(const uintptr_t *pcs_beg, - const uintptr_t *pcs_end) {} -size_t __sanitizer_cov_get_observed_pcs(uintptr_t **pc_entries) { - *pc_entries = new uintptr_t[0]; - return 0; -} -void __sanitizer_weak_hook_compare_bytes(void *caller_pc, const void *s1, - const void *s2, std::size_t n1, - std::size_t n2, int result) {} -void __sanitizer_weak_hook_memmem(void *called_pc, const void *s1, size_t len1, - const void *s2, size_t len2, void *result) {} -void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2) {} -void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases) {} -void __sanitizer_set_death_callback(void (*callback)()) {} -void __sanitizer_cov_trace_cmp4_with_pc(void *caller_pc, uint32_t arg1, - uint32_t arg2) {} -void __sanitizer_cov_trace_cmp8_with_pc(void *caller_pc, uint64_t arg1, - uint64_t arg2) {} -void __sanitizer_cov_trace_switch_with_pc(void *caller_pc, uint64_t val, - uint64_t *cases) {} -void __sanitizer_cov_trace_div4_with_pc(void *caller_pc, uint32_t val) {} -void __sanitizer_cov_trace_div8_with_pc(void *caller_pc, uint64_t val) {} -void __sanitizer_cov_trace_gep_with_pc(void *caller_pc, uintptr_t idx) {} -void __sanitizer_cov_trace_pc_indir_with_pc(void *caller_pc, uintptr_t callee) { -} -int LLVMFuzzerRunDriver(int *argc, char ***argv, - int (*UserCb)(const uint8_t *Data, size_t Size)) { - return 0; -} -} diff --git a/driver/signal_handler.cpp b/driver/signal_handler.cpp deleted file mode 100644 index 2600a53a..00000000 --- a/driver/signal_handler.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// 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 - -#include -#include - -#include "com_code_intelligence_jazzer_runtime_SignalHandler.h" - -#ifdef _WIN32 -// Windows does not have SIGUSR1, which triggers a graceful exit of libFuzzer. -// Instead, trigger a hard exit. -#define SIGUSR1 SIGTERM -#endif - -// Handles SIGINT raised while running Java code. -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_SignalHandler_handleInterrupt( - JNIEnv *, jclass) { - static std::atomic already_exiting{false}; - if (!already_exiting.exchange(true)) { - // Let libFuzzer exit gracefully when the JVM received SIGINT. - raise(SIGUSR1); - } else { - // Exit libFuzzer forcefully on repeated SIGINTs. - raise(SIGTERM); - } -} diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel b/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel index 05e04d02..a864fbb4 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -18,8 +18,12 @@ java_library( java_jni_library( name = "fuzz_target_runner", srcs = ["FuzzTargetRunner.java"], + native_libs = [ + "//driver/src/main/native/com/code_intelligence/jazzer/driver:jazzer_driver", + ], visibility = [ - "//driver:__pkg__", + "//agent:__pkg__", + "//driver/src/main/native/com/code_intelligence/jazzer/driver:__pkg__", "//driver/src/test:__subpackages__", ], deps = [ diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index 731cd82a..68e24fc3 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -30,6 +30,7 @@ import com.code_intelligence.jazzer.runtime.RecordingFuzzedDataProvider; import com.code_intelligence.jazzer.runtime.SignalHandler; import com.code_intelligence.jazzer.utils.ExceptionUtils; import com.code_intelligence.jazzer.utils.ManifestUtils; +import com.github.fmeum.rules_jni.RulesJni; import java.io.IOException; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -54,6 +55,10 @@ import java.util.Set; * concurrently. */ public final class FuzzTargetRunner { + static { + RulesJni.loadLibrary("jazzer_driver", FuzzTargetRunner.class); + } + // Default value of the libFuzzer -error_exitcode flag. private static final int LIBFUZZER_ERROR_EXIT_CODE = 77; private static final String AUTOFUZZ_FUZZ_TARGET = diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel b/driver/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel index edb0b4c7..2e846414 100644 --- a/driver/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -1,12 +1,127 @@ load("@fmeum_rules_jni//jni:defs.bzl", "cc_jni_library") +load("//bazel:compat.bzl", "SKIP_ON_WINDOWS") cc_jni_library( - name = "fuzzed_data_provider_standalone", + name = "jazzer_driver", + visibility = [ + "//agent/src/jmh:__subpackages__", + "//agent/src/test:__subpackages__", + "//driver/src/main/java/com/code_intelligence/jazzer/driver:__pkg__", + "//driver/src/test:__subpackages__", + ], + deps = [ + ":jazzer_driver_lib", + "@jazzer_libfuzzer//:libfuzzer_no_main", + ] + select({ + # Windows doesn't have a concept analogous to RTLD_GLOBAL. + "@platforms//os:windows": [], + "//conditions:default": [":trigger_driver_hooks_load"], + }), +) + +cc_library( + name = "jazzer_driver_lib", + visibility = ["//driver/src/test/native/com/code_intelligence/jazzer/driver/mocks:__pkg__"], + deps = [ + ":coverage_tracker", + ":fuzz_target_runner", + ":fuzzed_data_provider", + ":jazzer_fuzzer_callbacks", + ":libfuzzer_callbacks", + ], +) + +cc_library( + name = "coverage_tracker", + srcs = ["coverage_tracker.cpp"], + hdrs = ["coverage_tracker.h"], + deps = ["//agent/src/main/java/com/code_intelligence/jazzer/runtime:coverage_map.hdrs"], + # Symbols are only referenced dynamically via JNI. + alwayslink = True, +) + +cc_library( + name = "fuzz_target_runner", + srcs = ["fuzz_target_runner.cpp"], + hdrs = ["fuzz_target_runner.h"], + linkopts = select({ + "@platforms//os:windows": [], + "//conditions:default": ["-ldl"], + }), + deps = [ + ":fuzzed_data_provider", + ":sanitizer_symbols", + "//driver/src/main/java/com/code_intelligence/jazzer/driver:fuzz_target_runner.hdrs", + ], + # With sanitizers, symbols are only referenced dynamically via JNI. + alwayslink = True, +) + +cc_library( + name = "fuzzed_data_provider", + srcs = ["fuzzed_data_provider.cpp"], + hdrs = ["fuzzed_data_provider.h"], visibility = [ - "//agent/src/main/java/com/code_intelligence/jazzer/replay:__pkg__", - "//agent/src/test/java/com/code_intelligence/jazzer/runtime:__pkg__", + "//driver:__pkg__", + ], + deps = [ + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:fuzzed_data_provider.hdrs", + "@com_google_absl//absl/strings:str_format", ], + # Symbols may only be referenced dynamically via JNI. + alwayslink = True, +) + +cc_jni_library( + name = "fuzzed_data_provider_standalone", + visibility = ["//agent/src/main/java/com/code_intelligence/jazzer/replay:__pkg__"], + deps = [":fuzzed_data_provider"], +) + +cc_library( + name = "jazzer_fuzzer_callbacks", + srcs = ["jazzer_fuzzer_callbacks.cpp"], + deps = [ + ":sanitizer_hooks_with_pc", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:trace_data_flow_native_callbacks.hdrs", + ], + alwayslink = True, +) + +cc_library( + name = "libfuzzer_callbacks", + srcs = ["libfuzzer_callbacks.cpp"], deps = [ - "//driver:fuzzed_data_provider", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:trace_data_flow_native_callbacks.hdrs", + "@com_google_absl//absl/strings", ], + # Symbols are only referenced dynamically via JNI. + alwayslink = True, +) + +cc_library( + name = "trigger_driver_hooks_load", + srcs = ["trigger_driver_hooks_load.cpp"], + linkopts = ["-ldl"], + target_compatible_with = SKIP_ON_WINDOWS, + deps = ["@fmeum_rules_jni//jni"], + # Symbols are only referenced dynamically via JNI. + alwayslink = True, +) + +cc_library( + name = "sanitizer_hooks_with_pc", + hdrs = ["sanitizer_hooks_with_pc.h"], + visibility = [ + "//agent/src/jmh/native:__subpackages__", + "//driver:__pkg__", + "//driver/src/test/native/com/code_intelligence/jazzer/driver:__pkg__", + ], +) + +cc_library( + name = "sanitizer_symbols", + srcs = ["sanitizer_symbols.cpp"], + # Symbols are referenced dynamically by libFuzzer. + alwayslink = True, ) diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/coverage_tracker.cpp b/driver/src/main/native/com/code_intelligence/jazzer/driver/coverage_tracker.cpp new file mode 100644 index 00000000..47872fa9 --- /dev/null +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/coverage_tracker.cpp @@ -0,0 +1,116 @@ +// 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 "coverage_tracker.h" + +#include + +#include +#include +#include +#include + +#include "com_code_intelligence_jazzer_runtime_CoverageMap.h" + +extern "C" void __sanitizer_cov_8bit_counters_init(uint8_t *start, + uint8_t *end); +extern "C" void __sanitizer_cov_pcs_init(const uintptr_t *pcs_beg, + const uintptr_t *pcs_end); +extern "C" size_t __sanitizer_cov_get_observed_pcs(uintptr_t **pc_entries); + +namespace { +void AssertNoException(JNIEnv &env) { + if (env.ExceptionCheck()) { + env.ExceptionDescribe(); + throw std::runtime_error( + "Java exception occurred in CoverageTracker JNI code"); + } +} +} // namespace + +namespace jazzer { + +uint8_t *CoverageTracker::counters_ = nullptr; +PCTableEntry *CoverageTracker::pc_entries_ = nullptr; + +void CoverageTracker::Initialize(JNIEnv &env, jlong counters) { + if (counters_ != nullptr) { + throw std::runtime_error( + "CoverageTracker::Initialize must not be called more than once"); + } + counters_ = reinterpret_cast(static_cast(counters)); +} + +void CoverageTracker::RegisterNewCounters(JNIEnv &env, jint old_num_counters, + jint new_num_counters) { + if (counters_ == nullptr) { + throw std::runtime_error( + "CoverageTracker::Initialize should have been called first"); + } + if (new_num_counters < old_num_counters) { + throw std::runtime_error( + "new_num_counters must not be smaller than old_num_counters"); + } + if (new_num_counters == old_num_counters) { + return; + } + std::size_t diff_num_counters = new_num_counters - old_num_counters; + // libFuzzer requires an array containing the instruction addresses associated + // with the coverage counters registered above. This is required to report how + // many edges have been covered. However, libFuzzer only checks these + // addresses when the corresponding flag is set to 1. Therefore, it is safe to + // set the all PC entries to any value as long as the corresponding flag is + // set to zero. We set the value of each PC to the index of the corresponding + // edge ID. This facilitates finding the edge ID of each covered PC reported + // by libFuzzer. + pc_entries_ = new PCTableEntry[diff_num_counters]; + for (std::size_t i = 0; i < diff_num_counters; ++i) { + pc_entries_[i] = {i, 0}; + } + __sanitizer_cov_8bit_counters_init(counters_ + old_num_counters, + counters_ + new_num_counters); + __sanitizer_cov_pcs_init((uintptr_t *)(pc_entries_), + (uintptr_t *)(pc_entries_ + diff_num_counters)); +} +} // namespace jazzer + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_CoverageMap_initialize( + JNIEnv *env, jclass cls, jlong counters) { + ::jazzer::CoverageTracker::Initialize(*env, counters); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_CoverageMap_registerNewCounters( + JNIEnv *env, jclass cls, jint old_num_counters, jint new_num_counters) { + ::jazzer::CoverageTracker::RegisterNewCounters(*env, old_num_counters, + new_num_counters); +} + +[[maybe_unused]] jintArray +Java_com_code_1intelligence_jazzer_runtime_CoverageMap_getEverCoveredIds( + JNIEnv *env, jclass) { + uintptr_t *covered_pcs; + jint num_covered_pcs = __sanitizer_cov_get_observed_pcs(&covered_pcs); + std::vector covered_edge_ids(covered_pcs, + covered_pcs + num_covered_pcs); + delete[] covered_pcs; + + jintArray covered_edge_ids_jni = env->NewIntArray(num_covered_pcs); + AssertNoException(*env); + env->SetIntArrayRegion(covered_edge_ids_jni, 0, num_covered_pcs, + covered_edge_ids.data()); + AssertNoException(*env); + return covered_edge_ids_jni; +} diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/coverage_tracker.h b/driver/src/main/native/com/code_intelligence/jazzer/driver/coverage_tracker.h new file mode 100644 index 00000000..8cceceed --- /dev/null +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/coverage_tracker.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include + +namespace jazzer { + +// The members of this struct are only accessed by libFuzzer. +struct __attribute__((packed)) PCTableEntry { + [[maybe_unused]] uintptr_t PC, PCFlags; +}; + +// CoverageTracker registers an array of 8-bit coverage counters with +// libFuzzer. The array is populated from Java using Unsafe. +class CoverageTracker { + private: + static uint8_t *counters_; + static PCTableEntry *pc_entries_; + + public: + static void Initialize(JNIEnv &env, jlong counters); + static void RegisterNewCounters(JNIEnv &env, jint old_num_counters, + jint new_num_counters); +}; +} // namespace jazzer diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzz_target_runner.cpp b/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzz_target_runner.cpp new file mode 100644 index 00000000..ae620e8d --- /dev/null +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzz_target_runner.cpp @@ -0,0 +1,199 @@ +// 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. + +/** + * A native wrapper around the FuzzTargetRunner Java class that executes it as a + * libFuzzer fuzz target. + */ + +#include "fuzz_target_runner.h" + +#ifndef _WIN32 +#include +#endif +#include + +#include +#include +#include + +#include "com_code_intelligence_jazzer_driver_FuzzTargetRunner.h" +#include "fuzzed_data_provider.h" + +extern "C" int LLVMFuzzerRunDriver(int *argc, char ***argv, + int (*UserCb)(const uint8_t *Data, + size_t Size)); + +namespace { +bool gUseFuzzedDataProvider; +jclass gRunner; +jmethodID gRunOneId; +JavaVM *gJavaVm; +JNIEnv *gEnv; + +// A libFuzzer-registered callback that outputs the crashing input, but does +// not include a stack trace. +void (*gLibfuzzerPrintCrashingInput)() = nullptr; + +int testOneInput(const uint8_t *data, const std::size_t size) { + JNIEnv &env = *gEnv; + jint jsize = + std::min(size, static_cast(std::numeric_limits::max())); + int res; + if (gUseFuzzedDataProvider) { + ::jazzer::FeedFuzzedDataProvider(data, size); + res = env.CallStaticIntMethod(gRunner, gRunOneId, nullptr); + } else { + jbyteArray input = env.NewByteArray(jsize); + env.SetByteArrayRegion(input, 0, jsize, + reinterpret_cast(data)); + res = env.CallStaticIntMethod(gRunner, gRunOneId, input); + env.DeleteLocalRef(input); + } + if (env.ExceptionCheck()) { + env.ExceptionDescribe(); + _Exit(1); + } + return res; +} +} // namespace + +namespace jazzer { +void DumpJvmStackTraces() { + JNIEnv *env = nullptr; + if (gJavaVm->AttachCurrentThread(reinterpret_cast(&env), nullptr) != + JNI_OK) { + std::cerr << "WARN: AttachCurrentThread failed in DumpJvmStackTraces" + << std::endl; + return; + } + jmethodID dumpStack = + env->GetStaticMethodID(gRunner, "dumpAllStackTraces", "()V"); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return; + } + env->CallStaticVoidMethod(gRunner, dumpStack); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return; + } + // Do not detach as we may be the main thread (but the JVM exits anyway). +} +} // namespace jazzer + +[[maybe_unused]] jint +Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner_startLibFuzzer( + JNIEnv *env, jclass runner, jobjectArray args) { + gEnv = env; + env->GetJavaVM(&gJavaVm); + gRunner = reinterpret_cast(env->NewGlobalRef(runner)); + gRunOneId = env->GetStaticMethodID(runner, "runOne", "([B)I"); + if (gRunOneId == nullptr) { + env->ExceptionDescribe(); + _Exit(1); + } + jfieldID use_fuzzed_data_provider_id = + env->GetStaticFieldID(runner, "useFuzzedDataProvider", "Z"); + if (use_fuzzed_data_provider_id == nullptr) { + env->ExceptionDescribe(); + _Exit(1); + } + gUseFuzzedDataProvider = + env->GetStaticBooleanField(runner, use_fuzzed_data_provider_id); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + _Exit(1); + } + + int argc = env->GetArrayLength(args); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + _Exit(1); + } + std::vector argv_strings; + std::vector argv_c; + for (jsize i = 0; i < argc; i++) { + auto arg_jni = + reinterpret_cast(env->GetObjectArrayElement(args, i)); + if (arg_jni == nullptr) { + env->ExceptionDescribe(); + _Exit(1); + } + jbyte *arg_c = env->GetByteArrayElements(arg_jni, nullptr); + if (arg_c == nullptr) { + env->ExceptionDescribe(); + _Exit(1); + } + std::size_t arg_size = env->GetArrayLength(arg_jni); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + _Exit(1); + } + argv_strings.emplace_back(reinterpret_cast(arg_c), arg_size); + env->ReleaseByteArrayElements(arg_jni, arg_c, JNI_ABORT); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + _Exit(1); + } + } + for (jsize i = 0; i < argc; i++) { + argv_c.emplace_back(argv_strings[i].c_str()); + } + // Null-terminate argv. + argv_c.emplace_back(nullptr); + + const char **argv = argv_c.data(); + return LLVMFuzzerRunDriver(&argc, const_cast(&argv), testOneInput); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner_printCrashingInput( + JNIEnv *, jclass) { + if (gLibfuzzerPrintCrashingInput == nullptr) { + std::cerr << "" << std::endl; + } else { + gLibfuzzerPrintCrashingInput(); + } +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner__1Exit( + JNIEnv *, jclass, jint exit_code) { + _Exit(exit_code); +} + +// We apply a patch to libFuzzer to make it call this function instead of +// __sanitizer_set_death_callback to pass us the death callback. +extern "C" [[maybe_unused]] void __jazzer_set_death_callback( + void (*callback)()) { + gLibfuzzerPrintCrashingInput = callback; +#ifndef _WIN32 + void *sanitizer_set_death_callback = + dlsym(RTLD_DEFAULT, "__sanitizer_set_death_callback"); + if (sanitizer_set_death_callback != nullptr) { + (reinterpret_cast(sanitizer_set_death_callback))( + []() { + ::jazzer::DumpJvmStackTraces(); + gLibfuzzerPrintCrashingInput(); + // Ideally, we would be able to perform a graceful shutdown of the + // JVM. However, doing this directly results in a nested bug report by + // ASan or UBSan, likely because something about the stack/thread + // context in which they generate reports is incompatible with the JVM + // shutdown process. use_sigaltstack=0 does not help though, so this + // might be on us. + }); + } +#endif +} diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzz_target_runner.h b/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzz_target_runner.h new file mode 100644 index 00000000..fbc3f1e6 --- /dev/null +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzz_target_runner.h @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include +#include + +namespace jazzer { +/* + * Starts libFuzzer with the provided command-line arguments and runs the + * FuzzTargetRunner Java class in the provided JVM. + */ +int StartFuzzer(JNIEnv *env, int argc, char **argv); + +/* + * Print the stack traces of all active JVM threads. + * + * This function can be called from any thread. + */ +void DumpJvmStackTraces(); +} // namespace jazzer diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.cpp b/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.cpp new file mode 100644 index 00000000..f4956ac7 --- /dev/null +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.cpp @@ -0,0 +1,748 @@ +// 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. +// +// Modified from +// https://raw.githubusercontent.com/google/atheris/034284dc4bb1ad4f4ab6ba5d34fb4dca7c633660/fuzzed_data_provider.cc +// +// Original license and copyright notices: +// +// 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 +// +// 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. +// +// Modified from +// https://github.com/llvm/llvm-project/blob/70de7e0d9a95b7fcd7c105b06bd90fdf4e01f563/compiler-rt/include/fuzzer/FuzzedDataProvider.h +// +// Original license and copyright notices: +// +//===- FuzzedDataProvider.h - Utility header for fuzz targets ---*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// + +#include "fuzzed_data_provider.h" + +#include +#include +#include +#include +#include + +#include "absl/strings/str_format.h" +#include "com_code_intelligence_jazzer_runtime_FuzzedDataProviderImpl.h" + +namespace { + +// The current position in the fuzzer input. +const uint8_t *gDataPtr = nullptr; +// The remaining unconsumed bytes at the current position in the fuzzer input. +std::size_t gRemainingBytes = 0; + +const uint8_t *gFuzzerInputStart = nullptr; +std::size_t gFuzzerInputSize = 0; + +// Advance by `bytes` bytes in the buffer or stay at the end if it has been +// consumed. +void Advance(const std::size_t bytes) { + if (bytes > gRemainingBytes) { + gRemainingBytes = 0; + } else { + gDataPtr += bytes; + gRemainingBytes -= bytes; + } +} + +void ThrowIllegalArgumentException(JNIEnv &env, const std::string &message) { + jclass illegal_argument_exception = + env.FindClass("java/lang/IllegalArgumentException"); + env.ThrowNew(illegal_argument_exception, message.c_str()); +} + +template +struct JniArrayType {}; + +#define JNI_ARRAY_TYPE(lower_case, sentence_case) \ + template <> \ + struct JniArrayType { \ + typedef j##lower_case type; \ + typedef j##lower_case##Array array_type; \ + static constexpr array_type (JNIEnv::*kNewArrayFunc)(jsize) = \ + &JNIEnv::New##sentence_case##Array; \ + static constexpr void (JNIEnv::*kSetArrayRegionFunc)( \ + array_type array, jsize start, jsize len, \ + const type *buf) = &JNIEnv::Set##sentence_case##ArrayRegion; \ + }; + +JNI_ARRAY_TYPE(boolean, Boolean); +JNI_ARRAY_TYPE(byte, Byte); +JNI_ARRAY_TYPE(short, Short); +JNI_ARRAY_TYPE(int, Int); +JNI_ARRAY_TYPE(long, Long); + +template +typename JniArrayType::array_type JNICALL +ConsumeIntegralArray(JNIEnv &env, jobject self, jint max_length) { + if (max_length < 0) { + ThrowIllegalArgumentException(env, "maxLength must not be negative"); + return nullptr; + } + // Arrays of integral types are considered data and thus consumed from the + // beginning of the buffer. + std::size_t max_num_bytes = std::min(sizeof(T) * max_length, gRemainingBytes); + jsize actual_length = max_num_bytes / sizeof(T); + std::size_t actual_num_bytes = sizeof(T) * actual_length; + auto array = (env.*(JniArrayType::kNewArrayFunc))(actual_length); + (env.*(JniArrayType::kSetArrayRegionFunc))( + array, 0, actual_length, reinterpret_cast(gDataPtr)); + Advance(actual_num_bytes); + return array; +} + +template +jbyteArray JNICALL ConsumeRemainingAsArray(JNIEnv &env, jobject self) { + return ConsumeIntegralArray(env, self, std::numeric_limits::max()); +} + +template +T JNICALL ConsumeIntegralInRange(JNIEnv &env, jobject self, T min, T max) { + if (min > max) { + ThrowIllegalArgumentException( + env, absl::StrFormat( + "Consume*InRange: min must be <= max (got min: %d, max: %d)", + min, max)); + return 0; + } + + uint64_t range = static_cast(max) - min; + uint64_t result = 0; + std::size_t offset = 0; + + while (offset < 8 * sizeof(T) && (range >> offset) > 0 && + gRemainingBytes != 0) { + --gRemainingBytes; + result = (result << 8u) | gDataPtr[gRemainingBytes]; + offset += 8; + } + + if (range != std::numeric_limits::max()) + // We accept modulo bias in favor of reading a dynamic number of bytes as + // this would make it harder for the fuzzer to mutate towards values from + // the table of recent compares. + result = result % (range + 1); + + return static_cast(min + result); +} + +template +T JNICALL ConsumeIntegral(JNIEnv &env, jobject self) { + // First generate an unsigned value and then (safely) cast it to a signed + // integral type. By doing this rather than calling ConsumeIntegralInRange + // with bounds [signed_min, signed_max], we ensure that there is a direct + // correspondence between the consumed raw bytes and the result (e.g., 0 + // corresponds to 0 and not to signed_min). This should help mutating + // towards entries of the table of recent compares. + using UnsignedT = typename std::make_unsigned::type; + static_assert( + std::numeric_limits::is_modulo, + "Unsigned to signed conversion requires modulo-based overflow handling"); + return static_cast(ConsumeIntegralInRange( + env, self, 0, std::numeric_limits::max())); +} + +bool JNICALL ConsumeBool(JNIEnv &env, jobject self) { + return ConsumeIntegral(env, self) & 1u; +} + +jchar ConsumeCharInternal(JNIEnv &env, jobject self, bool filter_surrogates) { + auto raw_codepoint = ConsumeIntegral(env, self); + if (filter_surrogates && raw_codepoint >= 0xd800 && raw_codepoint < 0xe000) + raw_codepoint -= 0xd800; + return raw_codepoint; +} + +jchar JNICALL ConsumeChar(JNIEnv &env, jobject self) { + return ConsumeCharInternal(env, self, false); +} + +jchar JNICALL ConsumeCharNoSurrogates(JNIEnv &env, jobject self) { + return ConsumeCharInternal(env, self, true); +} + +template +T JNICALL ConsumeProbability(JNIEnv &env, jobject self) { + using IntegralType = + typename std::conditional<(sizeof(T) <= sizeof(uint32_t)), uint32_t, + uint64_t>::type; + T result = static_cast(ConsumeIntegral(env, self)); + result /= static_cast(std::numeric_limits::max()); + return result; +} + +template +T JNICALL ConsumeFloatInRange(JNIEnv &env, jobject self, T min, T max) { + if (min > max) { + ThrowIllegalArgumentException( + env, absl::StrFormat( + "Consume*InRange: min must be <= max (got min: %f, max: %f)", + min, max)); + return 0.0; + } + + T range; + T result = min; + + // Deal with overflow, in the event min and max are very far apart + if (min < 0 && max > 0 && min + std::numeric_limits::max() < max) { + range = (max / 2) - (min / 2); + if (ConsumeBool(env, self)) { + result += range; + } + } else { + range = max - min; + } + + T probability = ConsumeProbability(env, self); + return result + range * probability; +} + +template +T JNICALL ConsumeRegularFloat(JNIEnv &env, jobject self) { + return ConsumeFloatInRange(env, self, std::numeric_limits::lowest(), + std::numeric_limits::max()); +} + +template +T JNICALL ConsumeFloat(JNIEnv &env, jobject self) { + if (!gRemainingBytes) return 0.0; + + auto type_val = ConsumeIntegral(env, self); + + if (type_val <= 10) { + // Consume the same amount of bytes as for a regular float/double + ConsumeRegularFloat(env, self); + + switch (type_val) { + case 0: + return 0.0; + case 1: + return -0.0; + case 2: + return std::numeric_limits::infinity(); + case 3: + return -std::numeric_limits::infinity(); + case 4: + return std::numeric_limits::quiet_NaN(); + case 5: + return std::numeric_limits::denorm_min(); + case 6: + return -std::numeric_limits::denorm_min(); + case 7: + return std::numeric_limits::min(); + case 8: + return -std::numeric_limits::min(); + case 9: + return std::numeric_limits::max(); + case 10: + return -std::numeric_limits::max(); + default: + abort(); + } + } + + T regular = ConsumeRegularFloat(env, self); + return regular; +} + +// Polyfill for C++20 std::countl_one, which counts the number of leading ones +// in an unsigned integer. +inline __attribute__((always_inline)) uint8_t countl_one(uint8_t byte) { + // The result of __builtin_clz is undefined for 0. + if (byte == 0xFF) return 8; + return __builtin_clz(static_cast(~byte)) - 24; +} + +// Forces a byte to be a valid UTF-8 continuation byte. +inline __attribute__((always_inline)) void ForceContinuationByte( + uint8_t &byte) { + byte = (byte | (1u << 7u)) & ~(1u << 6u); +} + +constexpr uint8_t kTwoByteZeroLeadingByte = 0b11000000; +constexpr uint8_t kTwoByteZeroContinuationByte = 0b10000000; +constexpr uint8_t kThreeByteLowLeadingByte = 0b11100000; +constexpr uint8_t kSurrogateLeadingByte = 0b11101101; + +enum class Utf8GenerationState { + LeadingByte_Generic, + LeadingByte_AfterBackslash, + ContinuationByte_Generic, + ContinuationByte_LowLeadingByte, + FirstContinuationByte_LowLeadingByte, + FirstContinuationByte_SurrogateLeadingByte, + FirstContinuationByte_Generic, + SecondContinuationByte_Generic, + LeadingByte_LowSurrogate, + FirstContinuationByte_LowSurrogate, + SecondContinuationByte_HighSurrogate, + SecondContinuationByte_LowSurrogate, +}; + +// Consumes up to `max_bytes` arbitrary bytes pointed to by `ptr` and returns a +// valid "modified UTF-8" string of length at most `max_length` that resembles +// the input bytes as closely as possible as well as the number of consumed +// bytes. If `stop_on_slash` is true, then the string will end on the first +// single consumed '\'. +// +// "Modified UTF-8" is the string encoding used by the JNI. It is the same as +// the legacy encoding CESU-8, but with `\0` coded on two bytes. In these +// encodings, code points requiring 4 bytes in modern UTF-8 are represented as +// two surrogates, each of which is coded on 3 bytes. +// +// This function has been designed with the following goals in mind: +// 1. The generated string should be biased towards containing ASCII characters +// as these are often the ones that affect control flow directly. +// 2. Correctly encoded data (e.g. taken from the table of recent compares) +// should be emitted unchanged. +// 3. The raw fuzzer input should be preserved as far as possible, but the +// output must always be correctly encoded. +// +// The JVM accepts string in two encodings: UTF-16 and modified UTF-8. +// Generating UTF-16 would make it harder to fulfill the first design goal and +// would potentially hinder compatibility with corpora using the much more +// widely used UTF-8 encoding, which is reasonably similar to modified UTF-8. As +// a result, this function uses modified UTF-8. +// +// See Algorithm 1 of https://arxiv.org/pdf/2010.03090.pdf for more details on +// the individual cases involved in determining the validity of a UTF-8 string. +template +std::pair FixUpModifiedUtf8(const uint8_t *data, + std::size_t max_bytes, + jint max_length) { + std::string str; + // Every character in modified UTF-8 is coded on at most six bytes. Every + // consumed byte is transformed into at most one code unit, except for the + // case of a zero byte which requires two bytes. + if (max_bytes > std::numeric_limits::max() / 2) + max_bytes = std::numeric_limits::max() / 2; + if (ascii_only) { + str.reserve( + std::min(2 * static_cast(max_length), 2 * max_bytes)); + } else { + str.reserve( + std::min(6 * static_cast(max_length), 2 * max_bytes)); + } + + Utf8GenerationState state = Utf8GenerationState::LeadingByte_Generic; + const uint8_t *pos = data; + const auto data_end = data + max_bytes; + for (std::size_t length = 0; length < max_length && pos != data_end; ++pos) { + uint8_t c = *pos; + if (ascii_only) { + // Clamp to 7-bit ASCII range. + c &= 0x7Fu; + } + // Fix up c or previously read bytes according to the value of c and the + // current state. In the end, add the fixed up code unit c to the string. + // Exception: The zero character has to be coded on two bytes and is the + // only case in which an iteration of the loop adds two code units. + switch (state) { + case Utf8GenerationState::LeadingByte_Generic: { + switch (ascii_only ? 0 : countl_one(c)) { + case 0: { + // valid - 1-byte code point (ASCII) + // The zero character has to be coded on two bytes in modified + // UTF-8. + if (c == 0) { + str += static_cast(kTwoByteZeroLeadingByte); + c = kTwoByteZeroContinuationByte; + } else if (stop_on_backslash && c == '\\') { + state = Utf8GenerationState::LeadingByte_AfterBackslash; + // The slash either signals the end of the string or is skipped, + // so don't append anything. + continue; + } + // Remain in state LeadingByte. + ++length; + break; + } + case 1: { + // invalid - continuation byte at leader byte position + // Fix it up to be of the form 0b110XXXXX and fall through to the + // case of a 2-byte sequence. + c |= 1u << 6u; + c &= ~(1u << 5u); + [[fallthrough]]; + } + case 2: { + // (most likely) valid - start of a 2-byte sequence + // ASCII characters must be coded on a single byte, so we must + // ensure that the lower two bits combined with the six non-header + // bits of the following byte do not form a 7-bit ASCII value. This + // could only be the case if at most the lowest bit is set. + if ((c & 0b00011110u) == 0) { + state = Utf8GenerationState::ContinuationByte_LowLeadingByte; + } else { + state = Utf8GenerationState::ContinuationByte_Generic; + } + break; + } + // The default case falls through to the case of three leading ones + // coming right after. + default: { + // invalid - at least four leading ones + // In the case of exactly four leading ones, this would be valid + // UTF-8, but is not valid in the JVM's modified UTF-8 encoding. + // Fix it up by clearing the fourth leading one and falling through + // to the 3-byte case. + c &= ~(1u << 4u); + [[fallthrough]]; + } + case 3: { + // valid - start of a 3-byte sequence + if (c == kThreeByteLowLeadingByte) { + state = Utf8GenerationState::FirstContinuationByte_LowLeadingByte; + } else if (c == kSurrogateLeadingByte) { + state = Utf8GenerationState:: + FirstContinuationByte_SurrogateLeadingByte; + } else { + state = Utf8GenerationState::FirstContinuationByte_Generic; + } + break; + } + } + break; + } + case Utf8GenerationState::LeadingByte_AfterBackslash: { + if (c != '\\') { + // Mark the current byte as consumed. + ++pos; + goto done; + } + // A double backslash is consumed as a single one. As we skipped the + // first one, emit the second one as usual. + state = Utf8GenerationState::LeadingByte_Generic; + ++length; + break; + } + case Utf8GenerationState::ContinuationByte_LowLeadingByte: { + ForceContinuationByte(c); + // Preserve the zero character, which is coded on two bytes in modified + // UTF-8. In all other cases ensure that we are not incorrectly encoding + // an ASCII character on two bytes by setting the eigth least + // significant bit of the encoded value (second least significant bit of + // the leading byte). + auto previous_c = static_cast(str.back()); + if (previous_c != kTwoByteZeroLeadingByte || + c != kTwoByteZeroContinuationByte) { + str.back() = static_cast(previous_c | (1u << 1u)); + } + state = Utf8GenerationState::LeadingByte_Generic; + ++length; + break; + } + case Utf8GenerationState::ContinuationByte_Generic: { + ForceContinuationByte(c); + state = Utf8GenerationState::LeadingByte_Generic; + ++length; + break; + } + case Utf8GenerationState::FirstContinuationByte_LowLeadingByte: { + ForceContinuationByte(c); + // Ensure that the current code point could not have been coded on two + // bytes. As two bytes encode up to 11 bits and three bytes encode up + // to 16 bits, we thus have to make it such that the five highest bits + // are not all zero. Four of these bits are the non-header bits of the + // leader byte. Thus, set the highest non-header bit in this byte (fifth + // highest in the encoded value). + c |= 1u << 5u; + state = Utf8GenerationState::SecondContinuationByte_Generic; + break; + } + case Utf8GenerationState::FirstContinuationByte_SurrogateLeadingByte: { + ForceContinuationByte(c); + if (c & (1u << 5u)) { + // Start with a high surrogate (0xD800-0xDBFF). c contains the second + // byte and the first two bits of the third byte. The first two bits + // of this second byte are fixed to 10 (in 0x8-0xB). + c |= 1u << 5u; + c &= ~(1u << 4u); + // The high surrogate must be followed by a low surrogate. + state = Utf8GenerationState::SecondContinuationByte_HighSurrogate; + } else { + state = Utf8GenerationState::SecondContinuationByte_Generic; + } + break; + } + case Utf8GenerationState::FirstContinuationByte_Generic: { + ForceContinuationByte(c); + state = Utf8GenerationState::SecondContinuationByte_Generic; + break; + } + case Utf8GenerationState::SecondContinuationByte_HighSurrogate: { + ForceContinuationByte(c); + state = Utf8GenerationState::LeadingByte_LowSurrogate; + ++length; + break; + } + case Utf8GenerationState::SecondContinuationByte_LowSurrogate: + case Utf8GenerationState::SecondContinuationByte_Generic: { + ForceContinuationByte(c); + state = Utf8GenerationState::LeadingByte_Generic; + ++length; + break; + } + case Utf8GenerationState::LeadingByte_LowSurrogate: { + // We have to emit a low surrogate leading byte, which is a fixed value. + // We still consume a byte from the input to make fuzzer changes more + // stable and preserve valid surrogate pairs picked up from e.g. the + // table of recent compares. + c = kSurrogateLeadingByte; + state = Utf8GenerationState::FirstContinuationByte_LowSurrogate; + break; + } + case Utf8GenerationState::FirstContinuationByte_LowSurrogate: { + ForceContinuationByte(c); + // Low surrogates are code points in the range 0xDC00-0xDFFF. c contains + // the second byte and the first two bits of the third byte. The first + // two bits of this second byte are fixed to 11 (in 0xC-0xF). + c |= (1u << 5u) | (1u << 4u); + // The second continuation byte of a low surrogate is not restricted, + // but we need to track it differently to allow for correct backtracking + // if it isn't completed. + state = Utf8GenerationState::SecondContinuationByte_LowSurrogate; + break; + } + } + str += static_cast(c); + } + + // Backtrack the current incomplete character. + switch (state) { + case Utf8GenerationState::SecondContinuationByte_LowSurrogate: + str.pop_back(); + [[fallthrough]]; + case Utf8GenerationState::FirstContinuationByte_LowSurrogate: + str.pop_back(); + [[fallthrough]]; + case Utf8GenerationState::LeadingByte_LowSurrogate: + str.pop_back(); + [[fallthrough]]; + case Utf8GenerationState::SecondContinuationByte_Generic: + case Utf8GenerationState::SecondContinuationByte_HighSurrogate: + str.pop_back(); + [[fallthrough]]; + case Utf8GenerationState::ContinuationByte_Generic: + case Utf8GenerationState::ContinuationByte_LowLeadingByte: + case Utf8GenerationState::FirstContinuationByte_Generic: + case Utf8GenerationState::FirstContinuationByte_LowLeadingByte: + case Utf8GenerationState::FirstContinuationByte_SurrogateLeadingByte: + str.pop_back(); + [[fallthrough]]; + case Utf8GenerationState::LeadingByte_Generic: + case Utf8GenerationState::LeadingByte_AfterBackslash: + // No backtracking required. + break; + } + +done: + return std::make_pair(str, pos - data); +} +} // namespace + +namespace jazzer { +// Exposed for testing only. +std::pair FixUpModifiedUtf8(const uint8_t *data, + std::size_t max_bytes, + jint max_length, + bool ascii_only, + bool stop_on_backslash) { + if (ascii_only) { + if (stop_on_backslash) { + return ::FixUpModifiedUtf8(data, max_bytes, max_length); + } else { + return ::FixUpModifiedUtf8(data, max_bytes, max_length); + } + } else { + if (stop_on_backslash) { + return ::FixUpModifiedUtf8(data, max_bytes, max_length); + } else { + return ::FixUpModifiedUtf8(data, max_bytes, max_length); + } + } +} +} // namespace jazzer + +namespace { +jstring ConsumeStringInternal(JNIEnv &env, jint max_length, bool ascii_only, + bool stop_on_backslash) { + if (max_length < 0) { + ThrowIllegalArgumentException(env, "maxLength must not be negative"); + return nullptr; + } + + if (max_length == 0 || gRemainingBytes == 0) return env.NewStringUTF(""); + + if (gRemainingBytes == 1) { + Advance(1); + return env.NewStringUTF(""); + } + + std::size_t max_bytes = gRemainingBytes; + std::string str; + std::size_t consumed_bytes; + std::tie(str, consumed_bytes) = jazzer::FixUpModifiedUtf8( + gDataPtr, max_bytes, max_length, ascii_only, stop_on_backslash); + Advance(consumed_bytes); + return env.NewStringUTF(str.c_str()); +} + +jstring JNICALL ConsumeAsciiString(JNIEnv &env, jobject self, jint max_length) { + return ConsumeStringInternal(env, max_length, true, true); +} + +jstring JNICALL ConsumeString(JNIEnv &env, jobject self, jint max_length) { + return ConsumeStringInternal(env, max_length, false, true); +} + +jstring JNICALL ConsumeRemainingAsAsciiString(JNIEnv &env, jobject self) { + return ConsumeStringInternal(env, std::numeric_limits::max(), true, + false); +} + +jstring JNICALL ConsumeRemainingAsString(JNIEnv &env, jobject self) { + return ConsumeStringInternal(env, std::numeric_limits::max(), false, + false); +} + +std::size_t RemainingBytes(JNIEnv &env, jobject self) { + return gRemainingBytes; +} + +const JNINativeMethod kFuzzedDataMethods[]{ + {(char *)"consumeBoolean", (char *)"()Z", (void *)&ConsumeBool}, + {(char *)"consumeByte", (char *)"()B", (void *)&ConsumeIntegral}, + {(char *)"consumeByte", (char *)"(BB)B", + (void *)&ConsumeIntegralInRange}, + {(char *)"consumeShort", (char *)"()S", (void *)&ConsumeIntegral}, + {(char *)"consumeShort", (char *)"(SS)S", + (void *)&ConsumeIntegralInRange}, + {(char *)"consumeInt", (char *)"()I", (void *)&ConsumeIntegral}, + {(char *)"consumeInt", (char *)"(II)I", + (void *)&ConsumeIntegralInRange}, + {(char *)"consumeLong", (char *)"()J", (void *)&ConsumeIntegral}, + {(char *)"consumeLong", (char *)"(JJ)J", + (void *)&ConsumeIntegralInRange}, + {(char *)"consumeFloat", (char *)"()F", (void *)&ConsumeFloat}, + {(char *)"consumeRegularFloat", (char *)"()F", + (void *)&ConsumeRegularFloat}, + {(char *)"consumeRegularFloat", (char *)"(FF)F", + (void *)&ConsumeFloatInRange}, + {(char *)"consumeProbabilityFloat", (char *)"()F", + (void *)&ConsumeProbability}, + {(char *)"consumeDouble", (char *)"()D", (void *)&ConsumeFloat}, + {(char *)"consumeRegularDouble", (char *)"()D", + (void *)&ConsumeRegularFloat}, + {(char *)"consumeRegularDouble", (char *)"(DD)D", + (void *)&ConsumeFloatInRange}, + {(char *)"consumeProbabilityDouble", (char *)"()D", + (void *)&ConsumeProbability}, + {(char *)"consumeChar", (char *)"()C", (void *)&ConsumeChar}, + {(char *)"consumeChar", (char *)"(CC)C", + (void *)&ConsumeIntegralInRange}, + {(char *)"consumeCharNoSurrogates", (char *)"()C", + (void *)&ConsumeCharNoSurrogates}, + {(char *)"consumeAsciiString", (char *)"(I)Ljava/lang/String;", + (void *)&ConsumeAsciiString}, + {(char *)"consumeRemainingAsAsciiString", (char *)"()Ljava/lang/String;", + (void *)&ConsumeRemainingAsAsciiString}, + {(char *)"consumeString", (char *)"(I)Ljava/lang/String;", + (void *)&ConsumeString}, + {(char *)"consumeRemainingAsString", (char *)"()Ljava/lang/String;", + (void *)&ConsumeRemainingAsString}, + {(char *)"consumeBooleans", (char *)"(I)[Z", + (void *)&ConsumeIntegralArray}, + {(char *)"consumeBytes", (char *)"(I)[B", + (void *)&ConsumeIntegralArray}, + {(char *)"consumeShorts", (char *)"(I)[S", + (void *)&ConsumeIntegralArray}, + {(char *)"consumeInts", (char *)"(I)[I", + (void *)&ConsumeIntegralArray}, + {(char *)"consumeLongs", (char *)"(I)[J", + (void *)&ConsumeIntegralArray}, + {(char *)"consumeRemainingAsBytes", (char *)"()[B", + (void *)&ConsumeRemainingAsArray}, + {(char *)"remainingBytes", (char *)"()I", (void *)&RemainingBytes}, +}; +const jint kNumFuzzedDataMethods = + sizeof(kFuzzedDataMethods) / sizeof(kFuzzedDataMethods[0]); +} // namespace + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_FuzzedDataProviderImpl_nativeInit( + JNIEnv *env, jclass clazz) { + env->RegisterNatives(clazz, kFuzzedDataMethods, kNumFuzzedDataMethods); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_FuzzedDataProviderImpl_reset( + JNIEnv *env, jclass clazz) { + gDataPtr = gFuzzerInputStart; + gRemainingBytes = gFuzzerInputSize; +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_FuzzedDataProviderImpl_feed( + JNIEnv *env, jclass, jbyteArray input) { + // This line is why this function must not be used if FeedFuzzedDataProvider + // is also called from native code. + delete[] gFuzzerInputStart; + + std::size_t size = env->GetArrayLength(input); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->FatalError("Failed to get length of input"); + } + auto *data = static_cast(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(data)); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->FatalError("Failed to copy input"); + } + jazzer::FeedFuzzedDataProvider(data, size); +} + +namespace jazzer { +void FeedFuzzedDataProvider(const uint8_t *data, std::size_t size) { + gDataPtr = data; + gRemainingBytes = size; + + gFuzzerInputStart = data; + gFuzzerInputSize = size; +} +} // namespace jazzer diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.h b/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.h new file mode 100644 index 00000000..c9b72546 --- /dev/null +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.h @@ -0,0 +1,41 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jazzer { + +constexpr char kFuzzedDataProviderImplClass[] = + "com/code_intelligence/jazzer/runtime/FuzzedDataProviderImpl"; + +// Feed the FuzzedDataProvider with a new data buffer. The buffer is accessed +// by native code and not copied into the JVM, so this is cheap to call. +void FeedFuzzedDataProvider(const uint8_t *data, std::size_t size); +} // namespace jazzer diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/jazzer_fuzzer_callbacks.cpp b/driver/src/main/native/com/code_intelligence/jazzer/driver/jazzer_fuzzer_callbacks.cpp new file mode 100644 index 00000000..01af1229 --- /dev/null +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/jazzer_fuzzer_callbacks.cpp @@ -0,0 +1,193 @@ +// Copyright 2022 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 + +#include +#include + +#include "com_code_intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks.h" +#include "sanitizer_hooks_with_pc.h" + +namespace { + +extern "C" { +void __sanitizer_weak_hook_compare_bytes(void *caller_pc, const void *s1, + const void *s2, std::size_t n1, + std::size_t n2, int result); +void __sanitizer_weak_hook_memmem(void *called_pc, const void *s1, size_t len1, + const void *s2, size_t len2, void *result); +void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2); +void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2); + +void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases); + +void __sanitizer_cov_trace_div4(uint32_t val); +void __sanitizer_cov_trace_div8(uint64_t val); + +void __sanitizer_cov_trace_gep(uintptr_t idx); +} + +inline __attribute__((always_inline)) void *idToPc(jint id) { + return reinterpret_cast(static_cast(id)); +} +} // namespace + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceStrstr0( + JNIEnv *env, jclass cls, jbyteArray needle, jint id) { + jint needle_length = env->GetArrayLength(needle); + auto *needle_native = + static_cast(env->GetPrimitiveArrayCritical(needle, nullptr)); + __sanitizer_weak_hook_memmem(idToPc(id), nullptr, 0, needle_native, + needle_length, nullptr); + env->ReleasePrimitiveArrayCritical(needle, needle_native, JNI_ABORT); +} + +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceStrstr0( + jint needle_length, jbyte *needle_native, jint id) { + __sanitizer_weak_hook_memmem(idToPc(id), nullptr, 0, needle_native, + needle_length, nullptr); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceMemcmp( + JNIEnv *env, jclass cls, jbyteArray b1, jbyteArray b2, jint result, + jint id) { + jint b1_length = env->GetArrayLength(b1); + jint b2_length = env->GetArrayLength(b2); + auto *b1_native = + static_cast(env->GetPrimitiveArrayCritical(b1, nullptr)); + auto *b2_native = + static_cast(env->GetPrimitiveArrayCritical(b2, nullptr)); + __sanitizer_weak_hook_compare_bytes(idToPc(id), b1_native, b2_native, + b1_length, b2_length, result); + env->ReleasePrimitiveArrayCritical(b1, b1_native, JNI_ABORT); + env->ReleasePrimitiveArrayCritical(b2, b2_native, JNI_ABORT); +} + +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceMemcmp( + jint b1_length, jbyte *b1, jint b2_length, jbyte *b2, jint result, + jint id) { + __sanitizer_weak_hook_compare_bytes(idToPc(id), b1, b2, b1_length, b2_length, + result); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpLong( + JNIEnv *env, jclass cls, jlong value1, jlong value2, jint id) { + __sanitizer_cov_trace_cmp8_with_pc(idToPc(id), value1, value2); +} + +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpLong( + jlong value1, jlong value2, jint id) { + __sanitizer_cov_trace_cmp8_with_pc(idToPc(id), value1, value2); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpInt( + JNIEnv *env, jclass cls, jint value1, jint value2, jint id) { + __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); +} + +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceCmpInt( + jint value1, jint value2, jint id) { + __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceConstCmpInt( + JNIEnv *env, jclass cls, jint value1, jint value2, jint id) { + __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); +} + +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceConstCmpInt( + jint value1, jint value2, jint id) { + __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitch( + JNIEnv *env, jclass cls, jlong switch_value, + jlongArray libfuzzer_case_values, jint id) { + auto *case_values = static_cast( + env->GetPrimitiveArrayCritical(libfuzzer_case_values, nullptr)); + __sanitizer_cov_trace_switch_with_pc( + idToPc(id), switch_value, reinterpret_cast(case_values)); + env->ReleasePrimitiveArrayCritical(libfuzzer_case_values, case_values, + JNI_ABORT); +} + +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceSwitch( + jlong switch_value, jint libfuzzer_case_values_length, jlong *case_values, + jint id) { + __sanitizer_cov_trace_switch_with_pc( + idToPc(id), switch_value, reinterpret_cast(case_values)); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivLong( + JNIEnv *env, jclass cls, jlong value, jint id) { + __sanitizer_cov_trace_div8_with_pc(idToPc(id), value); +} + +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivLong( + jlong value, jint id) { + __sanitizer_cov_trace_div8_with_pc(idToPc(id), value); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivInt( + JNIEnv *env, jclass cls, jint value, jint id) { + __sanitizer_cov_trace_div4_with_pc(idToPc(id), value); +} + +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceDivInt( + jint value, jint id) { + __sanitizer_cov_trace_div4_with_pc(idToPc(id), value); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceGep( + JNIEnv *env, jclass cls, jlong idx, jint id) { + __sanitizer_cov_trace_gep_with_pc(idToPc(id), static_cast(idx)); +} + +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_traceGep( + jlong idx, jint id) { + __sanitizer_cov_trace_gep_with_pc(idToPc(id), static_cast(idx)); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_tracePcIndir( + JNIEnv *env, jclass cls, jint caller_id, jint callee_id) { + __sanitizer_cov_trace_pc_indir_with_pc(idToPc(caller_id), + static_cast(callee_id)); +} + +extern "C" [[maybe_unused]] JNIEXPORT void JNICALL +JavaCritical_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_tracePcIndir( + jint caller_id, jint callee_id) { + __sanitizer_cov_trace_pc_indir_with_pc(idToPc(caller_id), + static_cast(callee_id)); +} diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/libfuzzer_callbacks.cpp b/driver/src/main/native/com/code_intelligence/jazzer/driver/libfuzzer_callbacks.cpp new file mode 100644 index 00000000..a6df806b --- /dev/null +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/libfuzzer_callbacks.cpp @@ -0,0 +1,128 @@ +// 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 + +#include +#include +#include +#include +#include + +#include "absl/strings/match.h" +#include "absl/strings/str_split.h" +#include "com_code_intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks.h" + +namespace { +bool is_using_native_libraries = false; +std::once_flag ignore_list_flag; +std::vector> ignore_for_interception_ranges; + +/** + * Adds the address ranges of executable segmentes of the library lib_name to + * the ignorelist for C standard library function interception (strcmp, memcmp, + * ...). + */ +void ignoreLibraryForInterception(const std::string &lib_name) { + std::ifstream loaded_libs("/proc/self/maps"); + if (!loaded_libs) { + // This early exit is taken e.g. on macOS, where /proc does not exist. + return; + } + std::string line; + while (std::getline(loaded_libs, line)) { + if (!absl::StrContains(line, lib_name)) continue; + // clang-format off + // A typical line looks as follows: + // 7f15356c9000-7f1536367000 r-xp 0020d000 fd:01 19275673 /usr/lib/jvm/java-15-openjdk-amd64/lib/server/libjvm.so + // clang-format on + std::vector parts = + absl::StrSplit(line, ' ', absl::SkipEmpty()); + if (parts.size() != 6) { + std::cout << "ERROR: Invalid format for /proc/self/maps\n" + << line << std::endl; + exit(1); + } + // Skip non-executable address rang"s. + if (!absl::StrContains(parts[1], "x")) continue; + std::string_view range_str = parts[0]; + std::vector range = absl::StrSplit(range_str, "-"); + if (range.size() != 2) { + std::cout + << "ERROR: Unexpected address range format in /proc/self/maps line: " + << range_str << std::endl; + exit(1); + } + std::size_t pos; + auto start = std::stoull(range[0], &pos, 16); + if (pos != range[0].size()) { + std::cout + << "ERROR: Unexpected address range format in /proc/self/maps line: " + << range_str << std::endl; + exit(1); + } + auto end = std::stoull(range[1], &pos, 16); + if (pos != range[0].size()) { + std::cout + << "ERROR: Unexpected address range format in /proc/self/maps line: " + << range_str << std::endl; + exit(1); + } + ignore_for_interception_ranges.emplace_back(start, end); + } +} + +const std::vector kLibrariesToIgnoreForInterception = { + // The driver executable itself can be treated just like a library. + "jazzer_driver", "libinstrument.so", "libjava.so", + "libjimage.so", "libjli.so", "libjvm.so", + "libnet.so", "libverify.so", "libzip.so", +}; +} // namespace + +extern "C" [[maybe_unused]] bool __sanitizer_weak_is_relevant_pc( + void *caller_pc) { + // If the fuzz target is not using native libraries, calls to strcmp, memcmp, + // etc. should never be intercepted. The values reported if they were at best + // duplicate the values received from our bytecode instrumentation and at + // worst pollute the table of recent compares with string internal to the JDK. + if (!is_using_native_libraries) return false; + // If the fuzz target is using native libraries, intercept calls only if they + // don't originate from those address ranges that are known to belong to the + // JDK. + return std::none_of(ignore_for_interception_ranges.cbegin(), + ignore_for_interception_ranges.cend(), + [caller_pc](const auto &range) { + uintptr_t start; + uintptr_t end; + std::tie(start, end) = range; + auto address = reinterpret_cast(caller_pc); + return start <= address && address <= end; + }); +} + +[[maybe_unused]] void +Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_handleLibraryLoad( + JNIEnv *env, jclass cls) { + std::call_once(ignore_list_flag, [] { + std::cout << "INFO: detected a native library load, enabling interception " + "for libc functions" + << std::endl; + for (const auto &lib_name : kLibrariesToIgnoreForInterception) + ignoreLibraryForInterception(lib_name); + // Enable the ignore list after it has been populated since vector is not + // thread-safe with respect to concurrent writes and reads. + is_using_native_libraries = true; + }); +} diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/sanitizer_hooks_with_pc.h b/driver/src/main/native/com/code_intelligence/jazzer/driver/sanitizer_hooks_with_pc.h new file mode 100644 index 00000000..be655adb --- /dev/null +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/sanitizer_hooks_with_pc.h @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#pragma once + +#include + +// This file declares variants of the libFuzzer compare, division, switch and +// gep hooks that accept an additional caller_pc argument that can be used to +// pass a custom value that is recorded as the caller's instruction pointer +// ("program counter"). This allows synthetic program counters obtained from +// Java coverage information to be used with libFuzzer's value profile, with +// which it records detailed information about the result of compares and +// associates it with particular coverage locations. +// +// Note: Only the lower 9 bits of the caller_pc argument are used by libFuzzer. +#ifdef __cplusplus +extern "C" { +#endif +void __sanitizer_cov_trace_cmp4_with_pc(void *caller_pc, uint32_t arg1, + uint32_t arg2); +void __sanitizer_cov_trace_cmp8_with_pc(void *caller_pc, uint64_t arg1, + uint64_t arg2); + +void __sanitizer_cov_trace_switch_with_pc(void *caller_pc, uint64_t val, + uint64_t *cases); + +void __sanitizer_cov_trace_div4_with_pc(void *caller_pc, uint32_t val); +void __sanitizer_cov_trace_div8_with_pc(void *caller_pc, uint64_t val); + +void __sanitizer_cov_trace_gep_with_pc(void *caller_pc, uintptr_t idx); + +void __sanitizer_cov_trace_pc_indir_with_pc(void *caller_pc, uintptr_t callee); +#ifdef __cplusplus +} +#endif diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/sanitizer_symbols.cpp b/driver/src/main/native/com/code_intelligence/jazzer/driver/sanitizer_symbols.cpp new file mode 100644 index 00000000..abc5f04e --- /dev/null +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/sanitizer_symbols.cpp @@ -0,0 +1,26 @@ +// 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. + +// Suppress libFuzzer warnings about missing sanitizer methods in non-sanitizer +// builds. +extern "C" [[maybe_unused]] int __sanitizer_acquire_crash_state() { return 1; } + +namespace jazzer { +void DumpJvmStackTraces(); +} + +// Dump a JVM stack trace on timeouts. +extern "C" [[maybe_unused]] void __sanitizer_print_stack_trace() { + jazzer::DumpJvmStackTraces(); +} diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/trigger_driver_hooks_load.cpp b/driver/src/main/native/com/code_intelligence/jazzer/driver/trigger_driver_hooks_load.cpp new file mode 100644 index 00000000..8e6d19ab --- /dev/null +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/trigger_driver_hooks_load.cpp @@ -0,0 +1,50 @@ +// Copyright 2022 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 +#include + +#include + +// The native driver binary, if used, forwards all calls to native libFuzzer +// hooks such as __sanitizer_cov_trace_cmp8 to the Jazzer JNI library. In order +// to load the hook symbols when the library is ready, it needs to be passed a +// handle - the JVM loads libraries with RTLD_LOCAL and thus their symbols +// wouldn't be found as part of the global lookup procedure. +jint JNI_OnLoad(JavaVM *, void *) { + Dl_info info; + + if (!dladdr(reinterpret_cast(&JNI_OnLoad), &info) || + !info.dli_fname) { + fprintf(stderr, "Failed to determine our dli_fname\n"); + abort(); + } + + void *handle = dlopen(info.dli_fname, RTLD_NOLOAD | RTLD_LAZY); + if (handle == nullptr) { + fprintf(stderr, "Failed to dlopen self: %s\n", dlerror()); + abort(); + } + + void *register_hooks = dlsym(RTLD_DEFAULT, "jazzer_initialize_native_hooks"); + // We may be running without the native driver, so not finding this method is + // an expected error. + if (register_hooks) { + reinterpret_cast(register_hooks)(handle); + } + + dlclose(handle); + + return JNI_VERSION_1_8; +} diff --git a/driver/src/test/java/com/code_intelligence/jazzer/driver/BUILD.bazel b/driver/src/test/java/com/code_intelligence/jazzer/driver/BUILD.bazel index 9da20d54..04119700 100644 --- a/driver/src/test/java/com/code_intelligence/jazzer/driver/BUILD.bazel +++ b/driver/src/test/java/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -7,10 +7,7 @@ java_test( "//agent/src/main/java/com/code_intelligence/jazzer/api", "//agent/src/main/java/com/code_intelligence/jazzer/runtime:coverage_map", "//agent/src/main/java/com/code_intelligence/jazzer/runtime:unsafe_provider", - "//agent/src/test/java/com/code_intelligence/jazzer:MockDriver", "//driver/src/main/java/com/code_intelligence/jazzer/driver:fuzz_target_runner", - "//driver/src/test/native/com/code_intelligence/jazzer/driver:fuzz_target_runner_mock", - "@fmeum_rules_jni//jni/tools/native_loader", ], ) diff --git a/driver/src/test/java/com/code_intelligence/jazzer/driver/FuzzTargetRunnerTest.java b/driver/src/test/java/com/code_intelligence/jazzer/driver/FuzzTargetRunnerTest.java index 7f6894c5..d8f048e5 100644 --- a/driver/src/test/java/com/code_intelligence/jazzer/driver/FuzzTargetRunnerTest.java +++ b/driver/src/test/java/com/code_intelligence/jazzer/driver/FuzzTargetRunnerTest.java @@ -16,11 +16,9 @@ package com.code_intelligence.jazzer.driver; -import com.code_intelligence.jazzer.MockDriver; import com.code_intelligence.jazzer.api.Jazzer; import com.code_intelligence.jazzer.runtime.CoverageMap; import com.code_intelligence.jazzer.runtime.UnsafeProvider; -import com.github.fmeum.rules_jni.RulesJni; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.nio.charset.StandardCharsets; @@ -31,11 +29,6 @@ import java.util.stream.Stream; import sun.misc.Unsafe; public class FuzzTargetRunnerTest { - static { - MockDriver.load(); - RulesJni.loadLibrary("fuzz_target_runner_mock", FuzzTargetRunnerTest.class); - } - private static final Pattern DEDUP_TOKEN_PATTERN = Pattern.compile("(?m)^DEDUP_TOKEN: ([0-9a-f]{16})$"); private static final Unsafe UNSAFE = UnsafeProvider.getUnsafe(); diff --git a/driver/src/test/native/com/code_intelligence/jazzer/driver/BUILD.bazel b/driver/src/test/native/com/code_intelligence/jazzer/driver/BUILD.bazel deleted file mode 100644 index 30d64fe7..00000000 --- a/driver/src/test/native/com/code_intelligence/jazzer/driver/BUILD.bazel +++ /dev/null @@ -1,11 +0,0 @@ -load("@fmeum_rules_jni//jni:defs.bzl", "cc_jni_library") - -cc_jni_library( - name = "fuzz_target_runner_mock", - testonly = True, - srcs = ["fuzz_target_runner_mock.cpp"], - visibility = ["//driver/src/test:__subpackages__"], - deps = [ - "//driver/src/main/java/com/code_intelligence/jazzer/driver:fuzz_target_runner.hdrs", - ], -) diff --git a/driver/src/test/native/com/code_intelligence/jazzer/driver/fuzz_target_runner_mock.cpp b/driver/src/test/native/com/code_intelligence/jazzer/driver/fuzz_target_runner_mock.cpp deleted file mode 100644 index d5178bf0..00000000 --- a/driver/src/test/native/com/code_intelligence/jazzer/driver/fuzz_target_runner_mock.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2022 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 - -#include "com_code_intelligence_jazzer_driver_FuzzTargetRunner.h" - -void Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner_printCrashingInput( - JNIEnv *, jclass) {} - -void Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner__1Exit( - JNIEnv *, jclass, jint exit_code) { - _Exit(exit_code); -} diff --git a/format.sh b/format.sh index da310c63..f1440f1b 100755 --- a/format.sh +++ b/format.sh @@ -1,5 +1,5 @@ # C++ & Java -find -name '*.cpp' -o -name '*.h' -o -name '*.java' | xargs clang-format-13 -i +find -name '*.cpp' -o -name '*.c' -o -name '*.h' -o -name '*.java' | xargs clang-format-13 -i # Kotlin # curl -sSLO https://github.com/pinterest/ktlint/releases/download/0.42.1/ktlint && chmod a+x ktlint diff --git a/tests/src/test/native/com/example/BUILD.bazel b/tests/src/test/native/com/example/BUILD.bazel index cce29a07..93b886a8 100644 --- a/tests/src/test/native/com/example/BUILD.bazel +++ b/tests/src/test/native/com/example/BUILD.bazel @@ -4,10 +4,23 @@ cc_jni_library( name = "native_value_profile_fuzzer", srcs = ["native_value_profile_fuzzer.cpp"], copts = [ - "-fsanitize=fuzzer-no-link", + "-fsanitize=fuzzer-no-link,address", + "-fno-sanitize-blacklist", + ], + defines = [ + # Workaround for Windows build failures with VS 2022: + # "lld-link: error: /INFERASANLIBS is not allowed in .drectve" + # https://github.com/llvm/llvm-project/issues/56300#issuecomment-1214313292 + "_DISABLE_STRING_ANNOTATION=1", + "_DISABLE_VECTOR_ANNOTATION=1", ], linkopts = select({ "//:clang_on_linux": ["-fuse-ld=lld"], + "@platforms//os:windows": [ + # Windows requires all symbols that should be imported from the main + # executable to be defined by an import lib. + "/wholearchive:clang_rt.asan_dll_thunk-x86_64.lib", + ], "//conditions:default": [], }), visibility = ["//tests:__pkg__"], -- cgit v1.2.3 From a87720c819b446542b0fdab00c47b66db5dbc874 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 16 Aug 2022 18:38:40 +0200 Subject: driver: Remove unused sanitizer hook declarations --- .../code_intelligence/jazzer/driver/jazzer_fuzzer_callbacks.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/jazzer_fuzzer_callbacks.cpp b/driver/src/main/native/com/code_intelligence/jazzer/driver/jazzer_fuzzer_callbacks.cpp index 01af1229..8764aaaa 100644 --- a/driver/src/main/native/com/code_intelligence/jazzer/driver/jazzer_fuzzer_callbacks.cpp +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/jazzer_fuzzer_callbacks.cpp @@ -28,15 +28,6 @@ void __sanitizer_weak_hook_compare_bytes(void *caller_pc, const void *s1, std::size_t n2, int result); void __sanitizer_weak_hook_memmem(void *called_pc, const void *s1, size_t len1, const void *s2, size_t len2, void *result); -void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2); -void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2); - -void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases); - -void __sanitizer_cov_trace_div4(uint32_t val); -void __sanitizer_cov_trace_div8(uint64_t val); - -void __sanitizer_cov_trace_gep(uintptr_t idx); } inline __attribute__((always_inline)) void *idToPc(jint id) { -- cgit v1.2.3 From 485f8af313bf6433a064d8aa1a50bf64dae503f5 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 16 Aug 2022 21:33:03 +0200 Subject: driver: Restore compatibility with C++11 With most of the driver rewritten in Java, we were barely using any C++14/17 features anymore. By reverting to C++11, we get better compatibility for our upcoming Maven version of Jazzer and a simplified build structure by no longer having to transition our cc_library targets. --- .bazelrc | 6 -- bazel/cc.bzl | 84 ---------------------- driver/BUILD.bazel | 17 ++--- driver/jazzer_main.cpp | 9 +-- driver/jvm_tooling.cpp | 8 +-- driver/jvm_tooling.h | 2 +- driver/jvm_tooling_test.cpp | 4 +- .../jazzer/driver/coverage_tracker.cpp | 6 +- .../jazzer/driver/fuzz_target_runner.h | 9 --- .../jazzer/driver/fuzzed_data_provider.cpp | 2 + .../jazzer/driver/libfuzzer_callbacks.cpp | 27 +++---- 11 files changed, 36 insertions(+), 138 deletions(-) delete mode 100644 bazel/cc.bzl diff --git a/.bazelrc b/.bazelrc index ac14c0d6..78b0d871 100644 --- a/.bazelrc +++ b/.bazelrc @@ -4,12 +4,6 @@ build --enable_platform_specific_config build -c opt # C/C++ -# Only relevant for tests and their dependencies. Everything that external -# repositories can reference must build without this, e.g., by using a -# transition. -build:linux --cxxopt='-std=c++17' -build:macos --cxxopt='-std=c++17' -build:windows --cxxopt='/std:c++17' common --repo_env=CC=clang build --incompatible_enable_cc_toolchain_resolution # Requires a relatively modern clang. diff --git a/bazel/cc.bzl b/bazel/cc.bzl deleted file mode 100644 index 8cd1c8b2..00000000 --- a/bazel/cc.bzl +++ /dev/null @@ -1,84 +0,0 @@ -# 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. - -def _add_cxxopt_std_17_impl(settings, attr): - STD_CXX_17_CXXOPTS = ["/std:c++17" if attr.is_windows else "-std=c++17"] - return { - "//command_line_option:cxxopt": settings["//command_line_option:cxxopt"] + STD_CXX_17_CXXOPTS, - } - -_add_cxxopt_std_17 = transition( - implementation = _add_cxxopt_std_17_impl, - inputs = [ - "//command_line_option:cxxopt", - ], - outputs = [ - "//command_line_option:cxxopt", - ], -) - -def _cc_17_library_impl(ctx): - library = ctx.attr.exports[0] - return [ - # Workaround for https://github.com/bazelbuild/bazel/issues/9442. - DefaultInfo( - data_runfiles = library[DefaultInfo].data_runfiles, - default_runfiles = library[DefaultInfo].default_runfiles, - files = library[DefaultInfo].files, - ), - library[CcInfo], - coverage_common.instrumented_files_info( - ctx, - dependency_attributes = ["library"], - ), - ] - -_cc_17_library = rule( - implementation = _cc_17_library_impl, - attrs = { - "is_windows": attr.bool(), - "exports": attr.label( - cfg = _add_cxxopt_std_17, - mandatory = True, - providers = [CcInfo], - ), - "_allowlist_function_transition": attr.label( - default = "@bazel_tools//tools/allowlists/function_transition_allowlist", - ), - }, - provides = [CcInfo], -) - -# A cc_library that is built with -std=c++17, including all its transitive -# dependencies. This is redundant while developing Jazzer itself as the .bazelrc -# sets this flag for all build commands, but is needed when Jazzer is included -# as an external workspace. -def cc_17_library(name, visibility = None, **kwargs): - library_name = name + "_original_do_not_use_" - kwargs.setdefault("tags", []).append("manual") - native.cc_library( - name = library_name, - visibility = ["//visibility:private"], - **kwargs - ) - - _cc_17_library( - name = name, - is_windows = select({ - "@platforms//os:windows": True, - "//conditions:default": False, - }), - exports = library_name, - visibility = visibility, - ) diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index 7c4f0964..ac285958 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -1,5 +1,4 @@ load("@fmeum_rules_jni//jni:defs.bzl", "cc_jni_library") -load("//bazel:cc.bzl", "cc_17_library") load("//bazel:compat.bzl", "SKIP_ON_WINDOWS") cc_library( @@ -89,12 +88,6 @@ cc_library( alwayslink = True, ) -cc_17_library( - name = "jazzer_main_lib", - deps = [":jazzer_main"], - alwayslink = True, -) - cc_binary( name = "jazzer_driver", data = [ @@ -106,7 +99,7 @@ cc_binary( }), linkstatic = True, visibility = ["//visibility:public"], - deps = [":jazzer_main_lib"], + deps = [":jazzer_main"], ) alias( @@ -141,7 +134,7 @@ cc_binary( }), linkstatic = True, visibility = ["//visibility:public"], - deps = [":jazzer_main_lib"] + select({ + deps = [":jazzer_main"] + select({ # There is no static ASan runtime on macOS, so link to the dynamic # runtime library if on macOS and using the toolchain. ":using_toolchain_on_osx": ["@llvm_toolchain_llvm//:macos_asan_dynamic"], @@ -177,7 +170,7 @@ cc_binary( linkstatic = True, visibility = ["//visibility:public"], deps = [ - ":jazzer_main_lib", + ":jazzer_main", ] + select({ "@platforms//os:windows": [], "//conditions:default": [":native_fuzzer_hooks"], @@ -212,6 +205,10 @@ cc_test( args = [ "--cp=jazzer/$(rootpath //driver/testdata:fuzz_target_mocks_deploy.jar)", ], + copts = select({ + "@platforms//os:windows": ["/std:c++17"], + "//conditions:default": ["-std=c++17"], + }), data = [ "//agent:jazzer_agent_deploy", "//driver/testdata:fuzz_target_mocks_deploy.jar", diff --git a/driver/jazzer_main.cpp b/driver/jazzer_main.cpp index 0697bd3e..01118813 100644 --- a/driver/jazzer_main.cpp +++ b/driver/jazzer_main.cpp @@ -23,9 +23,8 @@ #include #include -#include #include -#include +#include #include #include "absl/strings/match.h" @@ -33,8 +32,6 @@ #include "glog/logging.h" #include "jvm_tooling.h" -using namespace std::string_literals; - // Defined by glog DECLARE_bool(log_prefix); @@ -137,7 +134,7 @@ int main(int argc, char **argv) { // by taking only those with a leading double dash. std::vector our_args = {*argv}; std::copy_if(argv, argv_end, std::back_inserter(our_args), - [](const auto arg) { + [](const std::string &arg) { return absl::StartsWith(std::string(arg), "--"); }); int our_argc = our_args.size(); @@ -153,6 +150,6 @@ int main(int argc, char **argv) { << std::endl; } - return StartLibFuzzer(std::make_unique(argv[0]), + return StartLibFuzzer(std::unique_ptr(new jazzer::JVM(argv[0])), std::vector(argv, argv_end)); } diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index 580a4b6a..267639bd 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -144,9 +144,9 @@ namespace { constexpr auto kAgentBazelRunfilesPath = "jazzer/agent/jazzer_agent_deploy.jar"; constexpr auto kAgentFileName = "jazzer_agent_deploy.jar"; -std::string_view dirFromFullPath(std::string_view path) { +std::string dirFromFullPath(const std::string &path) { const auto pos = path.rfind(kPathSeparator); - if (pos != std::string_view::npos) { + if (pos != std::string::npos) { return path.substr(0, pos); } return ""; @@ -154,7 +154,7 @@ std::string_view dirFromFullPath(std::string_view path) { // getInstrumentorAgentPath searches for the fuzzing instrumentation agent and // returns the location if it is found. Otherwise it calls exit(0). -std::string getInstrumentorAgentPath(std::string_view executable_path) { +std::string getInstrumentorAgentPath(const std::string &executable_path) { // User provided agent location takes precedence. if (!FLAGS_agent_path.empty()) { if (std::ifstream(FLAGS_agent_path).good()) return FLAGS_agent_path; @@ -252,7 +252,7 @@ std::vector splitEscaped(const std::string &str) { namespace jazzer { -JVM::JVM(std::string_view executable_path) { +JVM::JVM(const std::string &executable_path) { // combine class path from command line flags and JAVA_FUZZER_CLASSPATH env // variable std::string class_path = absl::StrFormat("-Djava.class.path=%s", FLAGS_cp); diff --git a/driver/jvm_tooling.h b/driver/jvm_tooling.h index 7edb2c31..2a4a133c 100644 --- a/driver/jvm_tooling.h +++ b/driver/jvm_tooling.h @@ -35,7 +35,7 @@ class JVM { public: // Creates a JVM instance with default options + options that were provided as // command line flags. - explicit JVM(std::string_view executable_path); + explicit JVM(const std::string &executable_path); // Destroy the running JVM instance. ~JVM(); diff --git a/driver/jvm_tooling_test.cpp b/driver/jvm_tooling_test.cpp index 916cba6c..5aceadd4 100644 --- a/driver/jvm_tooling_test.cpp +++ b/driver/jvm_tooling_test.cpp @@ -14,6 +14,8 @@ #include "jvm_tooling.h" +#include + #include "gflags/gflags.h" #include "gtest/gtest.h" #include "tools/cpp/runfiles/runfiles.h" @@ -43,7 +45,7 @@ class JvmToolingTest : public ::testing::Test { Runfiles *runfiles = Runfiles::CreateForTest(); FLAGS_cp = runfiles->Rlocation(FLAGS_cp); - jvm_ = std::make_unique("test_executable"); + jvm_ = std::unique_ptr(new JVM("test_executable")); } static void TearDownTestCase() { jvm_.reset(nullptr); } diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/coverage_tracker.cpp b/driver/src/main/native/com/code_intelligence/jazzer/driver/coverage_tracker.cpp index 47872fa9..dc8349d4 100644 --- a/driver/src/main/native/com/code_intelligence/jazzer/driver/coverage_tracker.cpp +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/coverage_tracker.cpp @@ -16,8 +16,6 @@ #include -#include -#include #include #include @@ -87,13 +85,13 @@ void CoverageTracker::RegisterNewCounters(JNIEnv &env, jint old_num_counters, [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_CoverageMap_initialize( - JNIEnv *env, jclass cls, jlong counters) { + JNIEnv *env, jclass, jlong counters) { ::jazzer::CoverageTracker::Initialize(*env, counters); } [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_CoverageMap_registerNewCounters( - JNIEnv *env, jclass cls, jint old_num_counters, jint new_num_counters) { + JNIEnv *env, jclass, jint old_num_counters, jint new_num_counters) { ::jazzer::CoverageTracker::RegisterNewCounters(*env, old_num_counters, new_num_counters); } diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzz_target_runner.h b/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzz_target_runner.h index fbc3f1e6..0e8846c0 100644 --- a/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzz_target_runner.h +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzz_target_runner.h @@ -18,16 +18,7 @@ #include -#include -#include - namespace jazzer { -/* - * Starts libFuzzer with the provided command-line arguments and runs the - * FuzzTargetRunner Java class in the provided JVM. - */ -int StartFuzzer(JNIEnv *env, int argc, char **argv); - /* * Print the stack traces of all active JVM threads. * diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.cpp b/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.cpp index f4956ac7..7c8441bb 100644 --- a/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.cpp +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.cpp @@ -47,7 +47,9 @@ #include #include +#include #include +#include #include #include diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/libfuzzer_callbacks.cpp b/driver/src/main/native/com/code_intelligence/jazzer/driver/libfuzzer_callbacks.cpp index a6df806b..a20863fa 100644 --- a/driver/src/main/native/com/code_intelligence/jazzer/driver/libfuzzer_callbacks.cpp +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/libfuzzer_callbacks.cpp @@ -14,13 +14,13 @@ #include +#include #include #include #include #include #include -#include "absl/strings/match.h" #include "absl/strings/str_split.h" #include "com_code_intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks.h" @@ -47,7 +47,7 @@ void ignoreLibraryForInterception(const std::string &lib_name) { // A typical line looks as follows: // 7f15356c9000-7f1536367000 r-xp 0020d000 fd:01 19275673 /usr/lib/jvm/java-15-openjdk-amd64/lib/server/libjvm.so // clang-format on - std::vector parts = + std::vector parts = absl::StrSplit(line, ' ', absl::SkipEmpty()); if (parts.size() != 6) { std::cout << "ERROR: Invalid format for /proc/self/maps\n" @@ -56,7 +56,7 @@ void ignoreLibraryForInterception(const std::string &lib_name) { } // Skip non-executable address rang"s. if (!absl::StrContains(parts[1], "x")) continue; - std::string_view range_str = parts[0]; + std::string range_str = parts[0]; std::vector range = absl::StrSplit(range_str, "-"); if (range.size() != 2) { std::cout @@ -101,20 +101,21 @@ extern "C" [[maybe_unused]] bool __sanitizer_weak_is_relevant_pc( // If the fuzz target is using native libraries, intercept calls only if they // don't originate from those address ranges that are known to belong to the // JDK. - return std::none_of(ignore_for_interception_ranges.cbegin(), - ignore_for_interception_ranges.cend(), - [caller_pc](const auto &range) { - uintptr_t start; - uintptr_t end; - std::tie(start, end) = range; - auto address = reinterpret_cast(caller_pc); - return start <= address && address <= end; - }); + return std::none_of( + ignore_for_interception_ranges.cbegin(), + ignore_for_interception_ranges.cend(), + [caller_pc](const std::pair &range) { + uintptr_t start; + uintptr_t end; + std::tie(start, end) = range; + auto address = reinterpret_cast(caller_pc); + return start <= address && address <= end; + }); } [[maybe_unused]] void Java_com_code_1intelligence_jazzer_runtime_TraceDataFlowNativeCallbacks_handleLibraryLoad( - JNIEnv *env, jclass cls) { + JNIEnv *, jclass) { std::call_once(ignore_list_flag, [] { std::cout << "INFO: detected a native library load, enabling interception " "for libc functions" -- cgit v1.2.3 From 8958f6b4daa5dcc23490e05c4252090d07486ac6 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 16 Aug 2022 21:44:01 +0200 Subject: driver: Remove glog Two `LOG(ERROR)` usages are converted to ordinary prints, one `LOG(INFO)` usage is dropped without replacement since the `JAVA_FUZZER_CLASSPATH` variable isn't even documented. --- driver/BUILD.bazel | 2 -- driver/jazzer_main.cpp | 4 ---- driver/jvm_tooling.cpp | 12 +++++------- repositories.bzl | 9 --------- 4 files changed, 5 insertions(+), 22 deletions(-) diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel index ac285958..2d503cce 100644 --- a/driver/BUILD.bazel +++ b/driver/BUILD.bazel @@ -7,7 +7,6 @@ cc_library( deps = [ ":jvm_tooling_lib", "@com_google_absl//absl/strings", - "@com_google_glog//:glog", "@fmeum_rules_jni//jni:libjvm", "@jazzer_com_github_gflags_gflags//:gflags", ], @@ -25,7 +24,6 @@ cc_library( "@bazel_tools//tools/cpp/runfiles", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", - "@com_google_glog//:glog", "@fmeum_rules_jni//jni", "@jazzer_com_github_gflags_gflags//:gflags", ], diff --git a/driver/jazzer_main.cpp b/driver/jazzer_main.cpp index 01118813..c72e111f 100644 --- a/driver/jazzer_main.cpp +++ b/driver/jazzer_main.cpp @@ -29,7 +29,6 @@ #include "absl/strings/match.h" #include "gflags/gflags.h" -#include "glog/logging.h" #include "jvm_tooling.h" // Defined by glog @@ -121,9 +120,6 @@ int StartLibFuzzer(std::unique_ptr jvm, int main(int argc, char **argv) { gflags::SetUsageMessage(kUsageMessage); - // Disable glog log prefixes to mimic libFuzzer output. - FLAGS_log_prefix = false; - google::InitGoogleLogging(argv[0]); rules_jni_init(argv[0]); const auto argv_end = argv + argc; diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp index 267639bd..71a5f581 100644 --- a/driver/jvm_tooling.cpp +++ b/driver/jvm_tooling.cpp @@ -26,7 +26,6 @@ #include "absl/strings/str_replace.h" #include "absl/strings/str_split.h" #include "gflags/gflags.h" -#include "glog/logging.h" #include "tools/cpp/runfiles/runfiles.h" DEFINE_string(cp, ".", @@ -158,8 +157,8 @@ std::string getInstrumentorAgentPath(const std::string &executable_path) { // User provided agent location takes precedence. if (!FLAGS_agent_path.empty()) { if (std::ifstream(FLAGS_agent_path).good()) return FLAGS_agent_path; - LOG(ERROR) << "Could not find " << kAgentFileName << " at \"" - << FLAGS_agent_path << "\""; + std::cerr << "ERROR: Could not find " << kAgentFileName << " at \"" + << FLAGS_agent_path << "\"" << std::endl; exit(1); } // First check if we are running inside the Bazel tree and use the agent @@ -182,9 +181,9 @@ std::string getInstrumentorAgentPath(const std::string &executable_path) { auto agent_path = absl::StrFormat("%s%c%s", dir, kPathSeparator, kAgentFileName); if (std::ifstream(agent_path).good()) return agent_path; - LOG(ERROR) << "Could not find " << kAgentFileName - << ". Please provide " - "the pathname via the --agent_path flag."; + std::cerr << "ERROR: Could not find " << kAgentFileName + << ". Please provide the pathname via the --agent_path flag." + << std::endl; exit(1); } @@ -262,7 +261,6 @@ JVM::JVM(const std::string &executable_path) { } class_path += absl::StrCat(ARG_SEPARATOR, getInstrumentorAgentPath(executable_path)); - LOG(INFO) << "got class path " << class_path; std::vector options; options.push_back( diff --git a/repositories.bzl b/repositories.bzl index 8e757f15..7abffd87 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -45,15 +45,6 @@ def jazzer_dependencies(): url = "https://github.com/bazelbuild/rules_kotlin/releases/download/v1.6.0/rules_kotlin_release.tgz", ) - maybe( - http_archive, - name = "com_google_glog", - repo_mapping = {"@com_github_gflags_gflags": "@jazzer_com_github_gflags_gflags"}, - sha256 = "8a83bf982f37bb70825df71a9709fa90ea9f4447fb3c099e1d720a439d88bad6", - strip_prefix = "glog-0.6.0", - url = "https://github.com/google/glog/archive/refs/tags/v0.6.0.tar.gz", - ) - maybe( http_archive, name = "com_google_absl", -- cgit v1.2.3 From 87088298d3256bcea8210a6345369cd4b42ba31d Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 16 Aug 2022 22:12:51 +0200 Subject: driver: Remove dependency on Abseil StrFormat Checking the bounds in Java allows for better error messages and we reduce the binary size of the driver JNI library by almost 200 kiB. --- .../jazzer/runtime/FuzzedDataProviderImpl.java | 71 +++++++++++++++++++--- .../code_intelligence/jazzer/driver/BUILD.bazel | 1 - .../jazzer/driver/fuzzed_data_provider.cpp | 31 +++------- 3 files changed, 71 insertions(+), 32 deletions(-) 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 567b02d3..8d70ce10 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 @@ -45,23 +45,51 @@ public class FuzzedDataProviderImpl implements FuzzedDataProvider { @Override public native byte consumeByte(); - @Override public native byte consumeByte(byte min, byte max); + @Override + public byte consumeByte(byte min, byte max) { + if (min > max) { + throw new IllegalArgumentException( + String.format("min must be <= max (got min: %d, max: %d)", min, max)); + } + return consumeByteUnchecked(min, max); + } @Override public native short consumeShort(); - @Override public native short consumeShort(short min, short max); + @Override + public short consumeShort(short min, short max) { + if (min > max) { + throw new IllegalArgumentException( + String.format("min must be <= max (got min: %d, max: %d)", min, max)); + } + return consumeShortUnchecked(min, max); + } @Override public native short[] consumeShorts(int maxLength); @Override public native int consumeInt(); - @Override public native int consumeInt(int min, int max); + @Override + public int consumeInt(int min, int max) { + if (min > max) { + throw new IllegalArgumentException( + String.format("min must be <= max (got min: %d, max: %d)", min, max)); + } + return consumeIntUnchecked(min, max); + } @Override public native int[] consumeInts(int maxLength); @Override public native long consumeLong(); - @Override public native long consumeLong(long min, long max); + @Override + public long consumeLong(long min, long max) { + if (min > max) { + throw new IllegalArgumentException( + String.format("min must be <= max (got min: %d, max: %d)", min, max)); + } + return consumeLongUnchecked(min, max); + } @Override public native long[] consumeLongs(int maxLength); @@ -69,13 +97,27 @@ public class FuzzedDataProviderImpl implements FuzzedDataProvider { @Override public native float consumeRegularFloat(); - @Override public native float consumeRegularFloat(float min, float max); + @Override + public float consumeRegularFloat(float min, float max) { + if (min > max) { + throw new IllegalArgumentException( + String.format("min must be <= max (got min: %f, max: %f)", min, max)); + } + return consumeRegularFloatUnchecked(min, max); + } @Override public native float consumeProbabilityFloat(); @Override public native double consumeDouble(); - @Override public native double consumeRegularDouble(double min, double max); + @Override + public double consumeRegularDouble(double min, double max) { + if (min > max) { + throw new IllegalArgumentException( + String.format("min must be <= max (got min: %f, max: %f)", min, max)); + } + return consumeRegularDoubleUnchecked(min, max); + } @Override public native double consumeRegularDouble(); @@ -83,7 +125,14 @@ public class FuzzedDataProviderImpl implements FuzzedDataProvider { @Override public native char consumeChar(); - @Override public native char consumeChar(char min, char max); + @Override + public char consumeChar(char min, char max) { + if (min > max) { + throw new IllegalArgumentException( + String.format("min must be <= max (got min: %c, max: %c)", min, max)); + } + return consumeCharUnchecked(min, max); + } @Override public native char consumeCharNoSurrogates(); @@ -100,4 +149,12 @@ public class FuzzedDataProviderImpl implements FuzzedDataProvider { @Override public native byte[] consumeRemainingAsBytes(); @Override public native int remainingBytes(); + + private native byte consumeByteUnchecked(byte min, byte max); + private native short consumeShortUnchecked(short min, short max); + private native char consumeCharUnchecked(char min, char max); + private native int consumeIntUnchecked(int min, int max); + private native long consumeLongUnchecked(long min, long max); + private native float consumeRegularFloatUnchecked(float min, float max); + private native double consumeRegularDoubleUnchecked(double min, double max); } diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel b/driver/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel index 2e846414..845023c8 100644 --- a/driver/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -66,7 +66,6 @@ cc_library( ], deps = [ "//agent/src/main/java/com/code_intelligence/jazzer/runtime:fuzzed_data_provider.hdrs", - "@com_google_absl//absl/strings:str_format", ], # Symbols may only be referenced dynamically via JNI. alwayslink = True, diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.cpp b/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.cpp index 7c8441bb..129cb21f 100644 --- a/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.cpp +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.cpp @@ -53,7 +53,6 @@ #include #include -#include "absl/strings/str_format.h" #include "com_code_intelligence_jazzer_runtime_FuzzedDataProviderImpl.h" namespace { @@ -130,14 +129,6 @@ jbyteArray JNICALL ConsumeRemainingAsArray(JNIEnv &env, jobject self) { template T JNICALL ConsumeIntegralInRange(JNIEnv &env, jobject self, T min, T max) { - if (min > max) { - ThrowIllegalArgumentException( - env, absl::StrFormat( - "Consume*InRange: min must be <= max (got min: %d, max: %d)", - min, max)); - return 0; - } - uint64_t range = static_cast(max) - min; uint64_t result = 0; std::size_t offset = 0; @@ -205,14 +196,6 @@ T JNICALL ConsumeProbability(JNIEnv &env, jobject self) { template T JNICALL ConsumeFloatInRange(JNIEnv &env, jobject self, T min, T max) { - if (min > max) { - ThrowIllegalArgumentException( - env, absl::StrFormat( - "Consume*InRange: min must be <= max (got min: %f, max: %f)", - min, max)); - return 0.0; - } - T range; T result = min; @@ -646,33 +629,33 @@ std::size_t RemainingBytes(JNIEnv &env, jobject self) { const JNINativeMethod kFuzzedDataMethods[]{ {(char *)"consumeBoolean", (char *)"()Z", (void *)&ConsumeBool}, {(char *)"consumeByte", (char *)"()B", (void *)&ConsumeIntegral}, - {(char *)"consumeByte", (char *)"(BB)B", + {(char *)"consumeByteUnchecked", (char *)"(BB)B", (void *)&ConsumeIntegralInRange}, {(char *)"consumeShort", (char *)"()S", (void *)&ConsumeIntegral}, - {(char *)"consumeShort", (char *)"(SS)S", + {(char *)"consumeShortUnchecked", (char *)"(SS)S", (void *)&ConsumeIntegralInRange}, {(char *)"consumeInt", (char *)"()I", (void *)&ConsumeIntegral}, - {(char *)"consumeInt", (char *)"(II)I", + {(char *)"consumeIntUnchecked", (char *)"(II)I", (void *)&ConsumeIntegralInRange}, {(char *)"consumeLong", (char *)"()J", (void *)&ConsumeIntegral}, - {(char *)"consumeLong", (char *)"(JJ)J", + {(char *)"consumeLongUnchecked", (char *)"(JJ)J", (void *)&ConsumeIntegralInRange}, {(char *)"consumeFloat", (char *)"()F", (void *)&ConsumeFloat}, {(char *)"consumeRegularFloat", (char *)"()F", (void *)&ConsumeRegularFloat}, - {(char *)"consumeRegularFloat", (char *)"(FF)F", + {(char *)"consumeRegularFloatUnchecked", (char *)"(FF)F", (void *)&ConsumeFloatInRange}, {(char *)"consumeProbabilityFloat", (char *)"()F", (void *)&ConsumeProbability}, {(char *)"consumeDouble", (char *)"()D", (void *)&ConsumeFloat}, {(char *)"consumeRegularDouble", (char *)"()D", (void *)&ConsumeRegularFloat}, - {(char *)"consumeRegularDouble", (char *)"(DD)D", + {(char *)"consumeRegularDoubleUnchecked", (char *)"(DD)D", (void *)&ConsumeFloatInRange}, {(char *)"consumeProbabilityDouble", (char *)"()D", (void *)&ConsumeProbability}, {(char *)"consumeChar", (char *)"()C", (void *)&ConsumeChar}, - {(char *)"consumeChar", (char *)"(CC)C", + {(char *)"consumeCharUnchecked", (char *)"(CC)C", (void *)&ConsumeIntegralInRange}, {(char *)"consumeCharNoSurrogates", (char *)"()C", (void *)&ConsumeCharNoSurrogates}, -- cgit v1.2.3 From 327677ad48bdd3e87794a1fe3c2becc13288e4b7 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Fri, 19 Aug 2022 17:19:00 +0200 Subject: all: Make global state in FuzzedDataProviderImpl local An instance of FuzzedDataProviderImpl can now be one of the following: * backed by an automatically created native copy of a Java array that is freed by `close()`; * backed by a preexisting native array whose lifetime is managed elsewhere. All instances now maintain their own internal, non-global state and can thus be used concurrently. --- .../code_intelligence/jazzer/replay/Replayer.java | 9 +- .../code_intelligence/jazzer/runtime/BUILD.bazel | 3 +- .../jazzer/runtime/FuzzedDataProviderImpl.java | 110 ++++++++++++-- .../runtime/RecordingFuzzedDataProvider.java | 6 +- .../jazzer/runtime/FuzzedDataProviderImplTest.java | 7 +- driver/fuzzed_data_provider_test.cpp | 63 ++++---- .../code_intelligence/jazzer/driver/BUILD.bazel | 1 + .../jazzer/driver/FuzzTargetRunner.java | 50 +++++-- .../code_intelligence/jazzer/driver/BUILD.bazel | 2 - .../jazzer/driver/fuzz_target_runner.cpp | 29 +--- .../jazzer/driver/fuzzed_data_provider.cpp | 163 ++++++++------------- .../jazzer/driver/fuzzed_data_provider.h | 41 ------ 12 files changed, 244 insertions(+), 240 deletions(-) delete mode 100644 driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.h 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 index 80e372f3..0a250d1a 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer.java @@ -106,7 +106,9 @@ public class Replayer { try { Method fuzzerTestOneInput = fuzzTarget.getMethod("fuzzerTestOneInput", FuzzedDataProvider.class); - fuzzerTestOneInput.invoke(null, makeFuzzedDataProvider(input)); + try (FuzzedDataProviderImpl fuzzedDataProvider = FuzzedDataProviderImpl.withJavaData(input)) { + fuzzerTestOneInput.invoke(null, fuzzedDataProvider); + } return; } catch (Exception e) { handleInvokeException(e, fuzzTarget); @@ -151,9 +153,4 @@ public class Replayer { } } } - - private static FuzzedDataProvider makeFuzzedDataProvider(byte[] input) { - FuzzedDataProviderImpl.feed(input); - return new FuzzedDataProviderImpl(); - } } 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 23c26ed9..0d8162d5 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 @@ -12,6 +12,7 @@ java_jni_library( "//driver/src/main/native/com/code_intelligence/jazzer/driver:__pkg__", ], deps = [ + ":unsafe_provider", "//agent/src/main/java/com/code_intelligence/jazzer/api", ], ) @@ -56,7 +57,7 @@ java_library( name = "unsafe_provider", srcs = ["UnsafeProvider.java"], visibility = [ - "//driver/src/test:__subpackages__", + "//driver/src:__subpackages__", "//sanitizers/src/main/java:__subpackages__", ], ) 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 8d70ce10..b7aad33e 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,9 @@ package com.code_intelligence.jazzer.runtime; import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.github.fmeum.rules_jni.RulesJni; +import sun.misc.Unsafe; -public class FuzzedDataProviderImpl implements FuzzedDataProvider { +public class FuzzedDataProviderImpl implements FuzzedDataProvider, AutoCloseable { static { // The replayer loads a standalone version of the FuzzedDataProvider. if (System.getProperty("jazzer.is_replayer") == null) { @@ -26,18 +27,107 @@ public class FuzzedDataProviderImpl implements FuzzedDataProvider { nativeInit(); } - public FuzzedDataProviderImpl() {} - private static native void nativeInit(); - // Resets the FuzzedDataProvider state to read from the beginning to the end of the last fuzzer - // input. - public static native void reset(); + private final boolean ownsNativeData; + private long originalDataPtr; + private int originalRemainingBytes; + + // Accessed in fuzzed_data_provider.cpp. + private long dataPtr; + private int remainingBytes; + + private FuzzedDataProviderImpl(long dataPtr, int remainingBytes, boolean ownsNativeData) { + this.ownsNativeData = ownsNativeData; + this.originalDataPtr = dataPtr; + this.dataPtr = dataPtr; + this.originalRemainingBytes = remainingBytes; + this.remainingBytes = remainingBytes; + } + + /** + * Creates a {@link FuzzedDataProvider} that consumes bytes from an already existing native array. + * + *

      + *
    • {@link #close()} must be called on instances created with this method to free the + * native copy of the Java + * {@code byte} array. + *
    • {@link #setNativeData(long, int)} must not be called on instances created with this + * method. + * + * @param data the raw bytes used as input + * @return a {@link FuzzedDataProvider} backed by {@code data} + */ + public static FuzzedDataProviderImpl withJavaData(byte[] data) { + return new FuzzedDataProviderImpl(allocateNativeCopy(data), data.length, true); + } + + /** + * Creates a {@link FuzzedDataProvider} that consumes bytes from an already existing native array. + * + *

      The backing array can be set at any time using {@link #setNativeData(long, int)} and is + * initially empty. + * + * @return a {@link FuzzedDataProvider} backed by an empty array. + */ + public static FuzzedDataProviderImpl withNativeData() { + return new FuzzedDataProviderImpl(0, 0, false); + } + + /** + * Replaces the current native backing array. + * + *

      Must not be called on instances created with {@link #withJavaData(byte[])}. + * + * @param dataPtr a native pointer to the new backing array + * @param dataLength the length of the new backing array + */ + public void setNativeData(long dataPtr, int dataLength) { + this.originalDataPtr = dataPtr; + this.dataPtr = dataPtr; + this.originalRemainingBytes = dataLength; + this.remainingBytes = dataLength; + } - // Feeds new raw fuzzer input into the provider. - // Note: Clients *must not* use this method if they also use the native FeedFuzzedDataProvider - // method. - public static native void feed(byte[] input); + /** + * Resets the FuzzedDataProvider state to read from the beginning to the end of its current + * backing item. + */ + public void reset() { + dataPtr = originalDataPtr; + remainingBytes = originalRemainingBytes; + } + + /** + * Releases native memory allocated for this instance (if any). + * + *

      While the instance should not be used after this method returns, no usage of {@link + * FuzzedDataProvider} methods can result in memory corruption. + */ + @Override + public void close() { + if (originalDataPtr == 0) { + return; + } + if (ownsNativeData) { + UNSAFE.freeMemory(originalDataPtr); + } + // Prevent double-frees and use-after-frees by effectively making all methods no-ops after + // close() has been called. + originalDataPtr = 0; + originalRemainingBytes = 0; + dataPtr = 0; + remainingBytes = 0; + } + + private static final Unsafe UNSAFE = UnsafeProvider.getUnsafe(); + private static final long BYTE_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(byte[].class); + + private static long allocateNativeCopy(byte[] data) { + long nativeCopy = UNSAFE.allocateMemory(data.length); + UNSAFE.copyMemory(data, BYTE_ARRAY_OFFSET, null, nativeCopy, data.length); + return nativeCopy; + } @Override public native boolean consumeBoolean(); diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider.java index 72d82814..4eb80222 100644 --- a/agent/src/main/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider.java +++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider.java @@ -31,11 +31,7 @@ public final class RecordingFuzzedDataProvider implements FuzzedDataProvider { this.target = target; } - public static FuzzedDataProvider makeFuzzedDataProviderProxy() { - return makeFuzzedDataProviderProxy(new FuzzedDataProviderImpl()); - } - - static FuzzedDataProvider makeFuzzedDataProviderProxy(FuzzedDataProvider target) { + public static FuzzedDataProvider makeFuzzedDataProviderProxy(FuzzedDataProvider target) { return new RecordingFuzzedDataProvider(target); } diff --git a/agent/src/test/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImplTest.java b/agent/src/test/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImplTest.java index 31099066..5e922fc0 100644 --- a/agent/src/test/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImplTest.java +++ b/agent/src/test/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImplTest.java @@ -20,9 +20,10 @@ import java.util.stream.Collectors; public class FuzzedDataProviderImplTest { public static void main(String[] args) { - FuzzedDataProviderImpl fuzzedDataProvider = new FuzzedDataProviderImpl(); - FuzzedDataProviderImpl.feed(INPUT_BYTES); - verifyFuzzedDataProvider(fuzzedDataProvider); + try (FuzzedDataProviderImpl fuzzedDataProvider = + FuzzedDataProviderImpl.withJavaData(INPUT_BYTES)) { + verifyFuzzedDataProvider(fuzzedDataProvider); + } } private strictfp static void verifyFuzzedDataProvider(FuzzedDataProvider data) { diff --git a/driver/fuzzed_data_provider_test.cpp b/driver/fuzzed_data_provider_test.cpp index 85cf87e2..e6225b7f 100644 --- a/driver/fuzzed_data_provider_test.cpp +++ b/driver/fuzzed_data_provider_test.cpp @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.h" - #include #include #include @@ -30,77 +28,76 @@ DECLARE_bool(hooks); namespace jazzer { -std::pair FixUpModifiedUtf8(const uint8_t* pos, - std::size_t max_bytes, - jint max_length, - bool ascii_only, - bool stop_on_backslash); +std::pair FixUpModifiedUtf8(const uint8_t* pos, + jint max_bytes, jint max_length, + bool ascii_only, + bool stop_on_backslash); -std::pair FixUpRemainingModifiedUtf8( +std::pair FixUpRemainingModifiedUtf8( const std::string& str, bool ascii_only, bool stop_on_backslash) { return FixUpModifiedUtf8(reinterpret_cast(str.c_str()), str.length(), std::numeric_limits::max(), ascii_only, stop_on_backslash); } -// Work around the fact that size_t is unsigned long on Linux and unsigned long -// long on Windows. -std::size_t operator"" _z(unsigned long long x) { return x; } +std::pair expect(const std::string& s, jint i) { + return std::make_pair(s, i); +} using namespace std::literals::string_literals; TEST(FixUpModifiedUtf8Test, FullUtf8_ContinueOnBackslash) { - EXPECT_EQ(std::make_pair("jazzer"s, 6_z), + EXPECT_EQ(expect("jazzer"s, 6), FixUpRemainingModifiedUtf8("jazzer"s, false, false)); - EXPECT_EQ(std::make_pair("ja\xC0\x80zzer"s, 7_z), + EXPECT_EQ(expect("ja\xC0\x80zzer"s, 7), FixUpRemainingModifiedUtf8("ja\0zzer"s, false, false)); - EXPECT_EQ(std::make_pair("ja\xC0\x80\xC0\x80zzer"s, 8_z), + EXPECT_EQ(expect("ja\xC0\x80\xC0\x80zzer"s, 8), FixUpRemainingModifiedUtf8("ja\0\0zzer"s, false, false)); - EXPECT_EQ(std::make_pair("ja\\zzer"s, 7_z), + EXPECT_EQ(expect("ja\\zzer"s, 7), FixUpRemainingModifiedUtf8("ja\\zzer"s, false, false)); - EXPECT_EQ(std::make_pair("ja\\\\zzer"s, 8_z), + EXPECT_EQ(expect("ja\\\\zzer"s, 8), FixUpRemainingModifiedUtf8("ja\\\\zzer"s, false, false)); - EXPECT_EQ(std::make_pair("ۧ"s, 5_z), + EXPECT_EQ(expect("ۧ"s, 5), FixUpRemainingModifiedUtf8(u8"ۧ"s, false, false)); } TEST(FixUpModifiedUtf8Test, AsciiOnly_ContinueOnBackslash) { - EXPECT_EQ(std::make_pair("jazzer"s, 6_z), + EXPECT_EQ(expect("jazzer"s, 6), FixUpRemainingModifiedUtf8("jazzer"s, true, false)); - EXPECT_EQ(std::make_pair("ja\xC0\x80zzer"s, 7_z), + EXPECT_EQ(expect("ja\xC0\x80zzer"s, 7), FixUpRemainingModifiedUtf8("ja\0zzer"s, true, false)); - EXPECT_EQ(std::make_pair("ja\xC0\x80\xC0\x80zzer"s, 8_z), + EXPECT_EQ(expect("ja\xC0\x80\xC0\x80zzer"s, 8), FixUpRemainingModifiedUtf8("ja\0\0zzer"s, true, false)); - EXPECT_EQ(std::make_pair("ja\\zzer"s, 7_z), + EXPECT_EQ(expect("ja\\zzer"s, 7), FixUpRemainingModifiedUtf8("ja\\zzer"s, true, false)); - EXPECT_EQ(std::make_pair("ja\\\\zzer"s, 8_z), + EXPECT_EQ(expect("ja\\\\zzer"s, 8), FixUpRemainingModifiedUtf8("ja\\\\zzer"s, true, false)); - EXPECT_EQ(std::make_pair("\x62\x02\x2C\x43\x1F"s, 5_z), + EXPECT_EQ(expect("\x62\x02\x2C\x43\x1F"s, 5), FixUpRemainingModifiedUtf8(u8"ۧ"s, true, false)); } TEST(FixUpModifiedUtf8Test, FullUtf8_StopOnBackslash) { - EXPECT_EQ(std::make_pair("jazzer"s, 6_z), + EXPECT_EQ(expect("jazzer"s, 6), FixUpRemainingModifiedUtf8("jazzer"s, false, true)); - EXPECT_EQ(std::make_pair("ja\xC0\x80zzer"s, 7_z), + EXPECT_EQ(expect("ja\xC0\x80zzer"s, 7), FixUpRemainingModifiedUtf8("ja\0zzer"s, false, true)); - EXPECT_EQ(std::make_pair("ja\xC0\x80\xC0\x80zzer"s, 8_z), + EXPECT_EQ(expect("ja\xC0\x80\xC0\x80zzer"s, 8), FixUpRemainingModifiedUtf8("ja\0\0zzer"s, false, true)); - EXPECT_EQ(std::make_pair("ja"s, 4_z), + EXPECT_EQ(expect("ja"s, 4), FixUpRemainingModifiedUtf8("ja\\zzer"s, false, true)); - EXPECT_EQ(std::make_pair("ja\\zzer"s, 8_z), + EXPECT_EQ(expect("ja\\zzer"s, 8), FixUpRemainingModifiedUtf8("ja\\\\zzer"s, false, true)); } TEST(FixUpModifiedUtf8Test, AsciiOnly_StopOnBackslash) { - EXPECT_EQ(std::make_pair("jazzer"s, 6_z), + EXPECT_EQ(expect("jazzer"s, 6), FixUpRemainingModifiedUtf8("jazzer"s, true, true)); - EXPECT_EQ(std::make_pair("ja\xC0\x80zzer"s, 7_z), + EXPECT_EQ(expect("ja\xC0\x80zzer"s, 7), FixUpRemainingModifiedUtf8("ja\0zzer"s, true, true)); - EXPECT_EQ(std::make_pair("ja\xC0\x80\xC0\x80zzer"s, 8_z), + EXPECT_EQ(expect("ja\xC0\x80\xC0\x80zzer"s, 8), FixUpRemainingModifiedUtf8("ja\0\0zzer"s, true, true)); - EXPECT_EQ(std::make_pair("ja"s, 4_z), + EXPECT_EQ(expect("ja"s, 4), FixUpRemainingModifiedUtf8("ja\\zzer"s, true, true)); - EXPECT_EQ(std::make_pair("ja\\zzer"s, 8_z), + EXPECT_EQ(expect("ja\\zzer"s, 8), FixUpRemainingModifiedUtf8("ja\\\\zzer"s, true, true)); } diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel b/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel index a864fbb4..c8e6ba1e 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -37,6 +37,7 @@ java_jni_library( "//agent/src/main/java/com/code_intelligence/jazzer/runtime:coverage_map", "//agent/src/main/java/com/code_intelligence/jazzer/runtime:fuzzed_data_provider", "//agent/src/main/java/com/code_intelligence/jazzer/runtime:signal_handler", + "//agent/src/main/java/com/code_intelligence/jazzer/runtime:unsafe_provider", "//agent/src/main/java/com/code_intelligence/jazzer/utils", ], ) diff --git a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index 68e24fc3..5646e91a 100644 --- a/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/driver/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -28,6 +28,7 @@ import com.code_intelligence.jazzer.runtime.FuzzedDataProviderImpl; import com.code_intelligence.jazzer.runtime.JazzerInternal; import com.code_intelligence.jazzer.runtime.RecordingFuzzedDataProvider; import com.code_intelligence.jazzer.runtime.SignalHandler; +import com.code_intelligence.jazzer.runtime.UnsafeProvider; import com.code_intelligence.jazzer.utils.ExceptionUtils; import com.code_intelligence.jazzer.utils.ManifestUtils; import com.github.fmeum.rules_jni.RulesJni; @@ -47,6 +48,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; +import sun.misc.Unsafe; /** * Executes a fuzz target and reports findings. @@ -59,6 +61,9 @@ public final class FuzzTargetRunner { RulesJni.loadLibrary("jazzer_driver", FuzzTargetRunner.class); } + private static final Unsafe UNSAFE = UnsafeProvider.getUnsafe(); + private static final long BYTE_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(byte[].class); + // Default value of the libFuzzer -error_exitcode flag. private static final int LIBFUZZER_ERROR_EXIT_CODE = 77; private static final String AUTOFUZZ_FUZZ_TARGET = @@ -68,7 +73,8 @@ public final class FuzzTargetRunner { private static final String FUZZER_TEARDOWN = "fuzzerTearDown"; private static final Set ignoredTokens = new HashSet<>(Opt.ignore); - private static final FuzzedDataProvider fuzzedDataProvider = new FuzzedDataProviderImpl(); + private static final FuzzedDataProviderImpl fuzzedDataProvider = + FuzzedDataProviderImpl.withNativeData(); private static final Class fuzzTargetClass; private static final MethodHandle fuzzTarget; public static final boolean useFuzzedDataProvider; @@ -188,22 +194,37 @@ public final class FuzzTargetRunner { Runtime.getRuntime().addShutdownHook(new Thread(FuzzTargetRunner::shutdown)); } + /** + * A test-only convenience wrapper around {@link #runOne(long, int)}. + */ + static int runOne(byte[] data) { + long dataPtr = UNSAFE.allocateMemory(data.length); + UNSAFE.copyMemory(data, BYTE_ARRAY_OFFSET, null, dataPtr, data.length); + try { + return runOne(dataPtr, data.length); + } finally { + UNSAFE.freeMemory(dataPtr); + } + } + /** * Executes the user-provided fuzz target once. * - * @param data the raw fuzzer input if using a {@code byte[]}-based fuzz target and {@code null} - * when using a {@link FuzzedDataProvider}-based fuzz target. + * @param dataPtr a native pointer to beginning of the input provided by the fuzzer for this + * execution + * @param dataLength length of the fuzzer input * @return the value that the native LLVMFuzzerTestOneInput function should return. Currently, * this is always 0. The function may exit the process instead of returning. */ - static int runOne(byte[] data) { + private static int runOne(long dataPtr, int dataLength) { Throwable finding = null; + byte[] data = null; try { if (useFuzzedDataProvider) { - // The FuzzedDataProvider has already been fed with the fuzzer input in - // LLVMFuzzerTestOneInput. - fuzzTarget.invokeExact(fuzzedDataProvider); + fuzzedDataProvider.setNativeData(dataPtr, dataLength); + fuzzTarget.invokeExact((FuzzedDataProvider) fuzzedDataProvider); } else { + data = copyToArray(dataPtr, dataLength); fuzzTarget.invokeExact(data); } } catch (Throwable uncaughtFinding) { @@ -313,7 +334,7 @@ public final class FuzzTargetRunner { private static void dumpReproducer(byte[] data) { if (data == null) { assert useFuzzedDataProvider; - FuzzedDataProviderImpl.reset(); + fuzzedDataProvider.reset(); data = fuzzedDataProvider.consumeRemainingAsBytes(); } MessageDigest digest; @@ -325,16 +346,16 @@ public final class FuzzTargetRunner { String dataSha1 = toHexString(digest.digest(data)); if (!Opt.autofuzz.isEmpty()) { - FuzzedDataProviderImpl.reset(); + fuzzedDataProvider.reset(); FuzzTarget.dumpReproducer(fuzzedDataProvider, Opt.reproducerPath, dataSha1); return; } String base64Data; if (useFuzzedDataProvider) { - FuzzedDataProviderImpl.reset(); + fuzzedDataProvider.reset(); FuzzedDataProvider recordingFuzzedDataProvider = - RecordingFuzzedDataProvider.makeFuzzedDataProviderProxy(); + RecordingFuzzedDataProvider.makeFuzzedDataProviderProxy(fuzzedDataProvider); try { fuzzTarget.invokeExact(recordingFuzzedDataProvider); if (JazzerInternal.lastFinding == null) { @@ -392,6 +413,13 @@ public final class FuzzTargetRunner { ExceptionUtils.dumpAllStackTraces(); } + private static byte[] copyToArray(long ptr, int length) { + // TODO: Use Unsafe.allocateUninitializedArray instead once Java 9 is the base. + byte[] array = new byte[length]; + UNSAFE.copyMemory(null, ptr, array, BYTE_ARRAY_OFFSET, length); + return array; + } + /** * Starts libFuzzer via LLVMFuzzerRunDriver. * diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel b/driver/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel index 845023c8..863a1875 100644 --- a/driver/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -49,7 +49,6 @@ cc_library( "//conditions:default": ["-ldl"], }), deps = [ - ":fuzzed_data_provider", ":sanitizer_symbols", "//driver/src/main/java/com/code_intelligence/jazzer/driver:fuzz_target_runner.hdrs", ], @@ -60,7 +59,6 @@ cc_library( cc_library( name = "fuzzed_data_provider", srcs = ["fuzzed_data_provider.cpp"], - hdrs = ["fuzzed_data_provider.h"], visibility = [ "//driver:__pkg__", ], diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzz_target_runner.cpp b/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzz_target_runner.cpp index ae620e8d..6231af09 100644 --- a/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzz_target_runner.cpp +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzz_target_runner.cpp @@ -27,16 +27,15 @@ #include #include #include +#include #include "com_code_intelligence_jazzer_driver_FuzzTargetRunner.h" -#include "fuzzed_data_provider.h" extern "C" int LLVMFuzzerRunDriver(int *argc, char ***argv, int (*UserCb)(const uint8_t *Data, size_t Size)); namespace { -bool gUseFuzzedDataProvider; jclass gRunner; jmethodID gRunOneId; JavaVM *gJavaVm; @@ -50,17 +49,7 @@ int testOneInput(const uint8_t *data, const std::size_t size) { JNIEnv &env = *gEnv; jint jsize = std::min(size, static_cast(std::numeric_limits::max())); - int res; - if (gUseFuzzedDataProvider) { - ::jazzer::FeedFuzzedDataProvider(data, size); - res = env.CallStaticIntMethod(gRunner, gRunOneId, nullptr); - } else { - jbyteArray input = env.NewByteArray(jsize); - env.SetByteArrayRegion(input, 0, jsize, - reinterpret_cast(data)); - res = env.CallStaticIntMethod(gRunner, gRunOneId, input); - env.DeleteLocalRef(input); - } + int res = env.CallStaticIntMethod(gRunner, gRunOneId, data, jsize); if (env.ExceptionCheck()) { env.ExceptionDescribe(); _Exit(1); @@ -99,23 +88,11 @@ Java_com_code_1intelligence_jazzer_driver_FuzzTargetRunner_startLibFuzzer( gEnv = env; env->GetJavaVM(&gJavaVm); gRunner = reinterpret_cast(env->NewGlobalRef(runner)); - gRunOneId = env->GetStaticMethodID(runner, "runOne", "([B)I"); + gRunOneId = env->GetStaticMethodID(runner, "runOne", "(JI)I"); if (gRunOneId == nullptr) { env->ExceptionDescribe(); _Exit(1); } - jfieldID use_fuzzed_data_provider_id = - env->GetStaticFieldID(runner, "useFuzzedDataProvider", "Z"); - if (use_fuzzed_data_provider_id == nullptr) { - env->ExceptionDescribe(); - _Exit(1); - } - gUseFuzzedDataProvider = - env->GetStaticBooleanField(runner, use_fuzzed_data_provider_id); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - _Exit(1); - } int argc = env->GetArrayLength(args); if (env->ExceptionCheck()) { diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.cpp b/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.cpp index 129cb21f..494bb9e8 100644 --- a/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.cpp +++ b/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.cpp @@ -43,38 +43,19 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // -#include "fuzzed_data_provider.h" - #include #include #include #include #include #include -#include #include "com_code_intelligence_jazzer_runtime_FuzzedDataProviderImpl.h" namespace { -// The current position in the fuzzer input. -const uint8_t *gDataPtr = nullptr; -// The remaining unconsumed bytes at the current position in the fuzzer input. -std::size_t gRemainingBytes = 0; - -const uint8_t *gFuzzerInputStart = nullptr; -std::size_t gFuzzerInputSize = 0; - -// Advance by `bytes` bytes in the buffer or stay at the end if it has been -// consumed. -void Advance(const std::size_t bytes) { - if (bytes > gRemainingBytes) { - gRemainingBytes = 0; - } else { - gDataPtr += bytes; - gRemainingBytes -= bytes; - } -} +jfieldID gDataPtrField = nullptr; +jfieldID gRemainingBytesField = nullptr; void ThrowIllegalArgumentException(JNIEnv &env, const std::string &message) { jclass illegal_argument_exception = @@ -112,13 +93,22 @@ ConsumeIntegralArray(JNIEnv &env, jobject self, jint max_length) { } // Arrays of integral types are considered data and thus consumed from the // beginning of the buffer. - std::size_t max_num_bytes = std::min(sizeof(T) * max_length, gRemainingBytes); + const auto *dataPtr = + reinterpret_cast(env.GetLongField(self, gDataPtrField)); + jint remainingBytes = env.GetIntField(self, gRemainingBytesField); + + jint max_num_bytes = + std::min(static_cast(sizeof(T)) * max_length, remainingBytes); jsize actual_length = max_num_bytes / sizeof(T); - std::size_t actual_num_bytes = sizeof(T) * actual_length; + jint actual_num_bytes = sizeof(T) * actual_length; auto array = (env.*(JniArrayType::kNewArrayFunc))(actual_length); (env.*(JniArrayType::kSetArrayRegionFunc))( - array, 0, actual_length, reinterpret_cast(gDataPtr)); - Advance(actual_num_bytes); + array, 0, actual_length, reinterpret_cast(dataPtr)); + + env.SetLongField(self, gDataPtrField, (jlong)(dataPtr + actual_num_bytes)); + env.SetIntField(self, gRemainingBytesField, + remainingBytes - actual_num_bytes); + return array; } @@ -131,15 +121,22 @@ template T JNICALL ConsumeIntegralInRange(JNIEnv &env, jobject self, T min, T max) { uint64_t range = static_cast(max) - min; uint64_t result = 0; - std::size_t offset = 0; + jint offset = 0; + + const auto *dataPtr = + reinterpret_cast(env.GetLongField(self, gDataPtrField)); + jint remainingBytes = env.GetIntField(self, gRemainingBytesField); while (offset < 8 * sizeof(T) && (range >> offset) > 0 && - gRemainingBytes != 0) { - --gRemainingBytes; - result = (result << 8u) | gDataPtr[gRemainingBytes]; + remainingBytes != 0) { + --remainingBytes; + result = (result << 8u) | dataPtr[remainingBytes]; offset += 8; } + env.SetIntField(self, gRemainingBytesField, remainingBytes); + // dataPtr hasn't been modified, so we don't need to update gDataPtrField. + if (range != std::numeric_limits::max()) // We accept modulo bias in favor of reading a dynamic number of bytes as // this would make it harder for the fuzzer to mutate towards values from @@ -221,7 +218,7 @@ T JNICALL ConsumeRegularFloat(JNIEnv &env, jobject self) { template T JNICALL ConsumeFloat(JNIEnv &env, jobject self) { - if (!gRemainingBytes) return 0.0; + if (env.GetIntField(self, gRemainingBytesField) == 0) return 0.0; auto type_val = ConsumeIntegral(env, self); @@ -323,27 +320,25 @@ enum class Utf8GenerationState { // See Algorithm 1 of https://arxiv.org/pdf/2010.03090.pdf for more details on // the individual cases involved in determining the validity of a UTF-8 string. template -std::pair FixUpModifiedUtf8(const uint8_t *data, - std::size_t max_bytes, - jint max_length) { +std::pair FixUpModifiedUtf8(const uint8_t *data, + jint max_bytes, + jint max_length) { std::string str; // Every character in modified UTF-8 is coded on at most six bytes. Every // consumed byte is transformed into at most one code unit, except for the // case of a zero byte which requires two bytes. - if (max_bytes > std::numeric_limits::max() / 2) - max_bytes = std::numeric_limits::max() / 2; if (ascii_only) { - str.reserve( - std::min(2 * static_cast(max_length), 2 * max_bytes)); + str.reserve(std::min(2 * static_cast(max_length), + 2 * static_cast(max_bytes))); } else { - str.reserve( - std::min(6 * static_cast(max_length), 2 * max_bytes)); + str.reserve(std::min(6 * static_cast(max_length), + 2 * static_cast(max_bytes))); } Utf8GenerationState state = Utf8GenerationState::LeadingByte_Generic; const uint8_t *pos = data; const auto data_end = data + max_bytes; - for (std::size_t length = 0; length < max_length && pos != data_end; ++pos) { + for (jint length = 0; length < max_length && pos != data_end; ++pos) { uint8_t c = *pos; if (ascii_only) { // Clamp to 7-bit ASCII range. @@ -559,11 +554,10 @@ done: namespace jazzer { // Exposed for testing only. -std::pair FixUpModifiedUtf8(const uint8_t *data, - std::size_t max_bytes, - jint max_length, - bool ascii_only, - bool stop_on_backslash) { +std::pair FixUpModifiedUtf8(const uint8_t *data, + jint max_bytes, jint max_length, + bool ascii_only, + bool stop_on_backslash) { if (ascii_only) { if (stop_on_backslash) { return ::FixUpModifiedUtf8(data, max_bytes, max_length); @@ -581,49 +575,53 @@ std::pair FixUpModifiedUtf8(const uint8_t *data, } // namespace jazzer namespace { -jstring ConsumeStringInternal(JNIEnv &env, jint max_length, bool ascii_only, - bool stop_on_backslash) { +jstring ConsumeStringInternal(JNIEnv &env, jobject self, jint max_length, + bool ascii_only, bool stop_on_backslash) { if (max_length < 0) { ThrowIllegalArgumentException(env, "maxLength must not be negative"); return nullptr; } - if (max_length == 0 || gRemainingBytes == 0) return env.NewStringUTF(""); + const auto *dataPtr = + reinterpret_cast(env.GetLongField(self, gDataPtrField)); + jint remainingBytes = env.GetIntField(self, gRemainingBytesField); - if (gRemainingBytes == 1) { - Advance(1); + if (max_length == 0 || remainingBytes == 0) return env.NewStringUTF(""); + + if (remainingBytes == 1) { + env.SetIntField(self, gRemainingBytesField, 0); return env.NewStringUTF(""); } - std::size_t max_bytes = gRemainingBytes; std::string str; - std::size_t consumed_bytes; + jint consumed_bytes; std::tie(str, consumed_bytes) = jazzer::FixUpModifiedUtf8( - gDataPtr, max_bytes, max_length, ascii_only, stop_on_backslash); - Advance(consumed_bytes); + dataPtr, remainingBytes, max_length, ascii_only, stop_on_backslash); + env.SetLongField(self, gDataPtrField, (jlong)(dataPtr + consumed_bytes)); + env.SetIntField(self, gRemainingBytesField, remainingBytes - consumed_bytes); return env.NewStringUTF(str.c_str()); } jstring JNICALL ConsumeAsciiString(JNIEnv &env, jobject self, jint max_length) { - return ConsumeStringInternal(env, max_length, true, true); + return ConsumeStringInternal(env, self, max_length, true, true); } jstring JNICALL ConsumeString(JNIEnv &env, jobject self, jint max_length) { - return ConsumeStringInternal(env, max_length, false, true); + return ConsumeStringInternal(env, self, max_length, false, true); } jstring JNICALL ConsumeRemainingAsAsciiString(JNIEnv &env, jobject self) { - return ConsumeStringInternal(env, std::numeric_limits::max(), true, - false); + return ConsumeStringInternal(env, self, std::numeric_limits::max(), + true, false); } jstring JNICALL ConsumeRemainingAsString(JNIEnv &env, jobject self) { - return ConsumeStringInternal(env, std::numeric_limits::max(), false, - false); + return ConsumeStringInternal(env, self, std::numeric_limits::max(), + false, false); } std::size_t RemainingBytes(JNIEnv &env, jobject self) { - return gRemainingBytes; + return env.GetIntField(self, gRemainingBytesField); } const JNINativeMethod kFuzzedDataMethods[]{ @@ -689,45 +687,6 @@ const jint kNumFuzzedDataMethods = Java_com_code_1intelligence_jazzer_runtime_FuzzedDataProviderImpl_nativeInit( JNIEnv *env, jclass clazz) { env->RegisterNatives(clazz, kFuzzedDataMethods, kNumFuzzedDataMethods); + gDataPtrField = env->GetFieldID(clazz, "dataPtr", "J"); + gRemainingBytesField = env->GetFieldID(clazz, "remainingBytes", "I"); } - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_FuzzedDataProviderImpl_reset( - JNIEnv *env, jclass clazz) { - gDataPtr = gFuzzerInputStart; - gRemainingBytes = gFuzzerInputSize; -} - -[[maybe_unused]] void -Java_com_code_1intelligence_jazzer_runtime_FuzzedDataProviderImpl_feed( - JNIEnv *env, jclass, jbyteArray input) { - // This line is why this function must not be used if FeedFuzzedDataProvider - // is also called from native code. - delete[] gFuzzerInputStart; - - std::size_t size = env->GetArrayLength(input); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->FatalError("Failed to get length of input"); - } - auto *data = static_cast(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(data)); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->FatalError("Failed to copy input"); - } - jazzer::FeedFuzzedDataProvider(data, size); -} - -namespace jazzer { -void FeedFuzzedDataProvider(const uint8_t *data, std::size_t size) { - gDataPtr = data; - gRemainingBytes = size; - - gFuzzerInputStart = data; - gFuzzerInputSize = size; -} -} // namespace jazzer diff --git a/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.h b/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.h deleted file mode 100644 index c9b72546..00000000 --- a/driver/src/main/native/com/code_intelligence/jazzer/driver/fuzzed_data_provider.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace jazzer { - -constexpr char kFuzzedDataProviderImplClass[] = - "com/code_intelligence/jazzer/runtime/FuzzedDataProviderImpl"; - -// Feed the FuzzedDataProvider with a new data buffer. The buffer is accessed -// by native code and not copied into the JVM, so this is cheap to call. -void FeedFuzzedDataProvider(const uint8_t *data, std::size_t size); -} // namespace jazzer -- cgit v1.2.3 From baa144a7a41f97bc0ff8161110cf142903d18878 Mon Sep 17 00:00:00 2001 From: Muhammad Haseeb Ahmad Date: Mon, 26 Sep 2022 23:10:09 +0000 Subject: Make Jazzer API device supported Bug: 246398305 Test: m jazzer Change-Id: Iec54adfc7741ff18d51a38affdf8f4ddd6c3e296 --- Android.bp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Android.bp b/Android.bp index 8f0b5ba9..7841cd32 100644 --- a/Android.bp +++ b/Android.bp @@ -45,8 +45,9 @@ license { ], } -java_library_host { +java_library { name: "jazzer", + host_supported: true, srcs: [ "agent/src/main/java/com/code_intelligence/jazzer/api/*.java", ], -- cgit v1.2.3 From 4d5be65020c98240c46ed35ff27af8398e6fc4fd Mon Sep 17 00:00:00 2001 From: Cory Barker Date: Thu, 6 Oct 2022 22:49:07 +0000 Subject: Adding JazzerSetup target to build everything needed for Java development. There are Jazzer scripts that need these built Test: Built locally and tested scenario Bug: 249123010 Change-Id: I4627b4298f8f3a3903cf2d59571eb9b01a9ede34 --- Android.bp | 8 ++++++++ JazzerSetup.java | 6 ++++++ 2 files changed, 14 insertions(+) create mode 100644 JazzerSetup.java diff --git a/Android.bp b/Android.bp index 7841cd32..d4377f44 100644 --- a/Android.bp +++ b/Android.bp @@ -53,3 +53,11 @@ java_library { ], visibility: ["//visibility:public"], } + +java_binary { + name: "jazzer_setup", + host_supported: true, + srcs: [ + "JazzerSetup.java", + ], +} diff --git a/JazzerSetup.java b/JazzerSetup.java new file mode 100644 index 00000000..b0624cc5 --- /dev/null +++ b/JazzerSetup.java @@ -0,0 +1,6 @@ +package com.jazzer; +public class JazzerSetup { + public static void main (String[] args) { + System.out.println("Init'd"); + } +} \ No newline at end of file -- cgit v1.2.3 From 73ef01c299e84072b45ab99cc349e42fcad3e561 Mon Sep 17 00:00:00 2001 From: Zi Wang Date: Fri, 14 Oct 2022 10:38:26 -0700 Subject: Add a wrapper for jazzer_setup Without a proper wrapper and a wrapper property showing where the wrapper is in bp, the build for jazzer_setup module is incorrect. This change fixes this problem. Owners of jazzer_setup feel free to modify the wrapper to their needs after this change. Test: m nothing and treehugger Change-Id: I4f7e7de6047095019455039c0395c73d0b512239 --- Android.bp | 1 + jazzer_setup.sh | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 jazzer_setup.sh diff --git a/Android.bp b/Android.bp index d4377f44..1f5b45be 100644 --- a/Android.bp +++ b/Android.bp @@ -56,6 +56,7 @@ java_library { java_binary { name: "jazzer_setup", + wrapper: "jazzer_setup.sh", host_supported: true, srcs: [ "JazzerSetup.java", diff --git a/jazzer_setup.sh b/jazzer_setup.sh new file mode 100644 index 00000000..8fd2c9fe --- /dev/null +++ b/jazzer_setup.sh @@ -0,0 +1,6 @@ +#!/system/bin/sh +# Script to start "jazzer_setup" on the device +# +base=/system +export CLASSPATH=$base/framework/jazzer_setup.jar +exec app_process $base/bin com.jazzer.JazzerSetup "$@" -- cgit v1.2.3