diff options
author | Cory Barker <87733503+TheCoryBarker@users.noreply.github.com> | 2023-06-08 18:40:13 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-06-08 18:40:13 +0000 |
commit | 14855310ea4b85b4571c02e1e89ff64307286d05 (patch) | |
tree | 76f8f19ea0b06e6db8286f8cf0347e402fe3d604 | |
parent | 1ba2ae03222fa60bff118ae054560fce643bf408 (diff) | |
download | jazzer-api-14855310ea4b85b4571c02e1e89ff64307286d05.tar.gz |
Add ability to fuzz bootstrap classes in Android (#737)
19 files changed, 929 insertions, 15 deletions
diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index b53d7b78..ab0ed1cf 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -3,7 +3,7 @@ workspace(name = "jazzer") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file", "http_jar") load("//:repositories.bzl", "jazzer_dependencies") -jazzer_dependencies() +jazzer_dependencies(android = True) load("//:init.bzl", "jazzer_init") @@ -291,3 +291,10 @@ cc_library( strip_prefix = "libprotobuf-mutator-1.1", urls = ["https://github.com/google/libprotobuf-mutator/archive/refs/tags/v1.1.tar.gz"], ) + +http_file( + name = "android_jvmti", + downloaded_file_path = "jvmti.encoded", + sha256 = "95bd6fb4f296ff1c49b893c1d3a665de3c2b1beaa3cc8fc570dea992202daa35", + url = "https://android.googlesource.com/platform/art/+/1cff8449bac0fdab6e84dc9255c3cccd504c1705/openjdkjvmti/include/jvmti.h?format=TEXT", +) diff --git a/repositories.bzl b/repositories.bzl index 2d13fba3..46451614 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -16,8 +16,9 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_jar") load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") +load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") -def jazzer_dependencies(): +def jazzer_dependencies(android = False): maybe( http_archive, name = "platforms", @@ -182,3 +183,12 @@ def jazzer_dependencies(): strip_prefix = "llvm-project-jazzer-2023-04-25/compiler-rt/lib/fuzzer", url = "https://github.com/CodeIntelligenceTesting/llvm-project-jazzer/archive/refs/tags/2023-04-25.tar.gz", ) + + if android: + maybe( + git_repository, + name = "jazzer_slicer", + remote = "https://android.googlesource.com/platform/tools/dexter", + build_file = "//third_party:slicer.BUILD", + commit = "0fe35538da107ff48da6e9f9b92b55b014973bf8", + ) diff --git a/src/main/java/com/code_intelligence/jazzer/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/BUILD.bazel index ea3eaa6b..660e4b14 100644 --- a/src/main/java/com/code_intelligence/jazzer/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/BUILD.bazel @@ -119,6 +119,7 @@ java_library( "//src/main/java/com/code_intelligence/jazzer/driver", "//src/main/java/com/code_intelligence/jazzer/runtime:constants", "//src/main/java/com/code_intelligence/jazzer/utils:log", + "//src/main/java/com/code_intelligence/jazzer/utils:zip_utils", "@fmeum_rules_jni//jni/tools/native_loader", ], ) diff --git a/src/main/java/com/code_intelligence/jazzer/Jazzer.java b/src/main/java/com/code_intelligence/jazzer/Jazzer.java index 449e78c3..e50693c7 100644 --- a/src/main/java/com/code_intelligence/jazzer/Jazzer.java +++ b/src/main/java/com/code_intelligence/jazzer/Jazzer.java @@ -26,6 +26,7 @@ import static java.util.stream.Collectors.toSet; import com.code_intelligence.jazzer.driver.Driver; import com.code_intelligence.jazzer.utils.Log; +import com.code_intelligence.jazzer.utils.ZipUtils; import com.github.fmeum.rules_jni.RulesJni; import java.io.ByteArrayOutputStream; import java.io.File; @@ -265,7 +266,42 @@ public class Jazzer { return Paths.get(System.getProperty("java.home"), "bin", javaBinaryName); } - private static Stream<String> javaBinaryArgs() { + private static Stream<String> javaBinaryArgs() throws IOException { + if (IS_ANDROID) { + // Add Android specific args + Path agentPath = + RulesJni.extractLibrary("android_native_agent", "/com/code_intelligence/jazzer/android"); + + String jazzerAgentPath = System.getProperty("jazzer.agent_path"); + String bootclassClassOverrides = + System.getProperty("jazzer.android_bootpath_classes_overrides"); + + String jazzerBootstrapJarPath = + "com/code_intelligence/jazzer/android/jazzer_bootstrap_android.jar"; + String jazzerBootstrapJarOut = "/data/local/tmp/jazzer_bootstrap_android.jar"; + + try { + ZipUtils.extractFile(jazzerAgentPath, jazzerBootstrapJarPath, jazzerBootstrapJarOut); + } catch (IOException ioe) { + Log.error( + "Could not extract jazzer_bootstrap_android.jar from Jazzer standalone agent", ioe); + exit(1); + } + + String nativeAgentOptions = "injectJars=" + jazzerBootstrapJarOut; + if (bootclassClassOverrides != null && !bootclassClassOverrides.isEmpty()) { + nativeAgentOptions += ",bootstrapClassOverrides=" + bootclassClassOverrides; + } + + // ManagementFactory wont work with Android + Stream<String> stream = + Stream.of("-cp", System.getProperty("java.class.path"), "-Xplugin:libopenjdkjvmti.so", + "-agentpath:" + agentPath.toString() + "=" + nativeAgentOptions, "-Xcompiler-option", + "--debuggable", "-Djdk.attach.allowAttachSelf=true", Jazzer.class.getName()); + + return stream; + } + Stream<String> stream = Stream.of("-cp", System.getProperty("java.class.path"), // Make ByteBuddyAgent's job simpler by allowing it to attach directly to the JVM // rather than relying on an external helper. The latter fails on macOS 12 with JDK 11+ @@ -277,11 +313,6 @@ public class Jazzer { // entitlements required for library insertion. "-Djdk.attach.allowAttachSelf=true", Jazzer.class.getName()); - if (IS_ANDROID) { - // ManagementFactory wont work with Android - return stream; - } - return Stream.concat(ManagementFactory.getRuntimeMXBean().getInputArguments().stream(), stream); } diff --git a/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel index 3106acb7..89acbda3 100644 --- a/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel @@ -4,9 +4,14 @@ load("//bazel:kotlin.bzl", "ktlint") java_library( name = "agent_installer", srcs = ["AgentInstaller.java"], - resources = [ - "//src/main/java/com/code_intelligence/jazzer/runtime:jazzer_bootstrap", - ], + resources = select({ + "@platforms//os:android": [ + "//src/main/java/com/code_intelligence/jazzer/android:jazzer_bootstrap_android", + ], + "//conditions:default": [ + "//src/main/java/com/code_intelligence/jazzer/runtime:jazzer_bootstrap", + ], + }), visibility = ["//visibility:public"], deps = [ ":agent_lib", diff --git a/src/main/java/com/code_intelligence/jazzer/android/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/android/BUILD.bazel index 627544a7..98b7d11f 100644 --- a/src/main/java/com/code_intelligence/jazzer/android/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/android/BUILD.bazel @@ -1,4 +1,57 @@ load("//bazel:compat.bzl", "SKIP_ON_WINDOWS") +load("@bazel_skylib//rules:copy_file.bzl", "copy_file") +load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library") + +java_import( + name = "jazzer_bootstrap_android_import", + jars = [ + "//src/main/java/com/code_intelligence/jazzer/runtime:jazzer_bootstrap", + ], + tags = ["manual"], + target_compatible_with = SKIP_ON_WINDOWS, +) + +android_library( + name = "jazzer_bootstrap_android_lib", + tags = ["manual"], + target_compatible_with = SKIP_ON_WINDOWS, + visibility = [ + "//src/main/java/com/code_intelligence/jazzer/agent:__pkg__", + ], + exports = [ + ":jazzer_bootstrap_android_import", + ], +) + +android_binary( + name = "jazzer_bootstrap_android_bin", + manifest = "//launcher/android:android_manifest", + min_sdk_version = 26, + tags = ["manual"], + target_compatible_with = SKIP_ON_WINDOWS, + deps = [ + ":jazzer_bootstrap_android_lib", + ], +) + +copy_file( + name = "jazzer_bootstrap_android", + src = "jazzer_bootstrap_android_bin.apk", + out = "jazzer_bootstrap_android.jar", + tags = ["manual"], + target_compatible_with = SKIP_ON_WINDOWS, + visibility = [ + "//src/main/java/com/code_intelligence/jazzer/agent:__pkg__", + ], +) + +java_jni_library( + name = "dex_file_manager", + srcs = ["DexFileManager.java"], + native_libs = [ + "//src/main/native/com/code_intelligence/jazzer/android:android_native_agent", + ], +) android_library( name = "jazzer_standalone_library", @@ -6,10 +59,7 @@ android_library( target_compatible_with = SKIP_ON_WINDOWS, exports = [ "//deploy:jazzer-api", - "//sanitizers:offline_only_sanitizers", "//src/main/java/com/code_intelligence/jazzer:jazzer_import", - "//src/main/java/com/code_intelligence/jazzer/runtime", - "//src/main/java/com/code_intelligence/jazzer/utils:unsafe_provider", ], ) @@ -24,6 +74,7 @@ android_binary( "//launcher/android:__pkg__", ], deps = [ + ":dex_file_manager", ":jazzer_standalone_library", ], ) diff --git a/src/main/java/com/code_intelligence/jazzer/android/DexFileManager.java b/src/main/java/com/code_intelligence/jazzer/android/DexFileManager.java new file mode 100644 index 00000000..23d2eeec --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/android/DexFileManager.java @@ -0,0 +1,68 @@ +/* + * Copyright 2023 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.android; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.Math; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public class DexFileManager { + private final static int MAX_READ_LENGTH = 2000000; + + public static byte[] getBytecodeFromDex(String jarPath, String dexFile) throws IOException { + try (JarFile jarFile = new JarFile(jarPath)) { + JarEntry entry = jarFile.stream() + .filter(jarEntry -> jarEntry.getName().equals(dexFile)) + .findFirst() + .orElse(null); + + if (entry == null) { + throw new IOException("Could not find dex file: " + dexFile); + } + + try (InputStream is = jarFile.getInputStream(entry)) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + byte[] buffer = new byte[64 * 104 * 1024]; + int read; + while ((read = is.read(buffer)) != -1) { + out.write(buffer, 0, read); + } + + return out.toByteArray(); + } + } + } + + public static String[] getDexFilesForJar(String jarpath) throws IOException { + try (JarFile jarFile = new JarFile(jarpath)) { + return jarFile.stream() + .map(JarEntry::getName) + .filter(entry -> entry.endsWith(".dex")) + .toArray(String[] ::new); + } + } +} diff --git a/src/main/java/com/code_intelligence/jazzer/driver/Opt.java b/src/main/java/com/code_intelligence/jazzer/driver/Opt.java index 78accfd6..723540a8 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/Opt.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/Opt.java @@ -153,6 +153,14 @@ public final class Opt { public static final boolean dedup = boolSetting("dedup", hooks, "Compute and print a deduplication token for every finding"); + public static final String androidBootclassJarPath = stringSetting("android_bootclass_jar_path", + null, + "Full path to booclass jar path that will be used on Android runs. If you are using the launcher this will be set for you."); + + public static final String androidBootclassClassesOverrides = stringSetting( + "android_bootpath_classes_overrides", null, + "Used for fuzzing classes loaded in through the bootstrap class loader on Android. Full path to jar file with the instrumented versions of the classes you want to override."); + // Whether hook instrumentation should add a check for JazzerInternal#hooksEnabled before // executing hooks. Used to disable hooks during non-fuzz JUnit tests. public static final boolean conditionalHooks = diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel index 6198f085..c31c86e4 100644 --- a/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel @@ -53,6 +53,7 @@ strip_jar( ], visibility = [ "//src/main/java/com/code_intelligence/jazzer/agent:__pkg__", + "//src/main/java/com/code_intelligence/jazzer/android:__pkg__", ], ) @@ -98,12 +99,17 @@ java_library( java_jni_library( name = "coverage_map", srcs = ["CoverageMap.java"], + native_libs = select({ + "@platforms//os:android": ["//src/main/native/com/code_intelligence/jazzer/driver:jazzer_driver"], + "//conditions:default": [], + }), visibility = [ "//src/jmh/java/com/code_intelligence/jazzer/instrumentor:__pkg__", "//src/main/native/com/code_intelligence/jazzer/driver:__pkg__", "//src/test:__subpackages__", ], deps = [ + "//src/main/java/com/code_intelligence/jazzer/runtime:constants", "//src/main/java/com/code_intelligence/jazzer/utils:unsafe_provider", ], ) diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java b/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java index d62465b9..a945a30a 100644 --- a/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java +++ b/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java @@ -14,6 +14,8 @@ package com.code_intelligence.jazzer.runtime; +import static com.code_intelligence.jazzer.runtime.Constants.IS_ANDROID; + import com.code_intelligence.jazzer.utils.UnsafeProvider; import com.github.fmeum.rules_jni.RulesJni; import java.lang.invoke.MethodHandle; @@ -110,6 +112,10 @@ final public class CoverageMap { // Called by the coverage instrumentation. @SuppressWarnings("unused") public static void recordCoverage(final int id) { + if (IS_ANDROID) { + enlargeIfNeeded(id); + } + final long address = countersAddress + id; final byte counter = UNSAFE.getByte(address); UNSAFE.putByte(address, (byte) (counter == -1 ? 1 : counter + 1)); diff --git a/src/main/java/com/code_intelligence/jazzer/utils/ZipUtils.java b/src/main/java/com/code_intelligence/jazzer/utils/ZipUtils.java index c637e616..4da35c3f 100644 --- a/src/main/java/com/code_intelligence/jazzer/utils/ZipUtils.java +++ b/src/main/java/com/code_intelligence/jazzer/utils/ZipUtils.java @@ -16,12 +16,15 @@ package com.code_intelligence.jazzer.utils; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.lang.IllegalArgumentException; import java.nio.file.FileVisitResult; import java.nio.file.Files; @@ -38,6 +41,7 @@ import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.stream.Stream; import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; public final class ZipUtils { @@ -96,4 +100,27 @@ public final class ZipUtils { return filesAdded; } + + public static void extractFile(String srcZip, String targetFile, String outputFilePath) + throws IOException { + try (OutputStream out = new FileOutputStream(outputFilePath); + ZipInputStream zis = new ZipInputStream(new FileInputStream(srcZip));) { + ZipEntry ze = zis.getNextEntry(); + while (ze != null) { + if (ze.getName().equals(targetFile)) { + byte[] buf = new byte[1024]; + int read = 0; + + while ((read = zis.read(buf)) > -1) { + out.write(buf, 0, read); + } + + out.close(); + break; + } + + ze = zis.getNextEntry(); + } + } + } } diff --git a/src/main/native/com/code_intelligence/jazzer/android/BUILD.bazel b/src/main/native/com/code_intelligence/jazzer/android/BUILD.bazel new file mode 100644 index 00000000..74f98cda --- /dev/null +++ b/src/main/native/com/code_intelligence/jazzer/android/BUILD.bazel @@ -0,0 +1,47 @@ +load("//bazel:compat.bzl", "SKIP_ON_WINDOWS") +load("@fmeum_rules_jni//jni:defs.bzl", "cc_jni_library") +load("@bazel_skylib//rules:copy_file.bzl", "copy_file") + +copy_file( + name = "jvmti_h_encoded", + src = "@android_jvmti//file", + out = "jvmti.encoded", + is_executable = False, + tags = ["manual"], + target_compatible_with = SKIP_ON_WINDOWS, +) + +genrule( + name = "jvmti_h", + srcs = [ + "jvmti.encoded", + ], + outs = ["jvmti.h"], + cmd = "base64 --decode $< > $(OUTS)", + tags = ["manual"], + target_compatible_with = SKIP_ON_WINDOWS, +) + +cc_jni_library( + name = "android_native_agent", + srcs = [ + "dex_file_manager.cpp", + "dex_file_manager.h", + "jazzer_jvmti_allocator.h", + "native_agent.cpp", + ":jvmti_h", + ], + includes = [ + ".", + ], + linkopts = [ + "-lz", + ], + tags = ["manual"], + target_compatible_with = SKIP_ON_WINDOWS, + visibility = ["//visibility:public"], + deps = [ + "@com_google_absl//absl/strings", + "@jazzer_slicer", + ], +) diff --git a/src/main/native/com/code_intelligence/jazzer/android/dex_file_manager.cpp b/src/main/native/com/code_intelligence/jazzer/android/dex_file_manager.cpp new file mode 100644 index 00000000..b409e82b --- /dev/null +++ b/src/main/native/com/code_intelligence/jazzer/android/dex_file_manager.cpp @@ -0,0 +1,208 @@ +// Copyright 2023 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 "dex_file_manager.h" + +#include <algorithm> +#include <iostream> +#include <sstream> +#include <string> +#include <vector> + +#include "jazzer_jvmti_allocator.h" +#include "jvmti.h" +#include "slicer/dex_ir.h" +#include "slicer/reader.h" +#include "slicer/writer.h" + +std::string GetName(const char* name) { + std::stringstream ss; + // Class name needs to be in the format "L<class_name>;" as it is stored in + // the types table in the DEX file for slicer to find it + ss << "L" << name << ";"; + return ss.str(); +} + +bool IsValidIndex(dex::u4 index) { return index != (unsigned)-1; } + +void DexFileManager::addDexFile(const unsigned char* bytes, int length) { + unsigned char* newArr = new unsigned char[length]; + std::copy(bytes, bytes + length, newArr); + + dexFiles.push_back(newArr); + dexFilesSize.push_back(length); +} + +unsigned char* DexFileManager::getClassBytes(const char* className, + int dexFileIndex, jvmtiEnv* jvmti, + size_t* newSize) { + dex::Reader dexReader(dexFiles[dexFileIndex], dexFilesSize[dexFileIndex]); + auto descName = GetName(className); + + auto classIndex = dexReader.FindClassIndex(descName.c_str()); + if (!IsValidIndex(classIndex)) { + *newSize = *newSize; + return nullptr; + } + + dexReader.CreateClassIr(classIndex); + auto oldIr = dexReader.GetIr(); + + dex::Writer writer(oldIr); + JazzerJvmtiAllocator allocator(jvmti); + return writer.CreateImage(&allocator, newSize); +} + +uint32_t DexFileManager::findDexFileForClass(const char* className) { + for (int i = 0; i < dexFiles.size(); i++) { + dex::Reader dexReader(dexFiles[i], dexFilesSize[i]); + + std::string descName = GetName(className); + dex::u4 classIndex = dexReader.FindClassIndex(descName.c_str()); + + if (IsValidIndex(classIndex)) { + return i; + } + } + + return -1; +} + +std::vector<std::string> getMethodDescriptions( + std::vector<ir::EncodedMethod*>* encMethodList) { + std::vector<std::string> methodDescs; + + for (int i = 0; i < encMethodList->size(); i++) { + std::stringstream ss; + ss << (*encMethodList)[i]->access_flags; + ss << (*encMethodList)[i]->decl->name->c_str(); + ss << (*encMethodList)[i]->decl->prototype->Signature().c_str(); + + methodDescs.push_back(ss.str()); + } + + sort(methodDescs.begin(), methodDescs.end()); + return methodDescs; +} + +std::vector<std::string> getFieldDescriptions( + std::vector<ir::EncodedField*>* encFieldList) { + std::vector<std::string> fieldDescs; + + for (int i = 0; i < encFieldList->size(); i++) { + std::stringstream ss; + ss << (*encFieldList)[i]->access_flags; + ss << (*encFieldList)[i]->decl->type->descriptor->c_str(); + ss << (*encFieldList)[i]->decl->name->c_str(); + fieldDescs.push_back(ss.str()); + } + + sort(fieldDescs.begin(), fieldDescs.end()); + return fieldDescs; +} + +bool matchFields(std::vector<ir::EncodedField*>* encodedFieldListOne, + std::vector<ir::EncodedField*>* encodedFieldListTwo) { + std::vector<std::string> fDescListOne = + getFieldDescriptions(encodedFieldListOne); + std::vector<std::string> fDescListTwo = + getFieldDescriptions(encodedFieldListTwo); + + if (fDescListOne.size() != fDescListTwo.size()) { + return false; + } + + for (int i = 0; i < fDescListOne.size(); i++) { + if (fDescListOne[i] != fDescListTwo[i]) { + return false; + } + } + + return true; +} + +bool matchMethods(std::vector<ir::EncodedMethod*>* encodedMethodListOne, + std::vector<ir::EncodedMethod*>* encodedMethodListTwo) { + std::vector<std::string> mDescListOne = + getMethodDescriptions(encodedMethodListOne); + std::vector<std::string> mDescListTwo = + getMethodDescriptions(encodedMethodListTwo); + + if (mDescListOne.size() != mDescListTwo.size()) { + return false; + } + + for (int i = 0; i < mDescListOne.size(); i++) { + if (mDescListOne[i] != mDescListTwo[i]) { + return false; + } + } + + return true; +} + +bool classStructureMatches(ir::Class* classOne, ir::Class* classTwo) { + return matchMethods(&(classOne->direct_methods), + &(classTwo->direct_methods)) && + matchMethods(&(classOne->virtual_methods), + &(classTwo->virtual_methods)) && + matchFields(&(classOne->static_fields), &(classTwo->static_fields)) && + matchFields(&(classOne->instance_fields), + &(classTwo->instance_fields)) && + classOne->access_flags == classTwo->access_flags; +} + +bool DexFileManager::structureMatches(dex::Reader* oldReader, + dex::Reader* newReader, + const char* className) { + std::string descName = GetName(className); + + dex::u4 oldReaderIndex = oldReader->FindClassIndex(descName.c_str()); + dex::u4 newReaderIndex = newReader->FindClassIndex(descName.c_str()); + + if (!IsValidIndex(oldReaderIndex) || !IsValidIndex(newReaderIndex)) { + return false; + } + + oldReader->CreateClassIr(oldReaderIndex); + newReader->CreateClassIr(newReaderIndex); + + std::shared_ptr<ir::DexFile> oldDexFile = oldReader->GetIr(); + std::shared_ptr<ir::DexFile> newDexFile = newReader->GetIr(); + + for (int i = 0; i < oldDexFile->classes.size(); i++) { + const char* oldClassDescriptor = + oldDexFile->classes[i]->type->descriptor->c_str(); + if (strcmp(oldClassDescriptor, descName.c_str()) != 0) { + continue; + } + + bool match = false; + for (int j = 0; j < newDexFile->classes.size(); j++) { + const char* newClassDescriptor = + newDexFile->classes[j]->type->descriptor->c_str(); + if (strcmp(oldClassDescriptor, newClassDescriptor) == 0) { + match = classStructureMatches(oldDexFile->classes[i].get(), + newDexFile->classes[j].get()); + break; + } + } + + if (!match) { + return false; + } + } + + return true; +} diff --git a/src/main/native/com/code_intelligence/jazzer/android/dex_file_manager.h b/src/main/native/com/code_intelligence/jazzer/android/dex_file_manager.h new file mode 100644 index 00000000..2b7dd67a --- /dev/null +++ b/src/main/native/com/code_intelligence/jazzer/android/dex_file_manager.h @@ -0,0 +1,37 @@ +/* + * Copyright 2023 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 <vector> + +#include "jvmti.h" +#include "slicer/reader.h" + +// DexFileManager will contain the contents to multiple DEX files +class DexFileManager { + public: + DexFileManager() {} + + void addDexFile(const unsigned char* bytes, int length); + unsigned char* getClassBytes(const char* className, int dexFileIndex, + jvmtiEnv* jvmti, size_t* newSize); + uint32_t findDexFileForClass(const char* className); + bool structureMatches(dex::Reader* oldReader, dex::Reader* newReader, + const char* className); + + private: + std::vector<unsigned char*> dexFiles; + std::vector<int> dexFilesSize; +}; diff --git a/src/main/native/com/code_intelligence/jazzer/android/jazzer_jvmti_allocator.h b/src/main/native/com/code_intelligence/jazzer/android/jazzer_jvmti_allocator.h new file mode 100644 index 00000000..0748c177 --- /dev/null +++ b/src/main/native/com/code_intelligence/jazzer/android/jazzer_jvmti_allocator.h @@ -0,0 +1,52 @@ +/* + * Copyright 2023 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 <iostream> + +#include "slicer/writer.h" + +class JazzerJvmtiAllocator : public dex::Writer::Allocator { + public: + JazzerJvmtiAllocator(jvmtiEnv* jvmti_env) : jvmti_env_(jvmti_env) {} + + virtual void* Allocate(size_t size) { + unsigned char* alloc = nullptr; + jvmtiError error_num = jvmti_env_->Allocate(size, &alloc); + + if (error_num != JVMTI_ERROR_NONE) { + std::cerr << "JazzerJvmtiAllocator Allocation error. JVMTI error: " + << error_num << std::endl; + } + + return (void*)alloc; + } + + virtual void Free(void* ptr) { + if (ptr == nullptr) { + return; + } + + jvmtiError error_num = jvmti_env_->Deallocate((unsigned char*)ptr); + + if (error_num != JVMTI_ERROR_NONE) { + std::cout << "JazzerJvmtiAllocator Free error. JVMTI error: " << error_num + << std::endl; + } + } + + private: + jvmtiEnv* jvmti_env_; +}; diff --git a/src/main/native/com/code_intelligence/jazzer/android/native_agent.cpp b/src/main/native/com/code_intelligence/jazzer/android/native_agent.cpp new file mode 100644 index 00000000..9f0b2ad8 --- /dev/null +++ b/src/main/native/com/code_intelligence/jazzer/android/native_agent.cpp @@ -0,0 +1,313 @@ +// Copyright 2023 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 <dlfcn.h> +#include <jni.h> + +#include <fstream> +#include <iostream> +#include <map> +#include <memory> +#include <sstream> +#include <string> +#include <unordered_set> +#include <vector> + +#include "absl/strings/str_split.h" +#include "dex_file_manager.h" +#include "jazzer_jvmti_allocator.h" +#include "jvmti.h" +#include "slicer/arrayview.h" +#include "slicer/dex_format.h" +#include "slicer/reader.h" +#include "slicer/writer.h" + +static std::string agentOptions; +static DexFileManager dfm; + +const std::string kAndroidAgentClass = + "com/code_intelligence/jazzer/android/DexFileManager"; + +void retransformLoadedClasses(jvmtiEnv* jvmti, JNIEnv* env) { + jint classCount = 0; + jclass* classes; + + jvmti->GetLoadedClasses(&classCount, &classes); + + std::vector<jclass> classesToRetransform; + for (int i = 0; i < classCount; i++) { + jboolean isModifiable = false; + jvmti->IsModifiableClass(classes[i], &isModifiable); + + if ((bool)isModifiable) { + classesToRetransform.push_back(classes[i]); + } + } + + jvmtiError errorNum = jvmti->RetransformClasses(classesToRetransform.size(), + &classesToRetransform[0]); + if (errorNum != JVMTI_ERROR_NONE) { + std::cerr << "Could not retransform classes. JVMTI error: " << errorNum + << std::endl; + exit(1); + } +} + +std::vector<std::string> getDexFiles(std::string jarPath, JNIEnv* env) { + jclass jazzerClass = env->FindClass(kAndroidAgentClass.c_str()); + if (jazzerClass == nullptr) { + std::cerr << kAndroidAgentClass << " could not be found" << std::endl; + exit(1); + } + + const char* getDexFilesFunction = "getDexFilesForJar"; + jmethodID getDexFilesForJar = + env->GetStaticMethodID(jazzerClass, getDexFilesFunction, + "(Ljava/lang/String;)[Ljava/lang/String;"); + if (getDexFilesForJar == nullptr) { + std::cerr << getDexFilesFunction << " could not be found\n"; + exit(1); + } + + jstring jJarFile = env->NewStringUTF(jarPath.data()); + jobjectArray dexFilesArray = (jobjectArray)env->CallStaticObjectMethod( + jazzerClass, getDexFilesForJar, jJarFile); + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + exit(1); + } + + int length = env->GetArrayLength(dexFilesArray); + + std::vector<std::string> dexFilesResult; + for (int i = 0; i < length; i++) { + jstring dexFileJstring = + (jstring)env->GetObjectArrayElement(dexFilesArray, i); + const char* dexFileChars = env->GetStringUTFChars(dexFileJstring, NULL); + std::string dexFileString(dexFileChars); + + env->ReleaseStringUTFChars(dexFileJstring, dexFileChars); + dexFilesResult.push_back(dexFileString); + } + + return dexFilesResult; +} + +void initializeBootclassOverrideJar(std::string jarPath, JNIEnv* env) { + std::vector<std::string> dexFiles = getDexFiles(jarPath, env); + + std::cerr << "Adding DEX files for: " << jarPath << std::endl; + for (int i = 0; i < dexFiles.size(); i++) { + std::cerr << "DEX FILE: " << dexFiles[i] << std::endl; + } + + for (int i = 0; i < dexFiles.size(); i++) { + jclass bootHelperClass = env->FindClass(kAndroidAgentClass.c_str()); + if (bootHelperClass == nullptr) { + std::cerr << kAndroidAgentClass << " could not be found" << std::endl; + exit(1); + } + + jmethodID getBytecodeFromDex = + env->GetStaticMethodID(bootHelperClass, "getBytecodeFromDex", + "(Ljava/lang/String;Ljava/lang/String;)[B"); + if (getBytecodeFromDex == nullptr) { + std::cerr << "'getBytecodeFromDex' not found\n"; + exit(1); + } + + jstring jjarPath = env->NewStringUTF(jarPath.data()); + jstring jdexFile = env->NewStringUTF(dexFiles[i].data()); + + int length = 1; + std::vector<unsigned char> dexFileBytes; + + jbyteArray dexBytes = (jbyteArray)env->CallStaticObjectMethod( + bootHelperClass, getBytecodeFromDex, jjarPath, jdexFile); + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + exit(1); + } + + jbyte* data = new jbyte; + data = env->GetByteArrayElements(dexBytes, 0); + length = env->GetArrayLength(dexBytes); + + for (int j = 0; j < length; j++) { + dexFileBytes.push_back(data[j]); + } + + env->DeleteLocalRef(dexBytes); + env->DeleteLocalRef(jjarPath); + env->DeleteLocalRef(jdexFile); + env->DeleteLocalRef(bootHelperClass); + + unsigned char* usData = reinterpret_cast<unsigned char*>(&dexFileBytes[0]); + dfm.addDexFile(usData, length); + } +} + +void JNICALL jazzerClassFileLoadHook( + jvmtiEnv* jvmti, JNIEnv* jni_env, jclass class_being_redefined, + jobject loader, const char* name, jobject protection_domain, + jint class_data_len, const unsigned char* class_data, + jint* new_class_data_len, unsigned char** new_class_data) { + // check if Jazzer class + const char* prefix = "com/code_intelligence/jazzer/"; + if (strncmp(name, prefix, 29) == 0) { + return; + } + + int indx = dfm.findDexFileForClass(name); + if (indx < 0) { + return; + } + + size_t newSize; + unsigned char* newClassDataResult = + dfm.getClassBytes(name, indx, jvmti, &newSize); + + dex::Reader oldReader(const_cast<unsigned char*>(class_data), + (size_t)class_data_len); + dex::Reader newReader(newClassDataResult, newSize); + if (dfm.structureMatches(&oldReader, &newReader, name)) { + std::cout << "REDEFINING WITH INSTRUMENTATION: " << name << std::endl; + *new_class_data = newClassDataResult; + *new_class_data_len = static_cast<jint>(newSize); + } +} + +bool fileExists(std::string filePath) { return std::ifstream(filePath).good(); } + +void JNICALL jazzerVMInit(jvmtiEnv* jvmti_env, JNIEnv* jni_env, + jthread thread) { + // Parse agentOptions + + std::stringstream ss(agentOptions); + std::string token; + + std::string jazzerClassesJar; + std::vector<std::string> bootpathClassesOverrides; + while (std::getline(ss, token, ',')) { + std::vector<std::string> split = + absl::StrSplit(token, absl::MaxSplits('=', 1)); + if (split.size() < 2) { + std::cerr << "ERROR: no option given for: " << token; + exit(1); + } + + if (split[0] == "injectJars") { + jazzerClassesJar = split[1]; + } else if (split[0] == "bootstrapClassOverrides") { + bootpathClassesOverrides = + absl::StrSplit(split[1], absl::MaxSplits(':', 10)); + } + } + + if (!fileExists(jazzerClassesJar)) { + std::cerr << "ERROR: Jazzer bootstrap class file not found at: " + << jazzerClassesJar << std::endl; + exit(1); + } + + jvmti_env->AddToBootstrapClassLoaderSearch(jazzerClassesJar.c_str()); + + jvmtiCapabilities jazzerJvmtiCapabilities = { + .can_tag_objects = 0, + .can_generate_field_modification_events = 0, + .can_generate_field_access_events = 0, + .can_get_bytecodes = 0, + .can_get_synthetic_attribute = 0, + .can_get_owned_monitor_info = 0, + .can_get_current_contended_monitor = 0, + .can_get_monitor_info = 0, + .can_pop_frame = 0, + .can_redefine_classes = 1, + .can_signal_thread = 0, + .can_get_source_file_name = 1, + .can_get_line_numbers = 0, + .can_get_source_debug_extension = 0, + .can_access_local_variables = 0, + .can_maintain_original_method_order = 0, + .can_generate_single_step_events = 0, + .can_generate_exception_events = 0, + .can_generate_frame_pop_events = 0, + .can_generate_breakpoint_events = 0, + .can_suspend = 0, + .can_redefine_any_class = 0, + .can_get_current_thread_cpu_time = 0, + .can_get_thread_cpu_time = 0, + .can_generate_method_entry_events = 0, + .can_generate_method_exit_events = 0, + .can_generate_all_class_hook_events = 0, + .can_generate_compiled_method_load_events = 0, + .can_generate_monitor_events = 0, + .can_generate_vm_object_alloc_events = 0, + .can_generate_native_method_bind_events = 0, + .can_generate_garbage_collection_events = 0, + .can_generate_object_free_events = 0, + .can_force_early_return = 0, + .can_get_owned_monitor_stack_depth_info = 0, + .can_get_constant_pool = 0, + .can_set_native_method_prefix = 0, + .can_retransform_classes = 1, + .can_retransform_any_class = 0, + .can_generate_resource_exhaustion_heap_events = 0, + .can_generate_resource_exhaustion_threads_events = 0, + }; + + jvmtiError je = jvmti_env->AddCapabilities(&jazzerJvmtiCapabilities); + if (je != JVMTI_ERROR_NONE) { + std::cerr << "JVMTI ERROR: " << je << std::endl; + exit(1); + } + + for (int i = 0; i < bootpathClassesOverrides.size(); i++) { + if (!fileExists(bootpathClassesOverrides[i])) { + std::cerr << "ERROR: Bootpath Class override jar not found at: " + << bootpathClassesOverrides[i] << std::endl; + exit(1); + } + + initializeBootclassOverrideJar(bootpathClassesOverrides[i], jni_env); + } + + retransformLoadedClasses(jvmti_env, jni_env); +} + +JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* reserved) { + jvmtiEnv* jvmti = nullptr; + if (vm->GetEnv((void**)&jvmti, JVMTI_VERSION_1_2) != JNI_OK) { + return 1; + } + + jvmtiEventCallbacks callbacks; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.ClassFileLoadHook = jazzerClassFileLoadHook; + callbacks.VMInit = jazzerVMInit; + + jvmti->SetEventCallbacks(&callbacks, sizeof(jvmtiEventCallbacks)); + jvmti->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL); + jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, NULL); + + // Save the options string here, this is the only time it will be available + // however, we wont be able to use this to initialize until VMInit callback is + // called + agentOptions = std::string(options); + return 0; +} diff --git a/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel b/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel index 7e233b91..59c62aed 100644 --- a/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel +++ b/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -8,6 +8,7 @@ cc_jni_library( "//src/jmh:__subpackages__", "//src/main/java/com/code_intelligence/jazzer/driver:__pkg__", "//src/main/java/com/code_intelligence/jazzer/junit:__pkg__", + "//src/main/java/com/code_intelligence/jazzer/runtime:__pkg__", "//src/test:__subpackages__", ], deps = [ diff --git a/third_party/android/android_configure.bzl b/third_party/android/android_configure.bzl index 4b634b4f..33118904 100644 --- a/third_party/android/android_configure.bzl +++ b/third_party/android/android_configure.bzl @@ -20,13 +20,18 @@ def _is_windows(repository_ctx): """Returns true if the current platform is Windows""" return repository_ctx.os.name.lower().startswith("windows") +def _supports_android(repository_ctx): + sdk_home = repository_ctx.os.environ.get(_ANDROID_SDK_HOME) + ndk_home = repository_ctx.os.environ.get(_ANDROID_NDK_HOME) + return sdk_home and ndk_home and not _is_windows(repository_ctx) + def _android_autoconf_impl(repository_ctx): """Implementation of the android_autoconf repo rule""" sdk_home = repository_ctx.os.environ.get(_ANDROID_SDK_HOME) ndk_home = repository_ctx.os.environ.get(_ANDROID_NDK_HOME) # rules_android_ndk does not support Windows yet. - if sdk_home and ndk_home and not _is_windows(repository_ctx): + if _supports_android(repository_ctx): repos = _ANDROID_REPOS_TEMPLATE.format( sdk_home = repr(sdk_home), ndk_home = repr(ndk_home), diff --git a/third_party/slicer.BUILD b/third_party/slicer.BUILD new file mode 100644 index 00000000..a7bc7b67 --- /dev/null +++ b/third_party/slicer.BUILD @@ -0,0 +1,31 @@ +cc_library( + name = "jazzer_slicer", + srcs = [ + "slicer/bytecode_encoder.cc", + "slicer/code_ir.cc", + "slicer/common.cc", + "slicer/control_flow_graph.cc", + "slicer/debuginfo_encoder.cc", + "slicer/dex_bytecode.cc", + "slicer/dex_format.cc", + "slicer/dex_ir.cc", + "slicer/dex_ir_builder.cc", + "slicer/dex_utf8.cc", + "slicer/instrumentation.cc", + "slicer/reader.cc", + "slicer/tryblocks_encoder.cc", + "slicer/writer.cc", + ], + hdrs = glob(["slicer/export/slicer/*.h"]), + copts = [ + "-Wall", + "-Wno-sign-compare", + "-Wno-unused-parameter", + "-Wno-shift-count-overflow", + "-Wno-missing-braces", + ], + includes = ["slicer/export"], + visibility = [ + "//visibility:public", + ], +) |