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
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 9a2e07e..9ca2db9 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1550,6 +1550,16 @@
*/
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 c94d428..f884848 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_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 @@
+ 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 5385100..6b659be 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 @@
@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 d2103af..15a184f 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -819,6 +819,19 @@
}
}
+ /**
+ * 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 @@
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 bf3fc57..12abc25 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -481,6 +481,11 @@
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 @@
(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 eaf452b..b9765ea 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -331,6 +331,11 @@
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 879b050..e57490a 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -119,31 +119,36 @@
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 e21abad..30ef25c 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -134,6 +134,9 @@
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);