summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Ioana-Teodora Isar <ioanaisar@google.com> 2024-07-29 15:01:38 +0000
committer Ioana-Teodora Isar <ioanaisar@google.com> 2024-08-14 10:19:56 +0000
commitb9075fca15c21ce4ea599e5379463c4fda82660a (patch)
treefcabd8b0214b3cc3bd824220b7ca6d67736de12c
parentef7d905b284800eb7a6858933e457eced5b7305e (diff)
Add a gtest to automatically test the class verification fuzzer's corpus.
Rename the DEX verification fuzzer's corpus directory. Add a separated corpus for class verification fuzzer that contains two DEX files: * Main: Empty main (valid DEX) * Hello-world: Main that prints "Hello, world!" It tries to verify each dex file in the class verification fuzzer's corpus. For now all dex files for class verification are expected to pass. Bug: 352721437 Test: m test-art-host-gtest-art_runtime_tests64 Test: m test-art-host-gtest-art_libdexfile_tests64 Test: With chroot method run libdexfile and runtime tests on targe Change-Id: I7dfbdecb28cd3adf36db83c4e158794774d3b907
-rwxr-xr-xbuild/apex/art_apex_test.py3
-rw-r--r--runtime/Android.bp3
-rw-r--r--runtime/art_standalone_runtime_tests.xml3
-rw-r--r--runtime/fuzzer_corpus_test.cc224
-rw-r--r--tools/fuzzer/Android.bp25
-rw-r--r--tools/fuzzer/class-verifier-corpus/Main.dex (renamed from tools/fuzzer/corpus/Main.dex)bin564 -> 564 bytes
-rw-r--r--tools/fuzzer/class-verifier-corpus/hello_world.dex (renamed from tools/fuzzer/corpus/hello_world.dex)bin756 -> 756 bytes
-rw-r--r--tools/fuzzer/dex-verifier-corpus/Main.dexbin0 -> 564 bytes
-rw-r--r--tools/fuzzer/dex-verifier-corpus/b323685074.dex (renamed from tools/fuzzer/corpus/b323685074.dex)bin1083 -> 1083 bytes
-rw-r--r--tools/fuzzer/dex-verifier-corpus/code_item_padding.dex (renamed from tools/fuzzer/corpus/code_item_padding.dex)bin567 -> 567 bytes
-rw-r--r--tools/fuzzer/dex-verifier-corpus/empty.dex (renamed from tools/fuzzer/corpus/empty.dex)0
-rw-r--r--tools/fuzzer/dex-verifier-corpus/encoded_array_value.dex (renamed from tools/fuzzer/corpus/encoded_array_value.dex)bin3428 -> 3428 bytes
-rw-r--r--tools/fuzzer/dex-verifier-corpus/encoded_array_value2.dex (renamed from tools/fuzzer/corpus/encoded_array_value2.dex)bin1864 -> 1864 bytes
-rw-r--r--tools/fuzzer/dex-verifier-corpus/hello_world.dexbin0 -> 756 bytes
-rw-r--r--tools/fuzzer/dex-verifier-corpus/recursive_encoded_array.dex (renamed from tools/fuzzer/corpus/recursive_encoded_array.dex)bin108883 -> 108883 bytes
15 files changed, 190 insertions, 68 deletions
diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py
index 68fe779a9e..d435cd77ef 100755
--- a/build/apex/art_apex_test.py
+++ b/build/apex/art_apex_test.py
@@ -793,7 +793,8 @@ class TestingTargetChecker:
self._checker.check_art_test_data('art-gtest-jars-SuperWithAccessChecks.dex')
# Fuzzer cases
- self._checker.check_art_test_data('fuzzer_corpus.zip')
+ self._checker.check_art_test_data('dex_verification_fuzzer_corpus.zip')
+ self._checker.check_art_test_data('class_verification_fuzzer_corpus.zip')
class NoSuperfluousFilesChecker:
diff --git a/runtime/Android.bp b/runtime/Android.bp
index fa6a15292d..244daeae2c 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -981,7 +981,8 @@ art_cc_defaults {
},
data: [
":art-gtest-jars-AllFields",
- ":art-gtest-jars-DexFuzzerFolder",
+ ":art-gtest-jars-DexVerificationFuzzerFolder",
+ ":art-gtest-jars-ClassVerificationFuzzerFolder",
":art-gtest-jars-ErroneousA",
":art-gtest-jars-ErroneousB",
":art-gtest-jars-ErroneousInit",
diff --git a/runtime/art_standalone_runtime_tests.xml b/runtime/art_standalone_runtime_tests.xml
index 50a771709e..7e61731893 100644
--- a/runtime/art_standalone_runtime_tests.xml
+++ b/runtime/art_standalone_runtime_tests.xml
@@ -62,7 +62,8 @@
<option name="push" value="art-gtest-jars-VerifierDeps.dex->/data/local/tmp/art_standalone_runtime_tests/art-gtest-jars-VerifierDeps.dex" />
<option name="push" value="art-gtest-jars-VerifierDepsMulti.dex->/data/local/tmp/art_standalone_runtime_tests/art-gtest-jars-VerifierDepsMulti.dex" />
<option name="push" value="art-gtest-jars-XandY.jar->/data/local/tmp/art_standalone_runtime_tests/art-gtest-jars-XandY.jar" />
- <option name="push" value="fuzzer_corpus.zip->/data/local/tmp/art_standalone_runtime_tests/fuzzer_corpus.zip" />
+ <option name="push" value="dex_verification_fuzzer_corpus.zip->/data/local/tmp/art_standalone_runtime_tests/dex_verification_fuzzer_corpus.zip" />
+ <option name="push" value="class_verification_fuzzer_corpus.zip->/data/local/tmp/art_standalone_runtime_tests/class_verification_fuzzer_corpus.zip" />
</target_preparer>
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
diff --git a/runtime/fuzzer_corpus_test.cc b/runtime/fuzzer_corpus_test.cc
index 24057e536d..17520f172e 100644
--- a/runtime/fuzzer_corpus_test.cc
+++ b/runtime/fuzzer_corpus_test.cc
@@ -19,28 +19,58 @@
#include <unordered_set>
#include "android-base/file.h"
+#include "common_runtime_test.h"
+#include "dex/class_accessor-inl.h"
#include "dex/dex_file_verifier.h"
#include "dex/standard_dex_file.h"
#include "gtest/gtest.h"
+#include "handle_scope-inl.h"
+#include "jni/java_vm_ext.h"
+#include "verifier/class_verifier.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())); }
-class FuzzerCorpusTest : public testing::Test {
+ private:
+ std::unique_ptr<ZipArchiveHandle> handle_;
+};
+
+class FuzzerCorpusTest : public CommonRuntimeTest {
public:
- static void VerifyDexFile(const uint8_t* data,
- size_t size,
- const std::string& name,
- bool expected_success) {
+ static void DexFileVerification(const uint8_t* data,
+ size_t size,
+ const std::string& name,
+ bool expected_success) {
// Do not verify the checksum as we only care about the DEX file contents,
// and know that the checksum would probably be erroneous (i.e. random).
constexpr bool kVerify = false;
- // Special case for empty dex file. Set a fake data since the size is 0 anyway.
- if (data == nullptr) {
- ASSERT_EQ(size, 0);
- data = reinterpret_cast<const uint8_t*>(&name);
- }
+ auto container = std::make_shared<art::MemoryDexFileContainer>(data, size);
+ art::StandardDexFile dex_file(data,
+ /*location=*/name,
+ /*location_checksum=*/0,
+ /*oat_dex_file=*/nullptr,
+ container);
+
+ std::string error_msg;
+ bool is_valid_dex_file =
+ art::dex::Verify(&dex_file, dex_file.GetLocation().c_str(), kVerify, &error_msg);
+ 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) {
+ // Do not verify the checksum as we only care about the DEX file contents,
+ // and know that the checksum would probably be erroneous (i.e. random)
+ constexpr bool kVerify = false;
+ bool passed_class_verification = true;
auto container = std::make_shared<art::MemoryDexFileContainer>(data, size);
art::StandardDexFile dex_file(data,
@@ -50,66 +80,144 @@ class FuzzerCorpusTest : public testing::Test {
container);
std::string error_msg;
- bool success = art::dex::Verify(&dex_file, dex_file.GetLocation().c_str(), kVerify, &error_msg);
- ASSERT_EQ(success, expected_success) << " Failed for " << name;
+ const bool success_dex =
+ art::dex::Verify(&dex_file, dex_file.GetLocation().c_str(), kVerify, &error_msg);
+ ASSERT_EQ(success_dex, true) << " Failed for " << name;
+
+ art::Runtime* runtime = art::Runtime::Current();
+ CHECK(runtime != nullptr);
+
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ art::ClassLinker* class_linker = runtime->GetClassLinker();
+ jobject class_loader = RegisterDexFileAndGetClassLoader(runtime, &dex_file);
+
+ // Scope for the handles
+ {
+ art::StackHandleScope<3> scope(soa.Self());
+ art::Handle<art::mirror::ClassLoader> h_loader =
+ scope.NewHandle(soa.Decode<art::mirror::ClassLoader>(class_loader));
+
+ for (ClassAccessor accessor : dex_file.GetClasses()) {
+ const char* descriptor = accessor.GetDescriptor();
+ const art::Handle<art::mirror::Class> h_klass(scope.NewHandle<art::mirror::Class>(
+ class_linker->FindClass(soa.Self(), descriptor, h_loader)));
+ const art::Handle<art::mirror::DexCache> h_dex_cache(
+ scope.NewHandle<art::mirror::DexCache>(h_klass->GetDexCache()));
+
+ // Ignore classes that couldn't be loaded since we are looking for crashes during
+ // class/method verification.
+ if (h_klass == nullptr || h_klass->IsErroneous()) {
+ soa.Self()->ClearException();
+ continue;
+ }
+
+ verifier::FailureKind failure =
+ art::verifier::ClassVerifier::VerifyClass(soa.Self(),
+ /* verifier_deps= */ nullptr,
+ h_dex_cache->GetDexFile(),
+ h_klass,
+ h_dex_cache,
+ h_loader,
+ *h_klass->GetClassDef(),
+ runtime->GetCompilerCallbacks(),
+ art::verifier::HardFailLogMode::kLogWarning,
+ /* api_level= */ 0,
+ &error_msg);
+ if (failure != verifier::FailureKind::kNoFailure) {
+ passed_class_verification = false;
+ }
+ }
+ }
+
+ // Delete global ref and unload class loader to free RAM.
+ soa.Env()->GetVm()->DeleteGlobalRef(soa.Self(), class_loader);
+ // Do a GC to unregister the dex files.
+ runtime->GetHeap()->CollectGarbage(/* clear_soft_references= */ true);
+
+ ASSERT_EQ(passed_class_verification, expected_success) << " Failed for " << name;
}
-};
-// Class that manages the ZipArchiveHandle liveness.
-class ZipArchiveHandleScope {
- public:
- explicit ZipArchiveHandleScope(ZipArchiveHandle* handle) : handle_(handle) {}
- ~ZipArchiveHandleScope() { CloseArchive(*(handle_.release())); }
+ 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);
+ }
private:
- std::unique_ptr<ZipArchiveHandle> handle_;
+ static jobject RegisterDexFileAndGetClassLoader(art::Runtime* runtime,
+ art::StandardDexFile* dex_file)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ art::Thread* self = art::Thread::Current();
+ art::ClassLinker* class_linker = runtime->GetClassLinker();
+ const std::vector<const art::DexFile*> dex_files = {dex_file};
+ jobject class_loader = class_linker->CreatePathClassLoader(self, dex_files);
+ art::ObjPtr<art::mirror::ClassLoader> cl = self->DecodeJObject(class_loader)->AsClassLoader();
+ class_linker->RegisterDexFile(*dex_file, cl);
+ return class_loader;
+ }
};
// 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";
- // 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 + "/fuzzer_corpus.zip";
-
- // 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 bool expected_success = valid_dex_files.find(name) != valid_dex_files.end();
- VerifyDexFile(
- reinterpret_cast<const uint8_t*>(data.data()), data.size(), name, expected_success);
- }
+ 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";
- ASSERT_TRUE(error >= -1) << "failed iterating " << filename << " : " << ErrorCodeString(error);
- EndIteration(cookie);
+ TestFuzzerHelper(archive_filename, valid_dex_files, ClassVerification);
}
} // namespace art
diff --git a/tools/fuzzer/Android.bp b/tools/fuzzer/Android.bp
index f129f8ed7e..4042250476 100644
--- a/tools/fuzzer/Android.bp
+++ b/tools/fuzzer/Android.bp
@@ -100,7 +100,7 @@ cc_fuzz {
// Can not be in defaults due to soong limitations.
corpus: [
":art_runtest_corpus",
- "corpus/*",
+ "dex-verifier-corpus/*",
],
}
@@ -113,7 +113,7 @@ cc_fuzz {
// Can not be in defaults due to soong limitations.
corpus: [
":art_runtest_corpus",
- "corpus/*",
+ "dex-verifier-corpus/*",
],
}
@@ -127,7 +127,7 @@ cc_fuzz {
// Can not be in defaults due to soong limitations.
corpus: [
":art_runtest_corpus",
- "corpus/*",
+ "class-verifier-corpus/*",
],
}
@@ -1319,12 +1319,23 @@ genrule {
}
genrule {
- name: "art-gtest-jars-DexFuzzerFolder",
- // Zip the corpus folder. To get the folder, we grab the first file
+ name: "art-gtest-jars-DexVerificationFuzzerFolder",
+ // Zip the dex-verifier-corpus folder. To get the folder, we grab the first file
// from `in` and use its directory.
cmd: "FILES=($(in)) &&" +
"$(location soong_zip) -j -L 0 -o $(out) -D $$(dirname $${FILES[0]})",
- srcs: ["corpus/*"],
- out: ["fuzzer_corpus.zip"],
+ srcs: ["dex-verifier-corpus/*"],
+ out: ["dex_verification_fuzzer_corpus.zip"],
+ tools: ["soong_zip"],
+}
+
+genrule {
+ name: "art-gtest-jars-ClassVerificationFuzzerFolder",
+ // Zip the class-verifier-corpus folder. To get the folder, we grab the first file
+ // from `in` and use its directory.
+ cmd: "FILES=($(in)) &&" +
+ "$(location soong_zip) -j -L 0 -o $(out) -D $$(dirname $${FILES[0]})",
+ srcs: ["class-verifier-corpus/*"],
+ out: ["class_verification_fuzzer_corpus.zip"],
tools: ["soong_zip"],
}
diff --git a/tools/fuzzer/corpus/Main.dex b/tools/fuzzer/class-verifier-corpus/Main.dex
index ec29035983..ec29035983 100644
--- a/tools/fuzzer/corpus/Main.dex
+++ b/tools/fuzzer/class-verifier-corpus/Main.dex
Binary files differ
diff --git a/tools/fuzzer/corpus/hello_world.dex b/tools/fuzzer/class-verifier-corpus/hello_world.dex
index 6e59dd76ad..6e59dd76ad 100644
--- a/tools/fuzzer/corpus/hello_world.dex
+++ b/tools/fuzzer/class-verifier-corpus/hello_world.dex
Binary files differ
diff --git a/tools/fuzzer/dex-verifier-corpus/Main.dex b/tools/fuzzer/dex-verifier-corpus/Main.dex
new file mode 100644
index 0000000000..ec29035983
--- /dev/null
+++ b/tools/fuzzer/dex-verifier-corpus/Main.dex
Binary files differ
diff --git a/tools/fuzzer/corpus/b323685074.dex b/tools/fuzzer/dex-verifier-corpus/b323685074.dex
index aa26d59f28..aa26d59f28 100644
--- a/tools/fuzzer/corpus/b323685074.dex
+++ b/tools/fuzzer/dex-verifier-corpus/b323685074.dex
Binary files differ
diff --git a/tools/fuzzer/corpus/code_item_padding.dex b/tools/fuzzer/dex-verifier-corpus/code_item_padding.dex
index 2310d5b8e7..2310d5b8e7 100644
--- a/tools/fuzzer/corpus/code_item_padding.dex
+++ b/tools/fuzzer/dex-verifier-corpus/code_item_padding.dex
Binary files differ
diff --git a/tools/fuzzer/corpus/empty.dex b/tools/fuzzer/dex-verifier-corpus/empty.dex
index e69de29bb2..e69de29bb2 100644
--- a/tools/fuzzer/corpus/empty.dex
+++ b/tools/fuzzer/dex-verifier-corpus/empty.dex
diff --git a/tools/fuzzer/corpus/encoded_array_value.dex b/tools/fuzzer/dex-verifier-corpus/encoded_array_value.dex
index 3079f479e9..3079f479e9 100644
--- a/tools/fuzzer/corpus/encoded_array_value.dex
+++ b/tools/fuzzer/dex-verifier-corpus/encoded_array_value.dex
Binary files differ
diff --git a/tools/fuzzer/corpus/encoded_array_value2.dex b/tools/fuzzer/dex-verifier-corpus/encoded_array_value2.dex
index ed327cc997..ed327cc997 100644
--- a/tools/fuzzer/corpus/encoded_array_value2.dex
+++ b/tools/fuzzer/dex-verifier-corpus/encoded_array_value2.dex
Binary files differ
diff --git a/tools/fuzzer/dex-verifier-corpus/hello_world.dex b/tools/fuzzer/dex-verifier-corpus/hello_world.dex
new file mode 100644
index 0000000000..6e59dd76ad
--- /dev/null
+++ b/tools/fuzzer/dex-verifier-corpus/hello_world.dex
Binary files differ
diff --git a/tools/fuzzer/corpus/recursive_encoded_array.dex b/tools/fuzzer/dex-verifier-corpus/recursive_encoded_array.dex
index c775e744cd..c775e744cd 100644
--- a/tools/fuzzer/corpus/recursive_encoded_array.dex
+++ b/tools/fuzzer/dex-verifier-corpus/recursive_encoded_array.dex
Binary files differ