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,
+ ],
+)