diff options
author | 2024-02-13 16:38:49 +0000 | |
---|---|---|
committer | 2024-02-20 17:43:35 +0000 | |
commit | 8701bf5f321ccad60ee948833fc3208ae6ea51df (patch) | |
tree | 8b0137a386b01bf003b9dccaad2c218542928dc5 | |
parent | 9414a6383eb60ad0c2b0332e7e066a84413b07f9 (diff) |
Separate handling of a single path and a list of paths when determining
API domains.
Becomes necessary in a later CL where it gets used on more or less
arbitrary paths where we shouldn't treat colons specially.
The old code crashed with a fatal if a list of paths had both vendor
and product directories in it. This changes that to a more appropriate
error that gets propagated to the java level and becomes an exception
where the classloader is created.
Test: atest libnativeloader_test
Bug: 237577392
Change-Id: I783af87a03de18c65fadcd1fd5a71423ec0c786b
-rw-r--r-- | libnativeloader/Android.bp | 1 | ||||
-rw-r--r-- | libnativeloader/library_namespaces.cpp | 52 | ||||
-rw-r--r-- | libnativeloader/library_namespaces.h | 4 | ||||
-rw-r--r-- | libnativeloader/library_namespaces_test.cpp | 78 | ||||
-rw-r--r-- | libnativeloader/native_loader.cpp | 10 |
5 files changed, 130 insertions, 15 deletions
diff --git a/libnativeloader/Android.bp b/libnativeloader/Android.bp index 16449ac745..e9c26c592c 100644 --- a/libnativeloader/Android.bp +++ b/libnativeloader/Android.bp @@ -158,6 +158,7 @@ art_cc_test { "native_loader_test.cpp", ], srcs: [ + "library_namespaces_test.cpp", "native_loader_api_test.c", "native_loader_test.cpp", "open_system_library.cpp", diff --git a/libnativeloader/library_namespaces.cpp b/libnativeloader/library_namespaces.cpp index 2aeaf88a1e..e2b27294f9 100644 --- a/libnativeloader/library_namespaces.cpp +++ b/libnativeloader/library_namespaces.cpp @@ -22,10 +22,13 @@ #include <dirent.h> #include <dlfcn.h> +#include <stdio.h> +#include <algorithm> #include <optional> #include <regex> #include <string> +#include <string_view> #include <vector> #include "android-base/file.h" @@ -43,6 +46,8 @@ namespace android::nativeloader { namespace { +using ::android::base::Error; + constexpr const char* kApexPath = "/apex/"; // clns-XX is a linker namespace that is created for normal apps installed in @@ -78,8 +83,8 @@ constexpr const char* kVendorLibPath = "/vendor/" LIB; // a symlink to the other. constexpr const char* kProductLibPath = "/product/" LIB ":/system/product/" LIB; -const std::regex kVendorPathRegex("(^|:)(/system)?/vendor/"); -const std::regex kProductPathRegex("(^|:)(/system)?/product/"); +const std::regex kVendorPathRegex("(/system)?/vendor/.*"); +const std::regex kProductPathRegex("(/system)?/product/.*"); jobject GetParentClassLoader(JNIEnv* env, jobject class_loader) { jclass class_loader_class = env->FindClass("java/lang/ClassLoader"); @@ -91,18 +96,41 @@ jobject GetParentClassLoader(JNIEnv* env, jobject class_loader) { } // namespace -ApiDomain GetApiDomainFromPath(const std::string& path) { - ApiDomain api_domain = API_DOMAIN_DEFAULT; - if (std::regex_search(path, kVendorPathRegex)) { - api_domain = API_DOMAIN_VENDOR; +ApiDomain GetApiDomainFromPath(const std::string_view path) { + if (std::regex_match(path.begin(), path.end(), kVendorPathRegex)) { + return API_DOMAIN_VENDOR; + } + if (is_product_treblelized() && std::regex_match(path.begin(), path.end(), kProductPathRegex)) { + return API_DOMAIN_PRODUCT; } - if (is_product_treblelized() && std::regex_search(path, kProductPathRegex)) { - LOG_ALWAYS_FATAL_IF(api_domain == API_DOMAIN_VENDOR, - "Path matches both vendor and product partitions: %s", - path.c_str()); - api_domain = API_DOMAIN_PRODUCT; + return API_DOMAIN_DEFAULT; +} + +// Returns the API domain for a ':'-separated list of paths, or an error if they +// match more than one. +Result<ApiDomain> GetApiDomainFromPathList(const std::string& path_list) { + ApiDomain result = API_DOMAIN_DEFAULT; + size_t start_pos = 0; + while (true) { + size_t end_pos = path_list.find(':', start_pos); + ApiDomain api_domain = + GetApiDomainFromPath(std::string_view(path_list).substr(start_pos, end_pos)); + // Allow mixing API_DOMAIN_DEFAULT with any other domain. That's a bit lax, + // since the default e.g. includes /data, which strictly speaking is a + // separate domain. However, we keep it this way to not risk compat issues + // until we actually need all domains. + if (api_domain != API_DOMAIN_DEFAULT) { + if (result != API_DOMAIN_DEFAULT && result != api_domain) { + return Error() << "Path list crosses partition boundaries: " << path_list; + } + result = api_domain; + } + if (end_pos == std::string::npos) { + break; + } + start_pos = end_pos + 1; } - return api_domain; + return result; } void LibraryNamespaces::Initialize() { diff --git a/libnativeloader/library_namespaces.h b/libnativeloader/library_namespaces.h index 741d4116b8..ae1cd88f20 100644 --- a/libnativeloader/library_namespaces.h +++ b/libnativeloader/library_namespaces.h @@ -24,6 +24,7 @@ #include <list> #include <optional> #include <string> +#include <string_view> #include "android-base/result.h" #include "jni.h" @@ -54,7 +55,8 @@ using ApiDomain = enum { API_DOMAIN_PRODUCT = 2, // Product partition }; -nativeloader::ApiDomain GetApiDomainFromPath(const std::string& path); +ApiDomain GetApiDomainFromPath(const std::string_view path); +Result<ApiDomain> GetApiDomainFromPathList(const std::string& path_list); // LibraryNamespaces is a singleton object that manages NativeLoaderNamespace // objects for an app process. Its main job is to create (and configure) a new diff --git a/libnativeloader/library_namespaces_test.cpp b/libnativeloader/library_namespaces_test.cpp new file mode 100644 index 0000000000..2e6647d7a6 --- /dev/null +++ b/libnativeloader/library_namespaces_test.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2024 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. + */ + +#if defined(ART_TARGET_ANDROID) + +#include "library_namespaces.h" + +#include "android-base/result-gmock.h" +#include "gtest/gtest.h" + +namespace android { +namespace nativeloader { +namespace { + +using ::android::base::testing::HasError; +using ::android::base::testing::HasValue; +using ::android::base::testing::WithMessage; +using ::testing::StartsWith; + +TEST(LibraryNamespacesTest, TestGetApiDomainFromPath) { + EXPECT_EQ(GetApiDomainFromPath("/data/somewhere"), API_DOMAIN_DEFAULT); + EXPECT_EQ(GetApiDomainFromPath("/system/somewhere"), API_DOMAIN_DEFAULT); + EXPECT_EQ(GetApiDomainFromPath("/product/somewhere"), API_DOMAIN_PRODUCT); + EXPECT_EQ(GetApiDomainFromPath("/vendor/somewhere"), API_DOMAIN_VENDOR); + EXPECT_EQ(GetApiDomainFromPath("/system/product/somewhere"), API_DOMAIN_PRODUCT); + EXPECT_EQ(GetApiDomainFromPath("/system/vendor/somewhere"), API_DOMAIN_VENDOR); + + EXPECT_EQ(GetApiDomainFromPath(""), API_DOMAIN_DEFAULT); + EXPECT_EQ(GetApiDomainFromPath("/"), API_DOMAIN_DEFAULT); + EXPECT_EQ(GetApiDomainFromPath("product/somewhere"), API_DOMAIN_DEFAULT); + EXPECT_EQ(GetApiDomainFromPath("/product"), API_DOMAIN_DEFAULT); + EXPECT_EQ(GetApiDomainFromPath("/product/"), API_DOMAIN_PRODUCT); + EXPECT_EQ(GetApiDomainFromPath(":/product/"), API_DOMAIN_DEFAULT); + + EXPECT_EQ(GetApiDomainFromPath("/data/somewhere:/product/somewhere"), API_DOMAIN_DEFAULT); + EXPECT_EQ(GetApiDomainFromPath("/vendor/somewhere:/product/somewhere"), API_DOMAIN_VENDOR); + EXPECT_EQ(GetApiDomainFromPath("/product/somewhere:/vendor/somewhere"), API_DOMAIN_PRODUCT); +} + +TEST(LibraryNamespacesTest, TestGetApiDomainFromPathList) { + EXPECT_THAT(GetApiDomainFromPathList("/data/somewhere"), HasValue(API_DOMAIN_DEFAULT)); + EXPECT_THAT(GetApiDomainFromPathList("/system/somewhere"), HasValue(API_DOMAIN_DEFAULT)); + EXPECT_THAT(GetApiDomainFromPathList("/product/somewhere"), HasValue(API_DOMAIN_PRODUCT)); + EXPECT_THAT(GetApiDomainFromPathList("/vendor/somewhere"), HasValue(API_DOMAIN_VENDOR)); + EXPECT_THAT(GetApiDomainFromPathList("/system/product/somewhere"), HasValue(API_DOMAIN_PRODUCT)); + EXPECT_THAT(GetApiDomainFromPathList("/system/vendor/somewhere"), HasValue(API_DOMAIN_VENDOR)); + + EXPECT_THAT(GetApiDomainFromPathList(""), HasValue(API_DOMAIN_DEFAULT)); + EXPECT_THAT(GetApiDomainFromPathList(":"), HasValue(API_DOMAIN_DEFAULT)); + EXPECT_THAT(GetApiDomainFromPathList(":/vendor/somewhere"), HasValue(API_DOMAIN_VENDOR)); + EXPECT_THAT(GetApiDomainFromPathList("/vendor/somewhere:"), HasValue(API_DOMAIN_VENDOR)); + + EXPECT_THAT(GetApiDomainFromPathList("/data/somewhere:/product/somewhere"), + HasValue(API_DOMAIN_PRODUCT)); + EXPECT_THAT(GetApiDomainFromPathList("/vendor/somewhere:/product/somewhere"), + HasError(WithMessage(StartsWith("Path list crosses partition boundaries")))); + EXPECT_THAT(GetApiDomainFromPathList("/product/somewhere:/vendor/somewhere"), + HasError(WithMessage(StartsWith("Path list crosses partition boundaries")))); +} + +} // namespace +} // namespace nativeloader +} // namespace android + +#endif // ART_TARGET_ANDROID diff --git a/libnativeloader/native_loader.cpp b/libnativeloader/native_loader.cpp index 80af4da502..61925431ef 100644 --- a/libnativeloader/native_loader.cpp +++ b/libnativeloader/native_loader.cpp @@ -199,6 +199,8 @@ void ResetNativeLoader() { #endif } +// dex_path_j may be a ':'-separated list of paths, e.g. when creating a shared +// library loader - cf. mCodePaths in android.content.pm.SharedLibraryInfo. jstring CreateClassLoaderNamespace(JNIEnv* env, int32_t target_sdk_version, jobject class_loader, @@ -213,13 +215,17 @@ jstring CreateClassLoaderNamespace(JNIEnv* env, ScopedUtfChars dex_path_chars(env, dex_path_j); dex_path = dex_path_chars.c_str(); } - nativeloader::ApiDomain api_domain = nativeloader::GetApiDomainFromPath(dex_path); + + Result<nativeloader::ApiDomain> api_domain = nativeloader::GetApiDomainFromPathList(dex_path); + if (!api_domain.ok()) { + return env->NewStringUTF(api_domain.error().message().c_str()); + } std::lock_guard<std::mutex> guard(g_namespaces_mutex); Result<NativeLoaderNamespace*> ns = CreateClassLoaderNamespaceLocked(env, target_sdk_version, class_loader, - api_domain, + api_domain.value(), is_shared, dex_path, library_path_j, |