From 07bd293d2f686a7b8131d2617602a8e5b7640ebc Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 21 Mar 2023 18:11:15 +0100 Subject: [PATCH] launcher: Use actual executable path instead of `argv[0]` When Jazzer is executed from `PATH`, `argv[0]` will just be `jazzer`, which we can't find `jazzer_standalone.jar` relative to. --- BUILD.bazel | 1 + launcher/fuzzed_data_provider_test.cpp | 2 +- launcher/jazzer_main.cpp | 2 +- launcher/jvm_tooling.cpp | 56 ++++++++++++++----- launcher/jvm_tooling.h | 2 +- launcher/jvm_tooling_test.cpp | 2 +- tests/BUILD.bazel | 10 ++++ tests/src/test/shell/jazzer_from_path_test.sh | 43 ++++++++++++++ 8 files changed, 100 insertions(+), 18 deletions(-) create mode 100755 tests/src/test/shell/jazzer_from_path_test.sh diff --git a/BUILD.bazel b/BUILD.bazel index 74fc4ade5..a5cfb93fe 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -18,6 +18,7 @@ pkg_tar( "@platforms//os:windows": ".\\", "//conditions:default": "./", }), + visibility = ["//tests:__pkg__"], ) alias( diff --git a/launcher/fuzzed_data_provider_test.cpp b/launcher/fuzzed_data_provider_test.cpp index c843052f0..e95c5920e 100644 --- a/launcher/fuzzed_data_provider_test.cpp +++ b/launcher/fuzzed_data_provider_test.cpp @@ -39,7 +39,7 @@ class FuzzedDataProviderTest : public ::testing::Test { FLAGS_cp = Runfiles::CreateForTest()->Rlocation( "jazzer/launcher/testdata/fuzz_target_mocks_deploy.jar"); - jvm_ = std::make_unique("test_executable"); + jvm_ = std::make_unique(); } static void TearDownTestCase() { jvm_.reset(nullptr); } diff --git a/launcher/jazzer_main.cpp b/launcher/jazzer_main.cpp index 47fe91d9c..d2c6e294b 100644 --- a/launcher/jazzer_main.cpp +++ b/launcher/jazzer_main.cpp @@ -104,6 +104,6 @@ int main(int argc, char **argv) { } } - StartLibFuzzer(std::unique_ptr(new jazzer::JVM(argv[0])), + StartLibFuzzer(std::unique_ptr(new jazzer::JVM()), std::vector(argv + 1, argv + argc)); } diff --git a/launcher/jvm_tooling.cpp b/launcher/jvm_tooling.cpp index 2c6c06fe5..d93b32311 100644 --- a/launcher/jvm_tooling.cpp +++ b/launcher/jvm_tooling.cpp @@ -16,6 +16,13 @@ #if defined(_ANDROID) #include +#elif defined(__APPLE__) +#include +#elif defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#include +#else // Assume Linux +#include #endif #include @@ -50,6 +57,24 @@ constexpr auto kJazzerBazelRunfilesPath = "jazzer_standalone_deploy.jar"; constexpr auto kJazzerFileName = "jazzer_standalone.jar"; +// Returns the absolute path to the current executable. Compared to argv[0], +// this path can always be used to locate the Jazzer JAR next to it, even when +// Jazzer is executed from PATH. +std::string getExecutablePath() { + char buf[655536]; +#if defined(__APPLE__) + uint32_t buf_size = sizeof(buf); + if (_NSGetExecutablePath(buf, &buf_size) != 0) { +#elif defined(_WIN32) + if (GetModuleFileNameA(NULL, buf, sizeof(buf)) == 0) { +#else // Assume Linux + if (readlink("/proc/self/exe", buf, sizeof(buf)) == -1) { +#endif + return ""; + } + return {buf}; +} + std::string dirFromFullPath(const std::string &path) { const auto pos = path.rfind(kPathSeparator); if (pos != std::string::npos) { @@ -60,7 +85,7 @@ std::string dirFromFullPath(const std::string &path) { // getInstrumentorAgentPath searches for the fuzzing instrumentation agent and // returns the location if it is found. Otherwise it calls exit(0). -std::string getInstrumentorAgentPath(const std::string &executable_path) { +std::string getInstrumentorAgentPath() { // User provided agent location takes precedence. if (!FLAGS_agent_path.empty()) { if (std::ifstream(FLAGS_agent_path).good()) return FLAGS_agent_path; @@ -68,9 +93,12 @@ std::string getInstrumentorAgentPath(const std::string &executable_path) { << FLAGS_agent_path << "\"" << std::endl; exit(1); } - // First check if we are running inside the Bazel tree and use the agent - // runfile. - { + + auto executable_path = getExecutablePath(); + + if (!executable_path.empty()) { + // First check if we are running inside the Bazel tree and use the agent + // runfile. using bazel::tools::cpp::runfiles::Runfiles; std::string error; std::unique_ptr runfiles(Runfiles::Create( @@ -80,13 +108,14 @@ std::string getInstrumentorAgentPath(const std::string &executable_path) { if (!bazel_path.empty() && std::ifstream(bazel_path).good()) return bazel_path; } + + // If the agent is not in the bazel path we look next to the jazzer binary. + const auto dir = dirFromFullPath(executable_path); + auto agent_path = + absl::StrFormat("%s%c%s", dir, kPathSeparator, kJazzerFileName); + if (std::ifstream(agent_path).good()) return agent_path; } - // If the agent is not in the bazel path we look next to the jazzer binary. - const auto dir = dirFromFullPath(executable_path); - auto agent_path = - absl::StrFormat("%s%c%s", dir, kPathSeparator, kJazzerFileName); - if (std::ifstream(agent_path).good()) return agent_path; std::cerr << "ERROR: Could not find " << kJazzerFileName << ". Please provide the pathname via the --agent_path flag." << std::endl; @@ -170,7 +199,7 @@ JNI_CreateJavaVM_t LoadAndroidVMLibs() { } #endif -std::string GetClassPath(const std::string &executable_path) { +std::string GetClassPath() { // 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); @@ -179,13 +208,12 @@ std::string GetClassPath(const std::string &executable_path) { class_path += absl::StrCat(ARG_SEPARATOR, class_path_from_env); } - class_path += - absl::StrCat(ARG_SEPARATOR, getInstrumentorAgentPath(executable_path)); + class_path += absl::StrCat(ARG_SEPARATOR, getInstrumentorAgentPath()); return class_path; } -JVM::JVM(const std::string &executable_path) { - std::string class_path = GetClassPath(executable_path); +JVM::JVM() { + std::string class_path = GetClassPath(); std::vector options; options.push_back( diff --git a/launcher/jvm_tooling.h b/launcher/jvm_tooling.h index 0ea1639ec..d7129a130 100644 --- a/launcher/jvm_tooling.h +++ b/launcher/jvm_tooling.h @@ -40,7 +40,7 @@ class JVM { public: // Creates a JVM instance with default options + options that were provided as // command line flags. - explicit JVM(const std::string &executable_path); + explicit JVM(); // Destroy the running JVM instance. ~JVM(); diff --git a/launcher/jvm_tooling_test.cpp b/launcher/jvm_tooling_test.cpp index 91ad8533d..8cfb6bc6e 100644 --- a/launcher/jvm_tooling_test.cpp +++ b/launcher/jvm_tooling_test.cpp @@ -39,7 +39,7 @@ class JvmToolingTest : public ::testing::Test { FLAGS_cp = Runfiles::CreateForTest()->Rlocation( "jazzer/launcher/testdata/fuzz_target_mocks_deploy.jar"); - jvm_ = std::unique_ptr(new JVM("test_executable")); + jvm_ = std::unique_ptr(new JVM()); } static void TearDownTestCase() { jvm_.reset(nullptr); } diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 27ded85cd..103a20702 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -445,3 +445,13 @@ java_fuzz_target_test( "//tests/src/test/proto:simple_java_proto", ], ) + +sh_test( + name = "jazzer_from_path_test", + srcs = ["src/test/shell/jazzer_from_path_test.sh"], + args = ["$(rlocationpath //:jazzer_release)"], + data = [ + "//:jazzer_release", + "@bazel_tools//tools/bash/runfiles", + ], +) diff --git a/tests/src/test/shell/jazzer_from_path_test.sh b/tests/src/test/shell/jazzer_from_path_test.sh new file mode 100755 index 000000000..357fde680 --- /dev/null +++ b/tests/src/test/shell/jazzer_from_path_test.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Copyright 2022 Code Intelligence GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Verify that the Jazzer launcher finds the jar when executed from PATH. + + +# --- begin runfiles.bash initialization v3 --- +# Copy-pasted from the Bazel Bash runfiles library v3. +set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e +# --- end runfiles.bash initialization v3 --- + +# Unpack the release archive to a temporary directory. +jazzer_release="$(rlocation "$1")" +tmp="$(mktemp -d)" +trap 'rm -r "$tmp"' EXIT +# GNU tar on Windows requires --force-local to support colons in archives names, +# macOS tar does not support it. +tar -xzf "$jazzer_release" -C "$tmp" --force-local || tar -xzf "$jazzer_release" -C "$tmp" + +# Add the Jazzer launcher to PATH first so that it is picked over host Jazzer +# installations. +PATH="$tmp:$PATH" +export PATH + +jazzer --version