diff options
author | 2023-08-31 17:10:09 +0100 | |
---|---|---|
committer | 2023-10-10 19:01:06 +0000 | |
commit | f9b35bd8381fa7ef2f010d76321242e37424b5d2 (patch) | |
tree | 343534ac71bb565a246e2a00bdd25e55b24f56f2 | |
parent | cde2f060a732c431308cbdce33ae12cd51679460 (diff) |
Move dex path and CLC validation to JNI.
Bug: 298183834
Bug: 296034438
Test: atest CtsCompilationTestCases
Test: atest ArtServiceTests
Test: atest art_standalone_artd_tests
Test: atest art_standalone_libartservice_tests
Change-Id: I7ed1a36392ea8ca6f583e4743baef043c9927ee5
Merged-In: I7ed1a36392ea8ca6f583e4743baef043c9927ee5
-rw-r--r-- | artd/Android.bp | 1 | ||||
-rw-r--r-- | artd/artd.cc | 40 | ||||
-rw-r--r-- | artd/artd.h | 7 | ||||
-rw-r--r-- | artd/binder/com/android/server/art/IArtd.aidl | 13 | ||||
-rw-r--r-- | artd/path_utils.cc | 48 | ||||
-rw-r--r-- | artd/path_utils.h | 2 | ||||
-rw-r--r-- | artd/path_utils_test.cc | 53 | ||||
-rw-r--r-- | libartservice/service/Android.bp | 11 | ||||
-rw-r--r-- | libartservice/service/java/com/android/server/art/ArtJni.java | 63 | ||||
-rw-r--r-- | libartservice/service/java/com/android/server/art/DexUseManagerLocal.java | 30 | ||||
-rw-r--r-- | libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java | 12 | ||||
-rw-r--r-- | libartservice/service/native/service.cc | 97 | ||||
-rw-r--r-- | libartservice/service/native/service.h | 12 | ||||
-rw-r--r-- | libartservice/service/native/service_test.cc | 87 |
14 files changed, 283 insertions, 193 deletions
diff --git a/artd/Android.bp b/artd/Android.bp index cacbe289ce..6de6aef96d 100644 --- a/artd/Android.bp +++ b/artd/Android.bp @@ -35,6 +35,7 @@ cc_defaults { "profman_headers", ], shared_libs: [ + "libartservice", "libarttools", // Contains "libc++fs". "libbase", "libbinder_ndk", diff --git a/artd/artd.cc b/artd/artd.cc index 09168a2e24..8b46d91803 100644 --- a/artd/artd.cc +++ b/artd/artd.cc @@ -73,6 +73,7 @@ #include "path_utils.h" #include "profman/profman_result.h" #include "selinux/android.h" +#include "service.h" #include "tools/cmdline_builder.h" #include "tools/tools.h" @@ -109,6 +110,7 @@ using ::android::base::Split; using ::android::base::StringReplace; using ::android::base::WriteStringToFd; using ::android::fs_mgr::FstabEntry; +using ::art::service::ValidateDexPath; using ::art::tools::CmdlineBuilder; using ::ndk::ScopedAStatus; @@ -1177,44 +1179,6 @@ ScopedAStatus Artd::deleteRuntimeArtifacts(const RuntimeArtifactsPath& in_runtim return ScopedAStatus::ok(); } -ScopedAStatus Artd::validateDexPath(const std::string& in_dexPath, - std::optional<std::string>* _aidl_return) { - if (Result<void> result = ValidateDexPath(in_dexPath); !result.ok()) { - *_aidl_return = result.error().message(); - } else { - *_aidl_return = std::nullopt; - } - return ScopedAStatus::ok(); -} - -ScopedAStatus Artd::validateClassLoaderContext(const std::string& in_dexPath, - const std::string& in_classLoaderContext, - std::optional<std::string>* _aidl_return) { - if (in_classLoaderContext == ClassLoaderContext::kUnsupportedClassLoaderContextEncoding) { - *_aidl_return = std::nullopt; - return ScopedAStatus::ok(); - } - - std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(in_classLoaderContext); - if (context == nullptr) { - *_aidl_return = ART_FORMAT("Class loader context '{}' is invalid", in_classLoaderContext); - return ScopedAStatus::ok(); - } - - std::vector<std::string> flattened_context = context->FlattenDexPaths(); - std::string dex_dir = Dirname(in_dexPath); - for (const std::string& context_element : flattened_context) { - std::string context_path = std::filesystem::path(dex_dir).append(context_element); - if (Result<void> result = ValidateDexPath(context_path); !result.ok()) { - *_aidl_return = result.error().message(); - return ScopedAStatus::ok(); - } - } - - *_aidl_return = std::nullopt; - return ScopedAStatus::ok(); -} - Result<void> Artd::Start() { OR_RETURN(SetLogVerbosity()); diff --git a/artd/artd.h b/artd/artd.h index 8d9fa6f7d3..c78aaa9a05 100644 --- a/artd/artd.h +++ b/artd/artd.h @@ -172,13 +172,6 @@ class Artd : public aidl::com::android::server::art::BnArtd { const aidl::com::android::server::art::RuntimeArtifactsPath& in_runtimeArtifactsPath, int64_t* _aidl_return) override; - ndk::ScopedAStatus validateDexPath(const std::string& in_dexPath, - std::optional<std::string>* _aidl_return) override; - - ndk::ScopedAStatus validateClassLoaderContext(const std::string& in_dexPath, - const std::string& in_classLoaderContext, - std::optional<std::string>* _aidl_return) override; - android::base::Result<void> Start(); private: diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl index a296337a57..014f63e3df 100644 --- a/artd/binder/com/android/server/art/IArtd.aidl +++ b/artd/binder/com/android/server/art/IArtd.aidl @@ -190,17 +190,4 @@ interface IArtd { */ long deleteRuntimeArtifacts( in com.android.server.art.RuntimeArtifactsPath runtimeArtifactsPath); - - /** - * Returns an error message if the given dex path is invalid, or null if the validation - * passes. - */ - @nullable @utf8InCpp String validateDexPath(@utf8InCpp String dexPath); - - /** - * Returns an error message if the given class loader context is invalid, or null if the - * validation passes. - */ - @nullable @utf8InCpp String validateClassLoaderContext(@utf8InCpp String dexPath, - @utf8InCpp String classLoaderContext); } diff --git a/artd/path_utils.cc b/artd/path_utils.cc index aa5a2b160f..61d185b56d 100644 --- a/artd/path_utils.cc +++ b/artd/path_utils.cc @@ -31,6 +31,7 @@ #include "fstab/fstab.h" #include "oat_file_assistant.h" #include "runtime_image.h" +#include "service.h" #include "tools/tools.h" namespace art { @@ -49,6 +50,9 @@ using ::android::base::StartsWith; using ::android::fs_mgr::Fstab; using ::android::fs_mgr::FstabEntry; using ::android::fs_mgr::ReadFstabFromProcMounts; +using ::art::service::ValidateDexPath; +using ::art::service::ValidatePathElement; +using ::art::service::ValidatePathElementSubstring; using PrebuiltProfilePath = ProfilePath::PrebuiltProfilePath; using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath; @@ -61,45 +65,6 @@ using WritableProfilePath = ProfilePath::WritableProfilePath; // Only to be changed for testing. std::string_view gListRootDir = "/"; -Result<void> ValidateAbsoluteNormalPath(const std::string& path_str) { - if (path_str.empty()) { - return Errorf("Path is empty"); - } - if (path_str.find('\0') != std::string::npos) { - return Errorf("Path '{}' has invalid character '\\0'", path_str); - } - std::filesystem::path path(path_str); - if (!path.is_absolute()) { - return Errorf("Path '{}' is not an absolute path", path_str); - } - if (path.lexically_normal() != path_str) { - return Errorf("Path '{}' is not in normal form", path_str); - } - return {}; -} - -Result<void> ValidatePathElementSubstring(const std::string& path_element_substring, - const std::string& name) { - if (path_element_substring.empty()) { - return Errorf("{} is empty", name); - } - if (path_element_substring.find('/') != std::string::npos) { - return Errorf("{} '{}' has invalid character '/'", name, path_element_substring); - } - if (path_element_substring.find('\0') != std::string::npos) { - return Errorf("{} '{}' has invalid character '\\0'", name, path_element_substring); - } - return {}; -} - -Result<void> ValidatePathElement(const std::string& path_element, const std::string& name) { - OR_RETURN(ValidatePathElementSubstring(path_element, name)); - if (path_element == "." || path_element == "..") { - return Errorf("Invalid {} '{}'", name, path_element); - } - return {}; -} - } // namespace Result<std::string> GetAndroidDataOrError() { @@ -190,11 +155,6 @@ Result<void> ValidateRuntimeArtifactsPath(const RuntimeArtifactsPath& runtime_ar return {}; } -Result<void> ValidateDexPath(const std::string& dex_path) { - OR_RETURN(ValidateAbsoluteNormalPath(dex_path)); - return {}; -} - Result<std::string> BuildArtBinPath(const std::string& binary_name) { return ART_FORMAT("{}/bin/{}", OR_RETURN(GetArtRootOrError()), binary_name); } diff --git a/artd/path_utils.h b/artd/path_utils.h index 77aa82ab7f..b42b6391c1 100644 --- a/artd/path_utils.h +++ b/artd/path_utils.h @@ -46,8 +46,6 @@ std::vector<std::string> ListRuntimeArtifactsFiles( android::base::Result<void> ValidateRuntimeArtifactsPath( const aidl::com::android::server::art::RuntimeArtifactsPath& runtime_artifacts_path); -android::base::Result<void> ValidateDexPath(const std::string& dex_path); - android::base::Result<std::string> BuildArtBinPath(const std::string& binary_name); // Returns the absolute path to the OAT file built from the `ArtifactsPath`. diff --git a/artd/path_utils_test.cc b/artd/path_utils_test.cc index 960bf9716d..47b503f982 100644 --- a/artd/path_utils_test.cc +++ b/artd/path_utils_test.cc @@ -62,29 +62,12 @@ TEST_F(PathUtilsTest, BuildOatPathDalvikCache) { HasValue(android_data_ + "/dalvik-cache/arm64/a@b.apk@classes.dex")); } -TEST_F(PathUtilsTest, BuildOatPathEmptyDexPath) { - EXPECT_THAT(BuildOatPath(ArtifactsPath{.dexPath = "", .isa = "arm64", .isInDalvikCache = false}), - HasError(WithMessage("Path is empty"))); -} - -TEST_F(PathUtilsTest, BuildOatPathRelativeDexPath) { +TEST_F(PathUtilsTest, BuildOatPathInvalidDexPath) { EXPECT_THAT( BuildOatPath(ArtifactsPath{.dexPath = "a/b.apk", .isa = "arm64", .isInDalvikCache = false}), HasError(WithMessage("Path 'a/b.apk' is not an absolute path"))); } -TEST_F(PathUtilsTest, BuildOatPathNonNormalDexPath) { - EXPECT_THAT(BuildOatPath(ArtifactsPath{ - .dexPath = "/a/c/../b.apk", .isa = "arm64", .isInDalvikCache = false}), - HasError(WithMessage("Path '/a/c/../b.apk' is not in normal form"))); -} - -TEST_F(PathUtilsTest, BuildOatPathNul) { - EXPECT_THAT(BuildOatPath(ArtifactsPath{ - .dexPath = "/a/\0/b.apk"s, .isa = "arm64", .isInDalvikCache = false}), - HasError(WithMessage("Path '/a/\0/b.apk' has invalid character '\\0'"s))); -} - TEST_F(PathUtilsTest, BuildOatPathInvalidIsa) { EXPECT_THAT(BuildOatPath( ArtifactsPath{.dexPath = "/a/b.apk", .isa = "invalid", .isInDalvikCache = false}), @@ -109,51 +92,27 @@ TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathPackageNameOk) { EXPECT_THAT(BuildPrimaryRefProfilePath( PrimaryRefProfilePath{.packageName = "...", .profileName = "primary"}), HasValue(android_data_ + "/misc/profiles/ref/.../primary.prof")); - EXPECT_THAT(BuildPrimaryRefProfilePath( - PrimaryRefProfilePath{.packageName = "!@#$%^&*()_+-=", .profileName = "primary"}), - HasValue(android_data_ + "/misc/profiles/ref/!@#$%^&*()_+-=/primary.prof")); } TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathPackageNameWrong) { EXPECT_THAT(BuildPrimaryRefProfilePath( - PrimaryRefProfilePath{.packageName = "", .profileName = "primary"}), - HasError(WithMessage("packageName is empty"))); - EXPECT_THAT(BuildPrimaryRefProfilePath( - PrimaryRefProfilePath{.packageName = ".", .profileName = "primary"}), - HasError(WithMessage("Invalid packageName '.'"))); - EXPECT_THAT(BuildPrimaryRefProfilePath( PrimaryRefProfilePath{.packageName = "..", .profileName = "primary"}), HasError(WithMessage("Invalid packageName '..'"))); EXPECT_THAT(BuildPrimaryRefProfilePath( PrimaryRefProfilePath{.packageName = "a/b", .profileName = "primary"}), HasError(WithMessage("packageName 'a/b' has invalid character '/'"))); - EXPECT_THAT(BuildPrimaryRefProfilePath( - PrimaryRefProfilePath{.packageName = "a\0b"s, .profileName = "primary"}), - HasError(WithMessage("packageName 'a\0b' has invalid character '\\0'"s))); } TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathProfileNameOk) { EXPECT_THAT(BuildPrimaryRefProfilePath( - PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "."}), - HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/..prof")); - EXPECT_THAT(BuildPrimaryRefProfilePath( PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = ".."}), HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/...prof")); - EXPECT_THAT(BuildPrimaryRefProfilePath(PrimaryRefProfilePath{.packageName = "com.android.foo", - .profileName = "!@#$%^&*()_+-="}), - HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/!@#$%^&*()_+-=.prof")); } TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathProfileNameWrong) { EXPECT_THAT(BuildPrimaryRefProfilePath( - PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = ""}), - HasError(WithMessage("profileName is empty"))); - EXPECT_THAT(BuildPrimaryRefProfilePath( PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "a/b"}), HasError(WithMessage("profileName 'a/b' has invalid character '/'"))); - EXPECT_THAT(BuildPrimaryRefProfilePath( - PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "a\0b"s}), - HasError(WithMessage("profileName 'a\0b' has invalid character '\\0'"s))); } TEST_F(PathUtilsTest, BuildFinalProfilePathForPrimary) { @@ -193,18 +152,8 @@ TEST_F(PathUtilsTest, BuildTmpProfilePathIdWrong) { EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{ .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "primary"}, - .id = ""}), - HasError(WithMessage("id is empty"))); - EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{ - .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo", - .profileName = "primary"}, .id = "123/45"}), HasError(WithMessage("id '123/45' has invalid character '/'"))); - EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{ - .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo", - .profileName = "primary"}, - .id = "123\0a"s}), - HasError(WithMessage("id '123\0a' has invalid character '\\0'"s))); } TEST_F(PathUtilsTest, BuildPrebuiltProfilePath) { diff --git a/libartservice/service/Android.bp b/libartservice/service/Android.bp index 6287acce87..19b3befbdd 100644 --- a/libartservice/service/Android.bp +++ b/libartservice/service/Android.bp @@ -30,8 +30,11 @@ cc_defaults { srcs: [ "native/service.cc", ], + export_include_dirs: ["native"], shared_libs: [ + "libarttools", // Contains "libc++fs". "libbase", + "libnativehelper", ], } @@ -43,6 +46,8 @@ cc_library { "com.android.art.debug", ], shared_libs: [ + "libart", + "libartbase", ], } @@ -57,6 +62,8 @@ cc_library { "com.android.art.debug", ], shared_libs: [ + "libartd", + "libartbased", ], } @@ -178,6 +185,9 @@ art_cc_defaults { srcs: [ "native/service_test.cc", ], + static_libs: [ + "libgmock", + ], } // Version of ART gtest `art_libartservice_tests` bundled with the ART APEX on target. @@ -230,6 +240,7 @@ android_test { ], jni_libs: [ + "libartservice", // The two libraries below are required by ExtendedMockito. "libdexmakerjvmtiagent", "libstaticjvmtiagent", diff --git a/libartservice/service/java/com/android/server/art/ArtJni.java b/libartservice/service/java/com/android/server/art/ArtJni.java new file mode 100644 index 0000000000..2cc9ea8911 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/ArtJni.java @@ -0,0 +1,63 @@ +/* + * 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. + */ + +package com.android.server.art; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import dalvik.system.VMRuntime; + +/** + * JNI methods for ART Service, with wrappers added for testability because Mockito cannot mock JNI + * methods. + * + * @hide + */ +public class ArtJni { + static { + if (VMRuntime.getRuntime().vmLibrary().equals("libartd.so")) { + System.loadLibrary("artserviced"); + } else { + System.loadLibrary("artservice"); + } + } + + private ArtJni() {} + + /** + * Returns an error message if the given dex path is invalid, or null if the validation passes. + */ + @Nullable + public static String validateDexPath(@NonNull String dexPath) { + return validateDexPathNative(dexPath); + } + + /** + * Returns an error message if the given class loader context is invalid, or null if the + * validation passes. + */ + @Nullable + public static String validateClassLoaderContext( + @NonNull String dexPath, @NonNull String classLoaderContext) { + return validateClassLoaderContextNative(dexPath, classLoaderContext); + } + + @Nullable private static native String validateDexPathNative(@NonNull String dexPath); + @Nullable + private static native String validateClassLoaderContextNative( + @NonNull String dexPath, @NonNull String classLoaderContext); +} diff --git a/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java index 97b3c8a186..c73f71b9e3 100644 --- a/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java +++ b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java @@ -566,7 +566,8 @@ public class DexUseManagerLocal { } mDexUse = new DexUse(); if (proto != null) { - mDexUse.fromProto(proto, this::validateDexPath, this::validateClassLoaderContext); + mDexUse.fromProto( + proto, ArtJni::validateDexPath, ArtJni::validateClassLoaderContext); } } } @@ -600,12 +601,12 @@ public class DexUseManagerLocal { for (var entry : classLoaderContextByDexContainerFile.entrySet()) { Utils.assertNonEmpty(entry.getKey()); - String errorMsg = validateDexPath(entry.getKey()); + String errorMsg = ArtJni.validateDexPath(entry.getKey()); if (errorMsg != null) { throw new IllegalArgumentException(errorMsg); } Utils.assertNonEmpty(entry.getValue()); - errorMsg = validateClassLoaderContext(entry.getKey(), entry.getValue()); + errorMsg = ArtJni.validateClassLoaderContext(entry.getKey(), entry.getValue()); if (errorMsg != null) { throw new IllegalArgumentException(errorMsg); } @@ -623,29 +624,6 @@ public class DexUseManagerLocal { } } - @Nullable - private String validateDexPath(@NonNull String dexPath) { - try { - return mInjector.getArtd().validateDexPath(dexPath); - } catch (RemoteException e) { - String errorMsg = "Failed to validate dex path " + dexPath; - Log.e(TAG, errorMsg, e); - return errorMsg; - } - } - - @Nullable - private String validateClassLoaderContext( - @NonNull String dexPath, @NonNull String classLoaderContext) { - try { - return mInjector.getArtd().validateClassLoaderContext(dexPath, classLoaderContext); - } catch (RemoteException e) { - String errorMsg = "Failed to validate class loader context " + classLoaderContext; - Log.e(TAG, errorMsg, e); - return errorMsg; - } - } - /** @hide */ @Nullable public String getSecondaryClassLoaderContext( diff --git a/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java index f34cd434ed..83cb81c1df 100644 --- a/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java +++ b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java @@ -81,8 +81,8 @@ public class DexUseManagerTest { private static final String SPLIT_APK = "/data/app/" + OWNING_PKG_NAME + "/split_0.apk"; @Rule - public StaticMockitoRule mockitoRule = - new StaticMockitoRule(SystemProperties.class, Constants.class, Process.class); + public StaticMockitoRule mockitoRule = new StaticMockitoRule( + SystemProperties.class, Constants.class, Process.class, ArtJni.class); private final UserHandle mUserHandle = Binder.getCallingUserHandle(); @@ -155,8 +155,8 @@ public class DexUseManagerTest { mTempFile = File.createTempFile("package-dex-usage", ".pb"); mTempFile.deleteOnExit(); - lenient().when(mArtd.validateDexPath(any())).thenReturn(null); - lenient().when(mArtd.validateClassLoaderContext(any(), any())).thenReturn(null); + lenient().when(ArtJni.validateDexPath(any())).thenReturn(null); + lenient().when(ArtJni.validateClassLoaderContext(any(), any())).thenReturn(null); lenient().when(mInjector.getArtd()).thenReturn(mArtd); lenient().when(mInjector.getCurrentTimeMillis()).thenReturn(0l); @@ -712,14 +712,14 @@ public class DexUseManagerTest { @Test(expected = IllegalArgumentException.class) public void testInvalidDexPath() throws Exception { - lenient().when(mArtd.validateDexPath(any())).thenReturn("invalid"); + lenient().when(ArtJni.validateDexPath(any())).thenReturn("invalid"); mDexUseManager.notifyDexContainersLoaded( mSnapshot, OWNING_PKG_NAME, Map.of("/a/b.jar", "PCL[]")); } @Test(expected = IllegalArgumentException.class) public void testInvalidClassLoaderContext() throws Exception { - lenient().when(mArtd.validateClassLoaderContext(any(), any())).thenReturn("invalid"); + lenient().when(ArtJni.validateClassLoaderContext(any(), any())).thenReturn("invalid"); mDexUseManager.notifyDexContainersLoaded( mSnapshot, OWNING_PKG_NAME, Map.of("/a/b.jar", "PCL[]")); } diff --git a/libartservice/service/native/service.cc b/libartservice/service/native/service.cc index d33cb59515..7d223e5c35 100644 --- a/libartservice/service/native/service.cc +++ b/libartservice/service/native/service.cc @@ -16,12 +16,105 @@ #include "service.h" +#include <jni.h> + +#include <filesystem> + +#include "android-base/errors.h" +#include "android-base/file.h" +#include "android-base/result.h" +#include "class_loader_context.h" +#include "nativehelper/utils.h" + namespace art { namespace service { -std::string getMsg() { - return "hello world!"; +using ::android::base::Dirname; +using ::android::base::Result; + +Result<void> ValidateAbsoluteNormalPath(const std::string& path_str) { + if (path_str.empty()) { + return Errorf("Path is empty"); + } + if (path_str.find('\0') != std::string::npos) { + return Errorf("Path '{}' has invalid character '\\0'", path_str); + } + std::filesystem::path path(path_str); + if (!path.is_absolute()) { + return Errorf("Path '{}' is not an absolute path", path_str); + } + if (path.lexically_normal() != path_str) { + return Errorf("Path '{}' is not in normal form", path_str); + } + return {}; } +Result<void> ValidatePathElementSubstring(const std::string& path_element_substring, + const std::string& name) { + if (path_element_substring.empty()) { + return Errorf("{} is empty", name); + } + if (path_element_substring.find('/') != std::string::npos) { + return Errorf("{} '{}' has invalid character '/'", name, path_element_substring); + } + if (path_element_substring.find('\0') != std::string::npos) { + return Errorf("{} '{}' has invalid character '\\0'", name, path_element_substring); + } + return {}; } + +Result<void> ValidatePathElement(const std::string& path_element, const std::string& name) { + OR_RETURN(ValidatePathElementSubstring(path_element, name)); + if (path_element == "." || path_element == "..") { + return Errorf("Invalid {} '{}'", name, path_element); + } + return {}; } + +Result<void> ValidateDexPath(const std::string& dex_path) { + OR_RETURN(ValidateAbsoluteNormalPath(dex_path)); + return {}; +} + +extern "C" JNIEXPORT jstring JNICALL +Java_com_android_server_art_ArtJni_validateDexPathNative(JNIEnv* env, jobject, jstring j_dex_path) { + std::string dex_path(GET_UTF_OR_RETURN(env, j_dex_path)); + + if (Result<void> result = ValidateDexPath(dex_path); !result.ok()) { + return CREATE_UTF_OR_RETURN(env, result.error().message()).release(); + } else { + return nullptr; + } +} + +extern "C" JNIEXPORT jstring JNICALL +Java_com_android_server_art_ArtJni_validateClassLoaderContextNative( + JNIEnv* env, jobject, jstring j_dex_path, jstring j_class_loader_context) { + ScopedUtfChars dex_path = GET_UTF_OR_RETURN(env, j_dex_path); + std::string class_loader_context(GET_UTF_OR_RETURN(env, j_class_loader_context)); + + if (class_loader_context == ClassLoaderContext::kUnsupportedClassLoaderContextEncoding) { + return nullptr; + } + + std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(class_loader_context); + if (context == nullptr) { + return CREATE_UTF_OR_RETURN( + env, ART_FORMAT("Class loader context '{}' is invalid", class_loader_context)) + .release(); + } + + std::vector<std::string> flattened_context = context->FlattenDexPaths(); + std::string dex_dir = Dirname(dex_path); + for (const std::string& context_element : flattened_context) { + std::string context_path = std::filesystem::path(dex_dir).append(context_element); + if (Result<void> result = ValidateDexPath(context_path); !result.ok()) { + return CREATE_UTF_OR_RETURN(env, result.error().message()).release(); + } + } + + return nullptr; +} + +} // namespace service +} // namespace art diff --git a/libartservice/service/native/service.h b/libartservice/service/native/service.h index 2b680a28a4..7f43f17f0b 100644 --- a/libartservice/service/native/service.h +++ b/libartservice/service/native/service.h @@ -19,10 +19,20 @@ #include <string> +#include "android-base/result.h" + namespace art { namespace service { -std::string getMsg(); +android::base::Result<void> ValidateAbsoluteNormalPath(const std::string& path_str); + +android::base::Result<void> ValidatePathElementSubstring(const std::string& path_element_substring, + const std::string& name); + +android::base::Result<void> ValidatePathElement(const std::string& path_element, + const std::string& name); + +android::base::Result<void> ValidateDexPath(const std::string& dex_path); } // namespace service } // namespace art diff --git a/libartservice/service/native/service_test.cc b/libartservice/service/native/service_test.cc index eb84b732c7..8300bf5e76 100644 --- a/libartservice/service/native/service_test.cc +++ b/libartservice/service/native/service_test.cc @@ -15,14 +15,97 @@ */ #include "service.h" + +#include "android-base/result-gmock.h" #include "gtest/gtest.h" namespace art { +namespace service { +namespace { + +using ::android::base::testing::HasError; +using ::android::base::testing::Ok; +using ::android::base::testing::WithMessage; + +using std::literals::operator""s; // NOLINT class ArtServiceTest : public testing::Test {}; -TEST_F(ArtServiceTest, Hello) { - EXPECT_EQ("hello world!", art::service::getMsg()); +TEST_F(ArtServiceTest, ValidatePathElementOk) { + EXPECT_THAT(ValidatePathElement("com.android.foo", "packageName"), Ok()); + EXPECT_THAT(ValidatePathElement("...", "packageName"), Ok()); + EXPECT_THAT(ValidatePathElement("!@#$%^&*()_+-=", "packageName"), Ok()); +} + +TEST_F(ArtServiceTest, ValidatePathElementEmpty) { + EXPECT_THAT(ValidatePathElement("", "packageName"), + HasError(WithMessage("packageName is empty"))); +} + +TEST_F(ArtServiceTest, ValidatePathElementDot) { + EXPECT_THAT(ValidatePathElement(".", "packageName"), + HasError(WithMessage("Invalid packageName '.'"))); +} + +TEST_F(ArtServiceTest, ValidatePathElementDotDot) { + EXPECT_THAT(ValidatePathElement("..", "packageName"), + HasError(WithMessage("Invalid packageName '..'"))); +} + +TEST_F(ArtServiceTest, ValidatePathElementSlash) { + EXPECT_THAT(ValidatePathElement("a/b", "packageName"), + HasError(WithMessage("packageName 'a/b' has invalid character '/'"))); +} + +TEST_F(ArtServiceTest, ValidatePathElementNul) { + EXPECT_THAT(ValidatePathElement("a\0b"s, "packageName"), + HasError(WithMessage("packageName 'a\0b' has invalid character '\\0'"s))); +} + +TEST_F(ArtServiceTest, ValidatePathElementSubstringOk) { + EXPECT_THAT(ValidatePathElementSubstring("com.android.foo", "packageName"), Ok()); + EXPECT_THAT(ValidatePathElementSubstring(".", "packageName"), Ok()); + EXPECT_THAT(ValidatePathElementSubstring("..", "packageName"), Ok()); + EXPECT_THAT(ValidatePathElementSubstring("...", "packageName"), Ok()); + EXPECT_THAT(ValidatePathElementSubstring("!@#$%^&*()_+-=", "packageName"), Ok()); +} + +TEST_F(ArtServiceTest, ValidatePathElementSubstringEmpty) { + EXPECT_THAT(ValidatePathElementSubstring("", "packageName"), + HasError(WithMessage("packageName is empty"))); +} + +TEST_F(ArtServiceTest, ValidatePathElementSubstringSlash) { + EXPECT_THAT(ValidatePathElementSubstring("a/b", "packageName"), + HasError(WithMessage("packageName 'a/b' has invalid character '/'"))); +} + +TEST_F(ArtServiceTest, ValidatePathElementSubstringNul) { + EXPECT_THAT(ValidatePathElementSubstring("a\0b"s, "packageName"), + HasError(WithMessage("packageName 'a\0b' has invalid character '\\0'"s))); +} + +TEST_F(ArtServiceTest, ValidateDexPathOk) { EXPECT_THAT(ValidateDexPath("/a/b.apk"), Ok()); } + +TEST_F(ArtServiceTest, ValidateDexPathEmpty) { + EXPECT_THAT(ValidateDexPath(""), HasError(WithMessage("Path is empty"))); +} + +TEST_F(ArtServiceTest, ValidateDexPathRelative) { + EXPECT_THAT(ValidateDexPath("a/b.apk"), + HasError(WithMessage("Path 'a/b.apk' is not an absolute path"))); +} + +TEST_F(ArtServiceTest, ValidateDexPathNonNormal) { + EXPECT_THAT(ValidateDexPath("/a/c/../b.apk"), + HasError(WithMessage("Path '/a/c/../b.apk' is not in normal form"))); +} + +TEST_F(ArtServiceTest, ValidateDexPathNul) { + EXPECT_THAT(ValidateDexPath("/a/\0/b.apk"s), + HasError(WithMessage("Path '/a/\0/b.apk' has invalid character '\\0'"s))); } +} // namespace +} // namespace service } // namespace art |