Resolve symlinks when checking class loader context.
There is some inconsistency in the build system. When /system_ext is
a symlink to /system/system_ext and there is a shared library in
/system_ext, the oat file generated by dexpreopt has the dex location
starting with /system/system_ext, while the metadata in
/system_ext/etc/permissions has the dex location starting with
/system_ext. This inconsitency causes the class loader context to fail.
This CL works around the inconsistency by resolving symlinks when
checking class loader context.
Bug: 247914536
Test: m test-art-host-gtest-art_runtime_tests
Change-Id: I7a177bda08e179c3d5417108be6411a84fdd5c40
(cherry picked from commit 742160318a3de2c10622b97dfe37473ef2b1985b)
Merged-In: I7a177bda08e179c3d5417108be6411a84fdd5c40
diff --git a/libartbase/base/common_art_test.cc b/libartbase/base/common_art_test.cc
index c74f3de..bef5a39 100644
--- a/libartbase/base/common_art_test.cc
+++ b/libartbase/base/common_art_test.cc
@@ -69,15 +69,7 @@
ScratchDir::~ScratchDir() {
if (!keep_files_) {
- // Recursively delete the directory and all its content.
- nftw(path_.c_str(), [](const char* name, const struct stat*, int type, struct FTW *) {
- if (type == FTW_F) {
- unlink(name);
- } else if (type == FTW_DP) {
- rmdir(name);
- }
- return 0;
- }, 256 /* max open file descriptors */, FTW_DEPTH);
+ std::filesystem::remove_all(path_);
}
}
diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc
index 2419b7b..eb32f2b 100644
--- a/runtime/class_loader_context.cc
+++ b/runtime/class_loader_context.cc
@@ -18,9 +18,9 @@
#include <algorithm>
-#include <android-base/parseint.h>
-#include <android-base/strings.h>
-
+#include "android-base/file.h"
+#include "android-base/parseint.h"
+#include "android-base/strings.h"
#include "art_field-inl.h"
#include "base/casts.h"
#include "base/dchecked_vector.h"
@@ -1345,6 +1345,28 @@
(std::string_view(path).substr(/*pos*/ path.size() - suffix.size()) == suffix);
}
+// Resolves symlinks and returns the canonicalized absolute path. Returns relative path as is.
+static std::string ResolveIfAbsolutePath(const std::string& path) {
+ if (!IsAbsoluteLocation(path)) {
+ return path;
+ }
+
+ std::string filename = path;
+ std::string multi_dex_suffix;
+ size_t pos = filename.find(DexFileLoader::kMultiDexSeparator);
+ if (pos != std::string::npos) {
+ multi_dex_suffix = filename.substr(pos);
+ filename.resize(pos);
+ }
+
+ std::string resolved_filename;
+ if (!android::base::Realpath(filename, &resolved_filename)) {
+ PLOG(ERROR) << "Unable to resolve path '" << path << "'";
+ return path;
+ }
+ return resolved_filename + multi_dex_suffix;
+}
+
// Returns true if the given dex names are mathing, false otherwise.
static bool AreDexNameMatching(const std::string& actual_dex_name,
const std::string& expected_dex_name) {
@@ -1355,28 +1377,30 @@
bool is_dex_name_absolute = IsAbsoluteLocation(actual_dex_name);
bool is_expected_dex_name_absolute = IsAbsoluteLocation(expected_dex_name);
bool dex_names_match = false;
+ std::string resolved_actual_dex_name = ResolveIfAbsolutePath(actual_dex_name);
+ std::string resolved_expected_dex_name = ResolveIfAbsolutePath(expected_dex_name);
if (is_dex_name_absolute == is_expected_dex_name_absolute) {
// If both locations are absolute or relative then compare them as they are.
// This is usually the case for: shared libraries and secondary dex files.
- dex_names_match = (actual_dex_name == expected_dex_name);
+ dex_names_match = (resolved_actual_dex_name == resolved_expected_dex_name);
} else if (is_dex_name_absolute) {
// The runtime name is absolute but the compiled name (the expected one) is relative.
// This is the case for split apks which depend on base or on other splits.
dex_names_match =
- AbsolutePathHasRelativeSuffix(actual_dex_name, expected_dex_name);
+ AbsolutePathHasRelativeSuffix(resolved_actual_dex_name, resolved_expected_dex_name);
} else if (is_expected_dex_name_absolute) {
// The runtime name is relative but the compiled name is absolute.
// There is no expected use case that would end up here as dex files are always loaded
// with their absolute location. However, be tolerant and do the best effort (in case
// there are unexpected new use case...).
dex_names_match =
- AbsolutePathHasRelativeSuffix(expected_dex_name, actual_dex_name);
+ AbsolutePathHasRelativeSuffix(resolved_expected_dex_name, resolved_actual_dex_name);
} else {
// Both locations are relative. In this case there's not much we can be sure about
// except that the names are the same. The checksum will ensure that the files are
// are same. This should not happen outside testing and manual invocations.
- dex_names_match = (actual_dex_name == expected_dex_name);
+ dex_names_match = (resolved_actual_dex_name == resolved_expected_dex_name);
}
return dex_names_match;
diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc
index 073b4b6..fd14476 100644
--- a/runtime/class_loader_context_test.cc
+++ b/runtime/class_loader_context_test.cc
@@ -18,6 +18,10 @@
#include <gtest/gtest.h>
+#include <filesystem>
+#include <fstream>
+
+#include "android-base/stringprintf.h"
#include "android-base/strings.h"
#include "art_field-inl.h"
#include "base/dchecked_vector.h"
@@ -42,6 +46,19 @@
class ClassLoaderContextTest : public CommonRuntimeTest {
public:
+ void SetUp() override {
+ CommonRuntimeTest::SetUp();
+ scratch_dir_ = std::make_unique<ScratchDir>();
+ scratch_path_ = scratch_dir_->GetPath();
+ // Remove the trailing '/';
+ scratch_path_.resize(scratch_path_.length() - 1);
+ }
+
+ void TearDown() override {
+ scratch_dir_.reset();
+ CommonRuntimeTest::TearDown();
+ }
+
void VerifyContextSize(ClassLoaderContext* context, size_t expected_size) {
ASSERT_TRUE(context != nullptr);
ASSERT_EQ(expected_size, context->GetParentChainSize());
@@ -340,6 +357,9 @@
return true;
}
+ std::unique_ptr<ScratchDir> scratch_dir_;
+ std::string scratch_path_;
+
private:
void VerifyClassLoaderInfo(ClassLoaderContext* context,
size_t index,
@@ -1600,6 +1620,28 @@
ClassLoaderContext::VerificationResult::kVerifies);
}
+TEST_F(ClassLoaderContextTest, VerifyClassLoaderContextMatchAfterResolvingSymlinks) {
+ {
+ std::ofstream ofs(scratch_path_ + "/foo.jar");
+ ASSERT_TRUE(ofs);
+ }
+ std::filesystem::create_directory_symlink(scratch_path_, scratch_path_ + "/bar");
+
+ std::string context_spec =
+ android::base::StringPrintf("PCL[%s/foo.jar*123:%s/foo.jar!classes2.dex*456]",
+ scratch_path_.c_str(),
+ scratch_path_.c_str());
+ std::unique_ptr<ClassLoaderContext> context = ParseContextWithChecksums(context_spec);
+ PretendContextOpenedDexFilesForChecksums(context.get());
+
+ std::string context_spec_with_symlinks =
+ android::base::StringPrintf("PCL[%s/bar/foo.jar*123:%s/bar/foo.jar!classes2.dex*456]",
+ scratch_path_.c_str(),
+ scratch_path_.c_str());
+ ASSERT_EQ(context->VerifyClassLoaderContextMatch(context_spec_with_symlinks),
+ ClassLoaderContext::VerificationResult::kVerifies);
+}
+
TEST_F(ClassLoaderContextTest, VerifyClassLoaderContextMatchAfterEncoding) {
jobject class_loader_a = LoadDexInPathClassLoader("ForClassLoaderA", nullptr);
jobject class_loader_b = LoadDexInDelegateLastClassLoader("ForClassLoaderB", class_loader_a);