diff --git a/.bazelrc b/.bazelrc index 7adc5c43e..23a0e71ce 100644 --- a/.bazelrc +++ b/.bazelrc @@ -25,6 +25,11 @@ build:ci --features=layering_check build --java_language_version=8 build --tool_java_language_version=8 +# Android +build:android_arm --incompatible_enable_android_toolchain_resolution +build:android_arm --android_platforms=//:android_arm64 +build:android_arm --copt=-D_ANDROID + # Windows # Only compiles with clang on Windows. build:windows --extra_toolchains=@local_config_cc//:cc-toolchain-x64_windows-clang-cl diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-all-tests.yml index 1fc6bcef9..2ece3a586 100644 --- a/.github/workflows/run-all-tests.yml +++ b/.github/workflows/run-all-tests.yml @@ -38,6 +38,7 @@ jobs: - os: ubuntu-20.04 arch: "linux" cache: "/home/runner/.cache/bazel-disk" + bazel_args: "//launcher/android:jazzer_android" - os: ubuntu-20.04 jdk: 19 # Workaround for https://github.com/bazelbuild/bazel/issues/14502 diff --git a/BUILD.bazel b/BUILD.bazel index e1178da14..74fc4ade5 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -24,3 +24,12 @@ alias( name = "jazzer", actual = "//launcher:jazzer", ) + +platform( + name = "android_arm64", + constraint_values = [ + "@platforms//cpu:arm64", + "@platforms//os:android", + ], + visibility = ["//:__subpackages__"], +) diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index 37ccbd400..9174931c7 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -146,3 +146,25 @@ http_file( sha256 = "c449591174982bbc003d1290003fcbc7b939215436922d2f0f25239d110d531a", urls = ["https://repo1.maven.org/maven2/org/jacoco/org.jacoco.cli/0.8.8/org.jacoco.cli-0.8.8-nodeps.jar"], ) + +http_archive( + name = "build_bazel_rules_android", + sha256 = "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", + strip_prefix = "rules_android-0.1.1", + urls = ["https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip"], +) + +http_archive( + name = "rules_android_ndk", + sha256 = "0fcaeed391bfc0ee784ab0365312e6c59fe75db9df3a67e8708c606e2a9cfd90", + strip_prefix = "rules_android_ndk-79923720aef601fad89c94e8802f5d77c1b73c5d", + url = "https://github.com/bazelbuild/rules_android_ndk/archive/79923720aef601fad89c94e8802f5d77c1b73c5d.zip", +) + +load("//third_party/android:android_configure.bzl", "android_configure") + +android_configure(name = "configure_android_rules") + +load("@configure_android_rules//:android_configure.bzl", "android_workspace") + +android_workspace() diff --git a/launcher/BUILD.bazel b/launcher/BUILD.bazel index 3bc8c91bc..bcf3eaa2e 100644 --- a/launcher/BUILD.bazel +++ b/launcher/BUILD.bazel @@ -17,6 +17,10 @@ cc_library( data = [ "//src/main/java/com/code_intelligence/jazzer:jazzer_standalone_deploy.jar", ], + linkopts = select({ + "@platforms//os:android": ["-ldl"], + "//conditions:default": [], + }), deps = [ "@bazel_tools//tools/cpp/runfiles", "@com_google_absl//absl/strings", @@ -29,6 +33,7 @@ cc_binary( name = "jazzer_single_arch", linkstatic = True, tags = ["manual"], + visibility = ["//launcher/android:__pkg__"], deps = [":jazzer_main"], ) diff --git a/launcher/android/AndroidManifest.xml b/launcher/android/AndroidManifest.xml new file mode 100644 index 000000000..8a00cfc68 --- /dev/null +++ b/launcher/android/AndroidManifest.xml @@ -0,0 +1,5 @@ + + diff --git a/launcher/android/BUILD.bazel b/launcher/android/BUILD.bazel new file mode 100644 index 000000000..502f61244 --- /dev/null +++ b/launcher/android/BUILD.bazel @@ -0,0 +1,32 @@ +load("//bazel:compat.bzl", "SKIP_ON_WINDOWS") + +android_library( + name = "jazzer_android_lib", + data = [ + "//launcher:jazzer_single_arch", + "//src/main/java/com/code_intelligence/jazzer/android:jazzer_standalone_android.apk", + ], + tags = ["manual"], + target_compatible_with = SKIP_ON_WINDOWS, +) + +android_binary( + name = "jazzer_android", + manifest = ":android_manifest", + min_sdk_version = 26, + tags = ["manual"], + target_compatible_with = SKIP_ON_WINDOWS, + visibility = ["//visibility:public"], + deps = [ + ":jazzer_android_lib", + ], +) + +filegroup( + name = "android_manifest", + srcs = ["AndroidManifest.xml"], + tags = ["manual"], + visibility = [ + "//visibility:public", + ], +) diff --git a/launcher/jvm_tooling.cpp b/launcher/jvm_tooling.cpp index 819bd9c03..2c6c06fe5 100644 --- a/launcher/jvm_tooling.cpp +++ b/launcher/jvm_tooling.cpp @@ -14,6 +14,10 @@ #include "jvm_tooling.h" +#if defined(_ANDROID) +#include +#endif + #include #include #include @@ -120,7 +124,53 @@ std::vector splitEscaped(const std::string &str) { namespace jazzer { -JVM::JVM(const std::string &executable_path) { +#if defined(_ANDROID) +typedef jint (*JNI_CreateJavaVM_t)(JavaVM **, JNIEnv **, void *); +JNI_CreateJavaVM_t LoadAndroidVMLibs() { + std::cout << "Loading Android libraries" << std::endl; + + void *art_so = nullptr; + art_so = dlopen("libnativehelper.so", RTLD_NOW); + + if (art_so == nullptr) { + std::cerr << "Could not find ART library" << std::endl; + exit(1); + } + + typedef void *(*JniInvocationCreate_t)(); + JniInvocationCreate_t JniInvocationCreate = + reinterpret_cast( + dlsym(art_so, "JniInvocationCreate")); + if (JniInvocationCreate == nullptr) { + std::cout << "JniInvocationCreate is null" << std::endl; + exit(1); + } + + void *impl = JniInvocationCreate(); + typedef bool (*JniInvocationInit_t)(void *, const char *); + JniInvocationInit_t JniInvocationInit = + reinterpret_cast(dlsym(art_so, "JniInvocationInit")); + if (JniInvocationInit == nullptr) { + std::cout << "JniInvocationInit is null" << std::endl; + exit(1); + } + + JniInvocationInit(impl, nullptr); + + constexpr char create_jvm_symbol[] = "JNI_CreateJavaVM"; + typedef jint (*JNI_CreateJavaVM_t)(JavaVM **, JNIEnv **, void *); + JNI_CreateJavaVM_t JNI_CreateArtVM = + reinterpret_cast(dlsym(art_so, create_jvm_symbol)); + if (JNI_CreateArtVM == nullptr) { + std::cout << "JNI_CreateJavaVM is null" << std::endl; + exit(1); + } + + return JNI_CreateArtVM; +} +#endif + +std::string GetClassPath(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); @@ -128,12 +178,20 @@ JVM::JVM(const std::string &executable_path) { if (class_path_from_env) { class_path += absl::StrCat(ARG_SEPARATOR, class_path_from_env); } + class_path += absl::StrCat(ARG_SEPARATOR, getInstrumentorAgentPath(executable_path)); + return class_path; +} + +JVM::JVM(const std::string &executable_path) { + std::string class_path = GetClassPath(executable_path); std::vector options; options.push_back( JavaVMOption{.optionString = const_cast(class_path.c_str())}); + +#if !defined(_ANDROID) // 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"}); @@ -148,13 +206,14 @@ JVM::JVM(const std::string &executable_path) { JavaVMOption{.optionString = (char *)"-XX:+IgnoreUnrecognizedVMOptions"}); options.push_back( JavaVMOption{.optionString = (char *)"-XX:+CriticalJNINatives"}); +#endif - // 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( @@ -162,30 +221,51 @@ JVM::JVM(const std::string &executable_path) { } } - // add additional jvm options set through command line flags + // Add additional jvm options set through command line flags. + // Keep the vectors in scope as they contain the strings backing the C strings + // added to options. std::vector jvm_args; if (!FLAGS_jvm_args.empty()) { jvm_args = splitEscaped(FLAGS_jvm_args); + for (const auto &arg : jvm_args) { + options.push_back( + JavaVMOption{.optionString = const_cast(arg.c_str())}); + } } - for (const auto &arg : jvm_args) { - options.push_back( - JavaVMOption{.optionString = const_cast(arg.c_str())}); - } + std::vector additional_jvm_args; if (!FLAGS_additional_jvm_args.empty()) { additional_jvm_args = splitEscaped(FLAGS_additional_jvm_args); + for (const auto &arg : additional_jvm_args) { + options.push_back( + JavaVMOption{.optionString = const_cast(arg.c_str())}); + } } - for (const auto &arg : additional_jvm_args) { - options.push_back( - JavaVMOption{.optionString = const_cast(arg.c_str())}); - } - JavaVMInitArgs jvm_init_args = {.version = JNI_VERSION_1_8, +#if !defined(_ANDROID) + jint jni_version = JNI_VERSION_1_8; +#else + jint jni_version = JNI_VERSION_1_6; +#endif + + JavaVMInitArgs jvm_init_args = {.version = jni_version, .nOptions = (int)options.size(), .options = options.data(), .ignoreUnrecognized = JNI_FALSE}; - auto ret = JNI_CreateJavaVM(&jvm_, (void **)&env_, &jvm_init_args); +#if !defined(_ANDROID) + int ret = JNI_CreateJavaVM(&jvm_, (void **)&env_, &jvm_init_args); +#else + JNI_CreateJavaVM_t CreateArtVM = LoadAndroidVMLibs(); + if (CreateArtVM == nullptr) { + std::cerr << "JNI_CreateJavaVM for Android not found" << std::endl; + exit(1); + } + + std::cout << "Starting Art VM" << std::endl; + int ret = CreateArtVM(&jvm_, (JNIEnv_ **)&env_, &jvm_init_args); +#endif + if (ret != JNI_OK) { throw std::runtime_error( absl::StrFormat("JNI_CreateJavaVM returned code %d", ret)); diff --git a/repositories.bzl b/repositories.bzl index 79dd954da..315eb4128 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -98,9 +98,9 @@ def jazzer_dependencies(): maybe( http_archive, name = "fmeum_rules_jni", - sha256 = "9bd07dade81131aa7e1ac36830cef5d2a6a076406bec53dff51ea59e0efc77a9", - strip_prefix = "rules_jni-0.6.1", - url = "https://github.com/fmeum/rules_jni/archive/refs/tags/v0.6.1.tar.gz", + sha256 = "530a02c4d86f7bcfabd61e7de830f8c78fcb2ea70943eab8f2bfdad96620f1f5", + strip_prefix = "rules_jni-0.7.0", + url = "https://github.com/fmeum/rules_jni/archive/refs/tags/v0.7.0.tar.gz", ) maybe( diff --git a/sanitizers/BUILD.bazel b/sanitizers/BUILD.bazel index 4965cdc3a..6d08be70a 100644 --- a/sanitizers/BUILD.bazel +++ b/sanitizers/BUILD.bazel @@ -5,3 +5,11 @@ java_library( "//sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers", ], ) + +java_library( + name = "offline_only_sanitizers", + visibility = ["//visibility:public"], + runtime_deps = [ + ":sanitizers", + ], +) diff --git a/src/main/java/com/code_intelligence/jazzer/Jazzer.java b/src/main/java/com/code_intelligence/jazzer/Jazzer.java index fc830242e..853a24a22 100644 --- a/src/main/java/com/code_intelligence/jazzer/Jazzer.java +++ b/src/main/java/com/code_intelligence/jazzer/Jazzer.java @@ -78,14 +78,16 @@ private static void start(List args) throws IOException, InterruptedExce Log.fixOutErr(System.out, System.err); parseJazzerArgsToProperties(args); + // --asan and --ubsan imply --native by default, but --native can also be used by itself to fuzz // native libraries without sanitizers (e.g. to quickly grow a corpus). final boolean loadASan = Boolean.parseBoolean(System.getProperty("jazzer.asan", "false")); final boolean loadUBSan = Boolean.parseBoolean(System.getProperty("jazzer.ubsan", "false")); + final boolean loadHWASan = Boolean.parseBoolean(System.getProperty("jazzer.hwasan", "false")); final boolean fuzzNative = Boolean.parseBoolean( - System.getProperty("jazzer.native", Boolean.toString(loadASan || loadUBSan))); - if ((loadASan || loadUBSan) && !fuzzNative) { - Log.error("--asan and --ubsan cannot be used without --native"); + System.getProperty("jazzer.native", Boolean.toString(loadASan || loadUBSan || loadHWASan))); + if ((loadASan || loadUBSan || loadHWASan) && !fuzzNative) { + Log.error("--asan, --hwasan and --ubsan cannot be used without --native"); exit(1); } // No native fuzzing has been requested, fuzz in the current process. @@ -109,7 +111,8 @@ private static void start(List args) throws IOException, InterruptedExce // Run ourselves as a subprocess with `jazzer_preload` and (optionally) native sanitizers // preloaded. By inheriting IO, this wrapping should become invisible for the user. - Set argsToFilter = Stream.of("--asan", "--ubsan", "--native").collect(toSet()); + Set argsToFilter = + Stream.of("--asan", "--ubsan", "--hwasan", "--native").collect(toSet()); ProcessBuilder processBuilder = new ProcessBuilder(); List preloadLibs = new ArrayList<>(); // We have to load jazzer_preload before we load ASan since the ASan includes no-op definitions @@ -128,10 +131,24 @@ private static void start(List args) throws IOException, InterruptedExce // We load jazzer_preload first. "verify_asan_link_order=0")); Log.warn("Jazzer is not compatible with LeakSanitizer. Leaks are not reported."); - preloadLibs.add(findHostClangLibrary(asanLibNames())); + preloadLibs.add(findLibrary(asanLibNames())); + } + if (loadHWASan) { + processBuilder.environment().compute("HWASAN_OPTIONS", + (name, currentValue) + -> appendWithPathListSeparator(name, + // The JVM produces an extremely large number of false positive leaks, which makes + // it impossible to use LeakSanitizer. + // TODO: Investigate whether we can hook malloc/free only for JNI shared + // libraries, not the JVM itself. + "detect_leaks=0", + // We load jazzer_preload first. + "verify_asan_link_order=0")); + Log.warn("Jazzer is not compatible with LeakSanitizer. Leaks are not reported."); + preloadLibs.add(findLibrary(hwasanLibNames())); } if (loadUBSan) { - preloadLibs.add(findHostClangLibrary(ubsanLibNames())); + preloadLibs.add(findLibrary(ubsanLibNames())); } // The launcher script we generate is executed by /bin/sh on macOS, which is codesigned without // the allow-dyld-environment-variables entitlement. The dynamic linker would thus remove all @@ -185,14 +202,22 @@ private static SimpleEntry parseSingleArg(String arg) { // libFuzzer's argv[0], libFuzzer modes that rely on subprocesses can work with the Java driver. // This trick is also used to allow native sanitizers to be preloaded. private static String prepareArgv0(Map additionalEnvironment) throws IOException { - if (!isPosix() && !additionalEnvironment.isEmpty()) { + if (!isPosixOrAndroid() && !additionalEnvironment.isEmpty()) { throw new IllegalArgumentException( - "Setting environment variables in the wrapper is only supported on POSIX systems"); + "Setting environment variables in the wrapper is only supported on POSIX systems and Android"); + } + char shellQuote = isPosixOrAndroid() ? '\'' : '"'; + String launcherTemplate; + if (isAndroid()) { + launcherTemplate = "#!/system/bin/env sh\n%s $@\n"; + } else if (isPosix()) { + launcherTemplate = "#!/usr/bin/env sh\n%s $@\n"; + } else { + launcherTemplate = "@echo off\r\n%s %%*\r\n"; } - char shellQuote = isPosix() ? '\'' : '"'; - String launcherTemplate = isPosix() ? "#!/usr/bin/env sh\n%s $@\n" : "@echo off\r\n%s %%*\r\n"; + String launcherExtension = isPosix() ? ".sh" : ".bat"; - FileAttribute[] launcherScriptAttributes = isPosix() + FileAttribute[] launcherScriptAttributes = isPosixOrAndroid() ? new FileAttribute[] {PosixFilePermissions.asFileAttribute( PosixFilePermissions.fromString("rwx------"))} : new FileAttribute[] {}; @@ -205,30 +230,54 @@ private static String prepareArgv0(Map additionalEnvironment) th // Escape individual arguments for the shell. .map(str -> shellQuote + str + shellQuote) .collect(joining(" ")); + String invocation = env.isEmpty() ? command : env + " " + command; String launcherContent = String.format(launcherTemplate, invocation); - Path launcher = Files.createTempFile("jazzer-", launcherExtension, launcherScriptAttributes); + + Path launcher; + if (isAndroid()) { + launcher = Files.createTempFile( + Paths.get("/data/local/tmp/"), "jazzer-", launcherExtension, launcherScriptAttributes); + } else { + launcher = Files.createTempFile("jazzer-", launcherExtension, launcherScriptAttributes); + } + launcher.toFile().deleteOnExit(); Files.write(launcher, launcherContent.getBytes(StandardCharsets.UTF_8)); return launcher.toAbsolutePath().toString(); } private static Path javaBinary() { - return Paths.get(System.getProperty("java.home"), "bin", isPosix() ? "java" : "java.exe"); + String javaBinaryName; + if (isAndroid()) { + javaBinaryName = "dalvikvm"; + } else if (isPosix()) { + javaBinaryName = "java"; + } else { + javaBinaryName = "java.exe"; + } + + return Paths.get(System.getProperty("java.home"), "bin", javaBinaryName); } private static Stream javaBinaryArgs() { - return Stream.concat(ManagementFactory.getRuntimeMXBean().getInputArguments().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+ - // (but not 8) and UBSan preloaded with: - // Caused by: java.io.IOException: Cannot run program - // "/Users/runner/hostedtoolcache/Java_Zulu_jdk/17.0.4-8/x64/bin/java": error=0, Failed - // to exec spawn helper: pid: 8227, signal: 9 - // Presumably, this issue is caused by codesigning and the exec helper missing the - // entitlements required for library insertion. - "-Djdk.attach.allowAttachSelf=true", Jazzer.class.getName())); + Stream 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+ + // (but not 8) and UBSan preloaded with: + // Caused by: java.io.IOException: Cannot run program + // "/Users/runner/hostedtoolcache/Java_Zulu_jdk/17.0.4-8/x64/bin/java": error=0, Failed + // to exec spawn helper: pid: 8227, signal: 9 + // Presumably, this issue is caused by codesigning and the exec helper missing the + // entitlements required for library insertion. + "-Djdk.attach.allowAttachSelf=true", Jazzer.class.getName()); + + if (isAndroid()) { + // ManagementFactory wont work with Android + return stream; + } + + return Stream.concat(ManagementFactory.getRuntimeMXBean().getInputArguments().stream(), stream); } /** @@ -248,6 +297,27 @@ private static String appendWithPathListSeparator(String name, String... options return currentValue + File.pathSeparator + additionalOptions; } + private static Path findLibrary(List candidateNames) { + if (!isAndroid()) { + return findHostClangLibrary(candidateNames); + } + + for (String candidateName : candidateNames) { + String candidateFullPath = "/apex/com.android.runtime/lib64/bionic/" + candidateName; + File f = new File(candidateFullPath); + if (f.exists()) { + return Paths.get(candidateFullPath); + } + } + + Log.error( + String.format("Failed to find one of %s%n for Android", String.join(", ", candidateNames))); + Log.error("If fuzzing hwasan, make sure you have a hwasan build flashed to your device"); + + exit(1); + throw new IllegalStateException("not reached"); + } + private static Path findHostClangLibrary(List candidateNames) { for (String name : candidateNames) { Optional path = tryFindLibraryInJazzerNativeSanitizersDir(name); @@ -314,8 +384,23 @@ private static String hostClang() { return Optional.ofNullable(System.getenv("CC")).orElse("clang"); } + private static List hwasanLibNames() { + if (!isAndroid()) { + Log.error("HWAsan is only supported for Android. Please try --asan"); + exit(1); + } + + return singletonList("libclang_rt.hwasan-aarch64-android.so"); + } + private static List asanLibNames() { if (isLinux()) { + if (isAndroid()) { + Log.error("ASan is not supported for Android at this time. Use --hwasan for Address " + + "Sanitization on Android"); + exit(1); + } + // Since LLVM 15 sanitizer runtimes no longer have the architecture in the filename. return asList("libclang_rt.asan.so", "libclang_rt.asan-x86_64.so"); } else { @@ -325,6 +410,12 @@ private static List asanLibNames() { private static List ubsanLibNames() { if (isLinux()) { + if (isAndroid()) { + // return asList("libclang_rt.ubsan_standalone-aarch64-android.so"); + Log.error("ERROR: UBSan is not supported for Android at this time."); + exit(1); + } + return asList("libclang_rt.ubsan_standalone.so", "libclang_rt.ubsan_standalone-x86_64.so"); } else { return singletonList("libclang_rt.ubsan_osx_dynamic.dylib"); @@ -344,7 +435,15 @@ private static boolean isMacOs() { } private static boolean isPosix() { - return FileSystems.getDefault().supportedFileAttributeViews().contains("posix"); + return !isAndroid() && FileSystems.getDefault().supportedFileAttributeViews().contains("posix"); + } + + private static boolean isAndroid() { + return Boolean.parseBoolean(System.getProperty("jazzer.android", "false")); + } + + private static boolean isPosixOrAndroid() { + return isPosix() || isAndroid(); } private static byte[] readAllBytes(InputStream in) throws IOException { diff --git a/src/main/java/com/code_intelligence/jazzer/agent/AgentInstaller.java b/src/main/java/com/code_intelligence/jazzer/agent/AgentInstaller.java index 920eb4885..5006c243a 100644 --- a/src/main/java/com/code_intelligence/jazzer/agent/AgentInstaller.java +++ b/src/main/java/com/code_intelligence/jazzer/agent/AgentInstaller.java @@ -16,6 +16,7 @@ import static com.code_intelligence.jazzer.agent.AgentUtils.extractBootstrapJar; +import com.code_intelligence.jazzer.driver.Opt; import java.lang.instrument.Instrumentation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -35,6 +36,11 @@ public static void install(boolean enableAgent) { if (!hasBeenInstalled.compareAndSet(false, true)) { return; } + + if (Opt.isAndroid) { + return; + } + Instrumentation instrumentation = ByteBuddyAgent.install(); instrumentation.appendToBootstrapClassLoaderSearch(extractBootstrapJar()); if (!enableAgent) { 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 5437086bb..ff024fa23 100644 --- a/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel @@ -9,6 +9,7 @@ java_library( visibility = ["//visibility:public"], deps = [ ":agent_lib", + "//src/main/java/com/code_intelligence/jazzer/driver:opt", "@net_bytebuddy_byte_buddy_agent//jar", ], ) diff --git a/src/main/java/com/code_intelligence/jazzer/android/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/android/BUILD.bazel new file mode 100644 index 000000000..627544a76 --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/android/BUILD.bazel @@ -0,0 +1,29 @@ +load("//bazel:compat.bzl", "SKIP_ON_WINDOWS") + +android_library( + name = "jazzer_standalone_library", + tags = ["manual"], + 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", + ], +) + +android_binary( + name = "jazzer_standalone_android", + manifest = "//launcher/android:android_manifest", + min_sdk_version = 26, + tags = ["manual"], + target_compatible_with = SKIP_ON_WINDOWS, + visibility = [ + "//:__pkg__", + "//launcher/android:__pkg__", + ], + deps = [ + ":jazzer_standalone_library", + ], +) diff --git a/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel index 1acbfc03d..16dec1bae 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -29,6 +29,7 @@ kt_jvm_library( srcs = ["ExceptionUtils.kt"], visibility = ["//src/main/java/com/code_intelligence/jazzer/driver:__subpackages__"], deps = [ + ":opt", "//src/main/java/com/code_intelligence/jazzer/api:hooks", "//src/main/java/com/code_intelligence/jazzer/utils:log", ], @@ -146,4 +147,5 @@ java_jni_library( srcs = ["SignalHandler.java"], native_libs = ["//src/main/native/com/code_intelligence/jazzer/driver:jazzer_signal_handler"], visibility = ["//src/main/native/com/code_intelligence/jazzer/driver:__pkg__"], + deps = [":opt"], ) diff --git a/src/main/java/com/code_intelligence/jazzer/driver/Driver.java b/src/main/java/com/code_intelligence/jazzer/driver/Driver.java index 216195e05..e74c8b4f6 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/Driver.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/Driver.java @@ -21,6 +21,7 @@ import com.code_intelligence.jazzer.agent.AgentInstaller; import com.code_intelligence.jazzer.driver.junit.JUnitRunner; import com.code_intelligence.jazzer.utils.Log; +import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -31,6 +32,22 @@ public class Driver { public static int start(List args, boolean spawnsSubprocesses) throws IOException { + boolean isAndroid = Boolean.parseBoolean(System.getProperty("jazzer.android", "false")); + if (isAndroid) { + if (!System.getProperty("jazzer.autofuzz", "").isEmpty()) { + Log.error("--autofuzz is not supported for Android"); + return 1; + } + if (!System.getProperty("jazzer.coverage_report", "").isEmpty()) { + Log.warn("--coverage_report is not supported for Android and has been disabled"); + System.clearProperty("jazzer.coverage_report"); + } + if (!System.getProperty("jazzer.coverage_dump", "").isEmpty()) { + Log.warn("--coverage_dump is not supported for Android and has been disabled"); + System.clearProperty("jazzer.coverage_dump"); + } + } + if (spawnsSubprocesses) { if (!System.getProperty("jazzer.coverage_report", "").isEmpty()) { Log.warn("--coverage_report does not support parallel fuzzing and has been disabled"); @@ -48,7 +65,13 @@ public static int start(List args, boolean spawnsSubprocesses) throws IO // 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-", ""); + if (!isAndroid) { + idSyncFile = Files.createTempFile("jazzer-", ""); + } else { + File f = File.createTempFile("jazzer-", "", new File("/data/local/tmp/")); + idSyncFile = f.toPath(); + } + args.add("--id_sync_file=" + idSyncFile.toAbsolutePath()); } else { // Creates the file, truncating it if it exists. @@ -87,7 +110,9 @@ public static int start(List args, boolean spawnsSubprocesses) throws IO // Do not modify properties beyond this point, loading Opt locks in their values. - AgentInstaller.install(Opt.hooks); + if (!isAndroid) { + AgentInstaller.install(Opt.hooks); + } Driver.class.getClassLoader().setDefaultAssertionStatus(true); if (!Opt.autofuzz.isEmpty()) { diff --git a/src/main/java/com/code_intelligence/jazzer/driver/ExceptionUtils.kt b/src/main/java/com/code_intelligence/jazzer/driver/ExceptionUtils.kt index 21d74cf41..3bec53da5 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/ExceptionUtils.kt +++ b/src/main/java/com/code_intelligence/jazzer/driver/ExceptionUtils.kt @@ -96,16 +96,21 @@ fun preprocessThrowable(throwable: Throwable): Throwable = when (throwable) { val bottomFramesWithoutRepetition = throwable.stackTrace.takeLastWhile { frame -> (frame !in observedFrames).also { observedFrames.add(frame) } } - FuzzerSecurityIssueLow("Stack overflow (use '${getReproducingXssArg()}' to reproduce)", throwable).apply { + var securityIssueMessage = "Stack overflow" + if (!Opt.isAndroid) { + securityIssueMessage = "$securityIssueMessage (use '${getReproducingXssArg()}' to reproduce)" + } + FuzzerSecurityIssueLow(securityIssueMessage, throwable).apply { stackTrace = bottomFramesWithoutRepetition.toTypedArray() } } - is OutOfMemoryError -> stripOwnStackTrace( - FuzzerSecurityIssueLow( - "Out of memory (use '${getReproducingXmxArg()}' to reproduce)", - throwable, - ), - ) + is OutOfMemoryError -> { + var securityIssueMessage = "Out of memory" + if (!Opt.isAndroid) { + securityIssueMessage = "$securityIssueMessage (use '${getReproducingXmxArg()}' to reproduce)" + } + stripOwnStackTrace(FuzzerSecurityIssueLow(securityIssueMessage, throwable)) + } is VirtualMachineError -> stripOwnStackTrace(FuzzerSecurityIssueLow(throwable)) else -> throwable }.also { dropInternalFrames(it) } @@ -194,6 +199,12 @@ fun dumpAllStackTraces() { } Log.println("") } + + if (Opt.isAndroid) { + // ManagementFactory is not supported on Android + return + } + Log.println("Garbage collector stats:") Log.println( ManagementFactory.getGarbageCollectorMXBeans().joinToString("\n", "\n", "\n") { diff --git a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetFinder.java b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetFinder.java index b53069cc9..dcb7dc916 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetFinder.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetFinder.java @@ -37,6 +37,10 @@ static String findFuzzTargetClassName() { if (!Opt.targetClass.isEmpty()) { return Opt.targetClass; } + if (Opt.isAndroid) { + // Fuzz target detection tools aren't supported on android + return null; + } return ManifestUtils.detectFuzzTargetClass(); } @@ -48,7 +52,12 @@ static String findFuzzTargetClassName() { static FuzzTarget findFuzzTarget(String targetClassName) { Class fuzzTargetClass; try { - fuzzTargetClass = Class.forName(targetClassName); + if (Opt.isAndroid) { + fuzzTargetClass = + Class.forName(targetClassName, false, FuzzTargetFinder.class.getClassLoader()); + } else { + fuzzTargetClass = Class.forName(targetClassName); + } } catch (ClassNotFoundException e) { Log.error(String.format( "'%s' not found on classpath:%n%n%s%n%nAll required classes must be on the classpath specified via --cp.", diff --git a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index 8086f471a..fe325f24d 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -88,6 +88,12 @@ public final class FuzzTargetRunner { throw new IllegalStateException(e); } useFuzzedDataProvider = fuzzTarget.usesFuzzedDataProvider(); + if (!useFuzzedDataProvider && Opt.isAndroid) { + Log.error("Android fuzz targets must use " + FuzzedDataProvider.class.getName()); + exit(1); + throw new IllegalStateException("Not reached"); + } + fuzzerTearDown = fuzzTarget.tearDown.orElse(null); reproducerTemplate = new ReproducerTemplate(fuzzTargetClass.getName(), useFuzzedDataProvider); 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 30f5456a1..57e7c47a6 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/Opt.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/Opt.java @@ -68,6 +68,7 @@ public final class Opt { "asan", false, "Allow fuzzing of native libraries compiled with '-fsanitize=address'"); boolSetting( "ubsan", false, "Allow fuzzing of native libraries compiled with '-fsanitize=undefined'"); + boolSetting("hwasan", false, "Allow fuzzing of native libraries compiled with hwasan"); boolSetting("native", false, "Allow fuzzing of native libraries compiled with '-fsanitize=fuzzer' (implied by --asan and --ubsan)"); } @@ -129,6 +130,10 @@ public final class Opt { public static final boolean dedup = boolSetting("dedup", hooks, "Compute and print a deduplication token for every finding"); + // Default to false. Sets if fuzzing is taking place on Android device (virtual or physical) + public static final boolean isAndroid = + boolSetting("android", false, "Jazzer is running on Android"); + // 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/driver/SignalHandler.java b/src/main/java/com/code_intelligence/jazzer/driver/SignalHandler.java index 215a04794..0ad913c9e 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/SignalHandler.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/SignalHandler.java @@ -19,8 +19,10 @@ public final class SignalHandler { static { - RulesJni.loadLibrary("jazzer_signal_handler", SignalHandler.class); - Signal.handle(new Signal("INT"), sig -> handleInterrupt()); + if (!Opt.isAndroid) { + RulesJni.loadLibrary("jazzer_signal_handler", SignalHandler.class); + Signal.handle(new Signal("INT"), sig -> handleInterrupt()); + } } public static void initialize() { 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 85b2f695a..87d9a588b 100644 --- a/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel @@ -103,6 +103,7 @@ java_jni_library( "//src/test:__subpackages__", ], deps = [ + "//src/main/java/com/code_intelligence/jazzer/driver:opt", "//src/main/java/com/code_intelligence/jazzer/utils:unsafe_provider", ], ) @@ -120,6 +121,9 @@ java_jni_library( name = "fuzz_target_runner_natives", srcs = ["FuzzTargetRunnerNatives.java"], visibility = ["//src/main/native/com/code_intelligence/jazzer/driver:__pkg__"], + deps = [ + "//src/main/java/com/code_intelligence/jazzer/driver:opt", + ], ) java_library( @@ -133,6 +137,7 @@ java_library( "TraceIndirHooks.java", ], visibility = [ + "//src/main/java/com/code_intelligence/jazzer/android:__pkg__", "//src/main/native/com/code_intelligence/jazzer/driver:__pkg__", "//src/test:__subpackages__", ], @@ -146,5 +151,6 @@ java_library( ":coverage_map", ":trace_data_flow_native_callbacks", "//src/main/java/com/code_intelligence/jazzer/api:hooks", + "//src/main/java/com/code_intelligence/jazzer/driver:opt", ], ) diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/FuzzTargetRunnerNatives.java b/src/main/java/com/code_intelligence/jazzer/runtime/FuzzTargetRunnerNatives.java index b60b19358..e4f2a72c3 100644 --- a/src/main/java/com/code_intelligence/jazzer/runtime/FuzzTargetRunnerNatives.java +++ b/src/main/java/com/code_intelligence/jazzer/runtime/FuzzTargetRunnerNatives.java @@ -14,6 +14,7 @@ package com.code_intelligence.jazzer.runtime; +import com.code_intelligence.jazzer.driver.Opt; import com.github.fmeum.rules_jni.RulesJni; /** @@ -25,7 +26,7 @@ */ public class FuzzTargetRunnerNatives { static { - if (FuzzTargetRunnerNatives.class.getClassLoader() != null) { + if (!Opt.isAndroid && FuzzTargetRunnerNatives.class.getClassLoader() != null) { throw new IllegalStateException( "FuzzTargetRunnerNatives must be loaded in the bootstrap loader"); } diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java b/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java index 495cad7c4..01573f452 100644 --- a/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java +++ b/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java @@ -16,6 +16,7 @@ import com.code_intelligence.jazzer.api.HookType; import com.code_intelligence.jazzer.api.MethodHook; +import com.code_intelligence.jazzer.driver.Opt; import java.lang.invoke.MethodHandle; @SuppressWarnings("unused") @@ -30,6 +31,10 @@ final public class NativeLibHooks { targetMethodDescriptor = "(Ljava/lang/String;)V") public static void loadLibraryHook(MethodHandle method, Object thisObject, Object[] arguments, int hookId) { + if (Opt.isAndroid) { + return; + } + TraceDataFlowNativeCallbacks.handleLibraryLoad(); } } diff --git a/src/main/native/com/code_intelligence/jazzer/driver/init_jazzer_preload.cpp b/src/main/native/com/code_intelligence/jazzer/driver/init_jazzer_preload.cpp index 600784eba..23a86c536 100644 --- a/src/main/native/com/code_intelligence/jazzer/driver/init_jazzer_preload.cpp +++ b/src/main/native/com/code_intelligence/jazzer/driver/init_jazzer_preload.cpp @@ -17,6 +17,12 @@ #include +#if defined(_ANDROID) +#define __jni_version__ JNI_VERSION_1_6 +#else +#define __jni_version__ JNI_VERSION_1_8 +#endif + // The jazzer_preload library, 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 @@ -46,5 +52,5 @@ jint JNI_OnLoad(JavaVM *, void *) { dlclose(handle); - return JNI_VERSION_1_8; + return __jni_version__; } diff --git a/third_party/android/BUILD b/third_party/android/BUILD new file mode 100644 index 000000000..e69de29bb diff --git a/third_party/android/android_configure.bzl b/third_party/android/android_configure.bzl new file mode 100644 index 000000000..4b634b4f6 --- /dev/null +++ b/third_party/android/android_configure.bzl @@ -0,0 +1,52 @@ +"""Repository rule for Android SKD and NDK autoconfigure""" + +load("@build_bazel_rules_android//android:rules.bzl", "android_sdk_repository") +load("@rules_android_ndk//:rules.bzl", "android_ndk_repository") + +_ANDROID_SDK_HOME = "ANDROID_HOME" +_ANDROID_NDK_HOME = "ANDROID_NDK_HOME" + +_ANDROID_REPOS_TEMPLATE = """android_sdk_repository( + name="androidsdk", + path={sdk_home}, + ) + android_ndk_repository( + name="androidndk", + path={ndk_home}, + ) +""" + +def _is_windows(repository_ctx): + """Returns true if the current platform is Windows""" + return repository_ctx.os.name.lower().startswith("windows") + +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): + repos = _ANDROID_REPOS_TEMPLATE.format( + sdk_home = repr(sdk_home), + ndk_home = repr(ndk_home), + ) + else: + repos = "pass" + + repository_ctx.file("BUILD.bazel", "") + repository_ctx.file("android_configure.bzl", """ +load("@build_bazel_rules_android//android:rules.bzl", "android_sdk_repository") +load("@rules_android_ndk//:rules.bzl", "android_ndk_repository") + +def android_workspace(): + {repos} + """.format(repos = repos)) + +android_configure = repository_rule( + implementation = _android_autoconf_impl, + environ = [ + _ANDROID_SDK_HOME, + _ANDROID_NDK_HOME, + ], +)