blob: 88d921817a8d2386d54819eaba86bddc94b3fb8b [file] [log] [blame]
/*
* Copyright (C) 2023 The Android Open Source Project
*
* 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 <cstdint>
#include <filesystem>
#include <unordered_set>
#include "android-base/file.h"
#include "android-base/macros.h"
#include "common_runtime_test.h"
#include "fuzzer_common.h"
#include "gtest/gtest.h"
#include "handle_scope-inl.h"
#include "ziparchive/zip_archive.h"
namespace art {
// Manages the ZipArchiveHandle liveness.
class ZipArchiveHandleScope {
public:
explicit ZipArchiveHandleScope(ZipArchiveHandle* handle) : handle_(handle) {}
~ZipArchiveHandleScope() { CloseArchive(*(handle_.release())); }
private:
std::unique_ptr<ZipArchiveHandle> handle_;
};
class FuzzerCorpusTest : public CommonRuntimeTest {
public:
static void DexFileVerification(const uint8_t* data,
size_t size,
const std::string& name,
bool expected_success) {
const bool is_valid_dex_file = fuzzer::VerifyDexFile(data, size, name) != nullptr;
ASSERT_EQ(is_valid_dex_file, expected_success) << " Failed for " << name;
}
static void ClassVerification(const uint8_t* data,
size_t size,
const std::string& name,
bool expected_success) {
std::unique_ptr<StandardDexFile> dex_file = fuzzer::VerifyDexFile(data, size, name);
ASSERT_EQ(dex_file != nullptr, true) << " Failed for " << name;
Runtime* runtime = Runtime::Current();
CHECK(runtime != nullptr);
jobject class_loader = fuzzer::RegisterDexFileAndGetClassLoader(runtime, dex_file.get());
const bool passed_class_verification = fuzzer::VerifyClasses(class_loader, dex_file.get());
fuzzer::IterationCleanup(class_loader, dex_file.get());
ASSERT_EQ(passed_class_verification, expected_success) << " Failed for " << name;
}
static void OptimizedCompilation(const uint8_t* data,
size_t size,
const std::string& name,
bool expected_success) {
std::unique_ptr<StandardDexFile> dex_file = fuzzer::VerifyDexFile(data, size, name);
ASSERT_EQ(dex_file != nullptr, true) << " Failed for " << name;
Runtime* runtime = Runtime::Current();
CHECK(runtime != nullptr);
fuzzer::FuzzerCompiledMethodStorage storage;
std::unique_ptr<fuzzer::FuzzerCompilerCallbacks> callbacks(
new fuzzer::FuzzerCompilerCallbacks());
std::unique_ptr<CompilerOptions> compiler_options = fuzzer::CreateCompilerOptions();
std::unique_ptr<Compiler> compiler(fuzzer::CreateCompiler(*compiler_options, &storage));
jobject class_loader = fuzzer::RegisterDexFileAndGetClassLoader(runtime, dex_file.get());
fuzzer::VerifyClasses(class_loader, dex_file.get());
const bool at_least_one_method_called_the_compiler = fuzzer::CompileClasses(
class_loader, dex_file.get(), compiler.get(), callbacks.get(), /*kDebugPrints=*/false);
// Note: no need to reset callbacks as they will get destroyed
fuzzer::IterationCleanup(class_loader, dex_file.get());
ASSERT_EQ(at_least_one_method_called_the_compiler, expected_success) << " Failed for " << name;
}
void TestFuzzerHelper(
const std::string& archive_filename,
const std::unordered_set<std::string>& valid_dex_files,
std::function<void(const uint8_t*, size_t, const std::string&, bool)> verify_file) {
// Consistency checks.
const std::string folder = android::base::GetExecutableDirectory();
ASSERT_TRUE(std::filesystem::is_directory(folder)) << folder << " is not a folder";
ASSERT_FALSE(std::filesystem::is_empty(folder)) << " No files found for directory " << folder;
const std::string filename = folder + "/" + archive_filename;
// Iterate using ZipArchiveHandle. We have to be careful about managing the pointers with
// CloseArchive, StartIteration, and EndIteration.
std::string error_msg;
ZipArchiveHandle handle;
ZipArchiveHandleScope scope(&handle);
int32_t error = OpenArchive(filename.c_str(), &handle);
ASSERT_TRUE(error == 0) << "Error: " << error;
void* cookie;
error = StartIteration(handle, &cookie);
ASSERT_TRUE(error == 0) << "couldn't iterate " << filename << " : " << ErrorCodeString(error);
ZipEntry64 entry;
std::string name;
std::vector<char> data;
while ((error = Next(cookie, &entry, &name)) >= 0) {
if (!name.ends_with(".dex")) {
// Skip non-DEX files.
LOG(WARNING) << "Found a non-dex file: " << name;
continue;
}
data.resize(entry.uncompressed_length);
error = ExtractToMemory(handle, &entry, reinterpret_cast<uint8_t*>(data.data()), data.size());
ASSERT_TRUE(error == 0) << "failed to extract entry: " << name << " from " << filename << ""
<< ErrorCodeString(error);
const uint8_t* file_data = reinterpret_cast<const uint8_t*>(data.data());
// Special case for empty dex file. Set a fake data since the size is 0 anyway.
if (file_data == nullptr) {
ASSERT_EQ(data.size(), 0);
file_data = reinterpret_cast<const uint8_t*>(&name);
}
const bool is_valid_dex_file = valid_dex_files.find(name) != valid_dex_files.end();
verify_file(file_data, data.size(), name, is_valid_dex_file);
}
ASSERT_TRUE(error >= -1) << "failed iterating " << filename << " : " << ErrorCodeString(error);
EndIteration(cookie);
}
};
// Tests that we can verify dex files without crashing.
TEST_F(FuzzerCorpusTest, VerifyCorpusDexFiles) {
// These dex files are expected to pass verification. The others are regressions tests.
const std::unordered_set<std::string> valid_dex_files = {"Main.dex", "hello_world.dex"};
const std::string archive_filename = "dex_verification_fuzzer_corpus.zip";
TestFuzzerHelper(archive_filename, valid_dex_files, DexFileVerification);
}
// Tests that we can verify classes from dex files without crashing.
TEST_F(FuzzerCorpusTest, VerifyCorpusClassDexFiles) {
// These dex files are expected to pass verification. The others are regressions tests.
const std::unordered_set<std::string> valid_dex_files = {"Main.dex", "hello_world.dex"};
const std::string archive_filename = "class_verification_fuzzer_corpus.zip";
TestFuzzerHelper(archive_filename, valid_dex_files, ClassVerification);
}
// Tests that we can compile classes with kOptimizing from dex files without crashing.
TEST_F(FuzzerCorpusTest, OptimizeCompileDexFiles) {
// These dex files are expected to pass verification. The others are regressions tests.
const std::unordered_set<std::string> valid_dex_files = {"Main.dex", "hello_world.dex"};
const std::string archive_filename = "optimized_compiler_fuzzer_corpus.zip";
TestFuzzerHelper(archive_filename, valid_dex_files, OptimizedCompilation);
}
} // namespace art