diff options
author | Ryan Mitchell <rtmitchell@google.com> | 2020-04-02 09:54:23 -0700 |
---|---|---|
committer | Ryan Mitchell <rtmitchell@google.com> | 2020-04-09 11:01:54 -0700 |
commit | 192400cf334e0e8befe336861a6d214c6a88c993 (patch) | |
tree | e5702b6ffbf4bd02012766ebada03e3ceec8f460 | |
parent | 2d8d9812e9925ed017c5ce10208b3d807258a2cc (diff) |
Fail install when resources.arsc is compressed
If an application targets R+, prevent the application from being
installed if the app has a compressed resources.arsc or if the
resources.arsc is not aligned on a 4-byte boundary. Resources tables
that cannot be memory mapped have to be read into a buffer in RAM
and exert unnecessary memory pressure on the system.
Bug: 132742131
Test: manual (adding CTS tests)
Change-Id: Ieef764c87643863de24531fac12cc520fe6d90d0
-rw-r--r-- | core/java/android/content/pm/PackageManager.java | 10 | ||||
-rw-r--r-- | core/java/android/content/pm/parsing/ParsingPackageUtils.java | 16 | ||||
-rw-r--r-- | core/java/android/content/pm/parsing/result/ParseInput.java | 10 | ||||
-rw-r--r-- | core/java/android/content/res/AssetManager.java | 14 | ||||
-rw-r--r-- | core/jni/android_util_AssetManager.cpp | 6 | ||||
-rw-r--r-- | libs/androidfw/AssetManager2.cpp | 5 | ||||
-rw-r--r-- | libs/androidfw/include/androidfw/ApkAssets.h | 17 | ||||
-rw-r--r-- | libs/androidfw/include/androidfw/AssetManager2.h | 3 |
8 files changed, 74 insertions, 7 deletions
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 9a2e07e9cfbd..9ca2db970eb7 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1550,6 +1550,16 @@ public abstract class PackageManager { */ public static final int INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED = -123; + /** + * Installation failed return code: the {@code resources.arsc} of one of the APKs being + * installed is compressed or not aligned on a 4-byte boundary. Resource tables that cannot be + * memory mapped exert excess memory pressure on the system and drastically slow down + * construction of {@link Resources} objects. + * + * @hide + */ + public static final int INSTALL_PARSE_FAILED_RESOURCES_ARSC_COMPRESSED = -124; + /** @hide */ @IntDef(flag = true, prefix = { "DELETE_" }, value = { DELETE_KEEP_DATA, diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index c94d428f4475..f884848f10f0 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -23,6 +23,7 @@ import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFES import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_RESOURCES_ARSC_COMPRESSED; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; import static android.os.Build.VERSION_CODES.DONUT; import static android.os.Build.VERSION_CODES.O; @@ -347,7 +348,20 @@ public class ParsingPackageUtils { + result.getErrorMessage()); } - ParsingPackage pkg = result.getResult(); + final ParsingPackage pkg = result.getResult(); + if (pkg.getTargetSdkVersion() >= Build.VERSION_CODES.R + && assets.containsAllocatedTable()) { + final ParseResult<?> deferResult = input.deferError( + "Targeting R+ (version" + Build.VERSION_CODES.R + " and above) requires the" + + " resources.arsc of installed APKs to be stored uncompressed and" + + " aligned on a 4-byte boundary", + DeferredError.RESOURCES_ARSC_COMPRESSED); + if (deferResult.isError()) { + return input.error(INSTALL_PARSE_FAILED_RESOURCES_ARSC_COMPRESSED, + deferResult.getErrorMessage()); + } + } + ApkAssets apkAssets = assets.getApkAssets()[0]; if (apkAssets.definesOverlayable()) { SparseArray<String> packageNames = assets.getAssignedPackageIdentifiers(); diff --git a/core/java/android/content/pm/parsing/result/ParseInput.java b/core/java/android/content/pm/parsing/result/ParseInput.java index 538510049e04..6b659bea84f1 100644 --- a/core/java/android/content/pm/parsing/result/ParseInput.java +++ b/core/java/android/content/pm/parsing/result/ParseInput.java @@ -59,6 +59,16 @@ public interface ParseInput { @ChangeId @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) public static final long EMPTY_INTENT_ACTION_CATEGORY = 151163173; + + /** + * The {@code resources.arsc} of one of the APKs being installed is compressed or not + * aligned on a 4-byte boundary. Resource tables that cannot be memory mapped exert excess + * memory pressure on the system and drastically slow down construction of + * {@link android.content.res.Resources} objects. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + public static final long RESOURCES_ARSC_COMPRESSED = 132742131; } <ResultType> ParseResult<ResultType> success(ResultType result); diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index d2103af1d247..15a184f0e5ef 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -819,6 +819,19 @@ public final class AssetManager implements AutoCloseable { } } + /** + * Returns whether the {@code resources.arsc} of any loaded apk assets is allocated in RAM + * (not mmapped). + * + * @hide + */ + public boolean containsAllocatedTable() { + synchronized (this) { + ensureValidLocked(); + return nativeContainsAllocatedTable(mObject); + } + } + CharSequence getPooledStringForCookie(int cookie, int id) { // Cookies map to ApkAssets starting at 1. return getApkAssets()[cookie - 1].getStringFromPool(id); @@ -1482,6 +1495,7 @@ public final class AssetManager implements AutoCloseable { long ptr, boolean includeOverlays, boolean includeLoaders); // File native methods. + private static native boolean nativeContainsAllocatedTable(long ptr); private static native @Nullable String[] nativeList(long ptr, @NonNull String path) throws IOException; private static native long nativeOpenAsset(long ptr, @NonNull String fileName, int accessMode); diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index bf3fc5704739..12abc256a20e 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -481,6 +481,11 @@ static jobject NativeGetAssignedPackageIdentifiers(JNIEnv* env, jclass /*clazz*/ return sparse_array; } +static jboolean ContainsAllocatedTable(JNIEnv* env, jclass /*clazz*/, jlong ptr) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + return assetmanager->ContainsAllocatedTable(); +} + static jobjectArray NativeList(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring path) { ScopedUtfChars path_utf8(env, path); if (path_utf8.c_str() == nullptr) { @@ -1495,6 +1500,7 @@ static const JNINativeMethod gAssetManagerMethods[] = { (void*)NativeGetAssignedPackageIdentifiers}, // AssetManager file methods. + {"nativeContainsAllocatedTable", "(J)Z", (void*)ContainsAllocatedTable}, {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList}, {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset}, {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;", diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index eaf452b5fa71..b9765ea7212c 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -331,6 +331,11 @@ bool AssetManager2::GetOverlayablesToString(const android::StringPiece& package_ return true; } +bool AssetManager2::ContainsAllocatedTable() const { + return std::find_if(apk_assets_.begin(), apk_assets_.end(), + std::mem_fn(&ApkAssets::IsTableAllocated)) != apk_assets_.end(); +} + void AssetManager2::SetConfiguration(const ResTable_config& configuration) { const int diff = configuration_.diff(configuration); configuration_ = configuration; diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h index 879b050b65bd..e57490aab2d8 100644 --- a/libs/androidfw/include/androidfw/ApkAssets.h +++ b/libs/androidfw/include/androidfw/ApkAssets.h @@ -119,31 +119,36 @@ class ApkAssets { package_property_t flags = 0U, std::unique_ptr<const AssetsProvider> override_asset = nullptr); - inline const std::string& GetPath() const { + const std::string& GetPath() const { return path_; } - inline const AssetsProvider* GetAssetsProvider() const { + const AssetsProvider* GetAssetsProvider() const { return assets_provider_.get(); } // This is never nullptr. - inline const LoadedArsc* GetLoadedArsc() const { + const LoadedArsc* GetLoadedArsc() const { return loaded_arsc_.get(); } - inline const LoadedIdmap* GetLoadedIdmap() const { + const LoadedIdmap* GetLoadedIdmap() const { return loaded_idmap_.get(); } - inline bool IsLoader() const { + bool IsLoader() const { return (property_flags_ & PROPERTY_LOADER) != 0; } - inline bool IsOverlay() const { + bool IsOverlay() const { return loaded_idmap_ != nullptr; } + // Returns whether the resources.arsc is allocated in RAM (not mmapped). + bool IsTableAllocated() const { + return resources_asset_ && resources_asset_->isAllocated(); + } + bool IsUpToDate() const; // Creates an Asset from a file on disk. diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index e21abade99a4..30ef25c6a516 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -134,6 +134,9 @@ class AssetManager2 { const std::unordered_map<std::string, std::string>* GetOverlayableMapForPackage(uint32_t package_id) const; + // Returns whether the resources.arsc of any loaded apk assets is allocated in RAM (not mmapped). + bool ContainsAllocatedTable() const; + // Sets/resets the configuration for this AssetManager. This will cause all // caches that are related to the configuration change to be invalidated. void SetConfiguration(const ResTable_config& configuration); |