diff options
-rw-r--r-- | artd/artd.cc | 38 | ||||
-rw-r--r-- | artd/artd.h | 7 | ||||
-rw-r--r-- | artd/binder/com/android/server/art/IArtd.aidl | 13 | ||||
-rw-r--r-- | libartservice/service/java/com/android/server/art/DexUseManagerLocal.java | 75 | ||||
-rw-r--r-- | libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java | 15 |
5 files changed, 134 insertions, 14 deletions
diff --git a/artd/artd.cc b/artd/artd.cc index 4558b33988..502942e82a 100644 --- a/artd/artd.cc +++ b/artd/artd.cc @@ -1103,6 +1103,44 @@ ScopedAStatus Artd::isInDalvikCache(const std::string& in_dexFile, bool* _aidl_r return NonFatal(ART_FORMAT("Fstab entries not found for '{}'", in_dexFile)); } +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 2386cfbfa1..a4012c6da1 100644 --- a/artd/artd.h +++ b/artd/artd.h @@ -166,6 +166,13 @@ class Artd : public aidl::com::android::server::art::BnArtd { ndk::ScopedAStatus isInDalvikCache(const std::string& in_dexFile, bool* _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 74403485d5..ec57bd451b 100644 --- a/artd/binder/com/android/server/art/IArtd.aidl +++ b/artd/binder/com/android/server/art/IArtd.aidl @@ -177,4 +177,17 @@ interface IArtd { * Throws fatal and non-fatal errors. */ boolean isInDalvikCache(@utf8InCpp String dexFile); + + /** + * 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/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java index 153e83b54b..89fb542c0a 100644 --- a/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java +++ b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java @@ -77,6 +77,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Collectors; @@ -562,7 +563,7 @@ public class DexUseManagerLocal { } mDexUse = new DexUse(); if (proto != null) { - mDexUse.fromProto(proto); + mDexUse.fromProto(proto, this::validateDexPath, this::validateClassLoaderContext); } } } @@ -587,7 +588,7 @@ public class DexUseManagerLocal { return !loader.loadingPackageName().equals(owningPackageName) || loader.isolatedProcess(); } - private static void validateInputs(@NonNull PackageManagerLocal.FilteredSnapshot snapshot, + private void validateInputs(@NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String loadingPackageName, @NonNull Map<String, String> classLoaderContextByDexContainerFile) { if (classLoaderContextByDexContainerFile.isEmpty()) { @@ -596,11 +597,15 @@ public class DexUseManagerLocal { for (var entry : classLoaderContextByDexContainerFile.entrySet()) { Utils.assertNonEmpty(entry.getKey()); - if (!Paths.get(entry.getKey()).isAbsolute()) { - throw new IllegalArgumentException(String.format( - "Dex container file path must be absolute, got '%s'", entry.getKey())); + String errorMsg = validateDexPath(entry.getKey()); + if (errorMsg != null) { + throw new IllegalArgumentException(errorMsg); } Utils.assertNonEmpty(entry.getValue()); + errorMsg = validateClassLoaderContext(entry.getKey(), entry.getValue()); + if (errorMsg != null) { + throw new IllegalArgumentException(errorMsg); + } } // TODO(b/253570365): Make the validation more strict. @@ -615,6 +620,29 @@ 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( @@ -878,10 +906,12 @@ public class DexUseManagerLocal { } } - void fromProto(@NonNull DexUseProto proto) { + void fromProto(@NonNull DexUseProto proto, + @NonNull Function<String, String> validateDexPath, + @NonNull BiFunction<String, String, String> validateClassLoaderContext) { for (PackageDexUseProto packageProto : proto.getPackageDexUseList()) { var packageDexUse = new PackageDexUse(); - packageDexUse.fromProto(packageProto); + packageDexUse.fromProto(packageProto, validateDexPath, validateClassLoaderContext); mPackageDexUseByOwningPackageName.put( Utils.assertNonEmpty(packageProto.getOwningPackageName()), packageDexUse); } @@ -914,7 +944,9 @@ public class DexUseManagerLocal { } } - void fromProto(@NonNull PackageDexUseProto proto) { + void fromProto(@NonNull PackageDexUseProto proto, + @NonNull Function<String, String> validateDexPath, + @NonNull BiFunction<String, String, String> validateClassLoaderContext) { for (PrimaryDexUseProto primaryProto : proto.getPrimaryDexUseList()) { var primaryDexUse = new PrimaryDexUse(); primaryDexUse.fromProto(primaryProto); @@ -922,10 +954,20 @@ public class DexUseManagerLocal { Utils.assertNonEmpty(primaryProto.getDexFile()), primaryDexUse); } for (SecondaryDexUseProto secondaryProto : proto.getSecondaryDexUseList()) { + String dexFile = Utils.assertNonEmpty(secondaryProto.getDexFile()); + + // Skip invalid dex paths persisted by previous versions. + String errorMsg = validateDexPath.apply(dexFile); + if (errorMsg != null) { + Log.e(TAG, errorMsg); + continue; + } + var secondaryDexUse = new SecondaryDexUse(); - secondaryDexUse.fromProto(secondaryProto); - mSecondaryDexUseByDexFile.put( - Utils.assertNonEmpty(secondaryProto.getDexFile()), secondaryDexUse); + secondaryDexUse.fromProto(secondaryProto, + classLoaderContext + -> validateClassLoaderContext.apply(dexFile, classLoaderContext)); + mSecondaryDexUseByDexFile.put(dexFile, secondaryDexUse); } } } @@ -972,10 +1014,19 @@ public class DexUseManagerLocal { } } - void fromProto(@NonNull SecondaryDexUseProto proto) { + void fromProto(@NonNull SecondaryDexUseProto proto, + @NonNull Function<String, String> validateClassLoaderContext) { Utils.check(proto.hasUserId()); mUserHandle = UserHandle.of(proto.getUserId().getValue()); for (SecondaryDexUseRecordProto recordProto : proto.getRecordList()) { + // Skip invalid class loader context persisted by previous versions. + String errorMsg = validateClassLoaderContext.apply( + Utils.assertNonEmpty(recordProto.getClassLoaderContext())); + if (errorMsg != null) { + Log.e(TAG, errorMsg); + continue; + } + var record = new SecondaryDexUseRecord(); record.fromProto(recordProto); mRecordByLoader.put( diff --git a/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java index 5850e617d8..cf27d8cba1 100644 --- a/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java +++ b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java @@ -147,6 +147,9 @@ 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(mInjector.getArtd()).thenReturn(mArtd); lenient().when(mInjector.getCurrentTimeMillis()).thenReturn(0l); lenient().when(mInjector.getFilename()).thenReturn(mTempFile.getPath()); @@ -699,9 +702,17 @@ public class DexUseManagerTest { } @Test(expected = IllegalArgumentException.class) - public void testNonAbsoluteKey() { + public void testInvalidDexPath() throws Exception { + lenient().when(mArtd.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"); mDexUseManager.notifyDexContainersLoaded( - mSnapshot, OWNING_PKG_NAME, Map.of("a/b.jar", "CLC")); + mSnapshot, OWNING_PKG_NAME, Map.of("/a/b.jar", "PCL[]")); } @Test(expected = IllegalArgumentException.class) |