Fix an IllegalStateException in the OverlayConfigParser

The issue could be reproduced when the device defined an
overlay configuration file and an overlay package which
was in the configuration file also declared the required
system property in its manifest. The device works fine when
the required system property matches the corresponding
value on the device. But an IllegalStateException was thrown
from the system if it didn't match.

The root cause is that the package parser fails to parse the
overlay package if the required system property in the manifest
does not match the corresponding value on the device. It resulted
in the OverlayConfigParser could not find the package present
in the partition and throws the exception.

To resolve this issue, this CL skips the overlay configuration
if the package not found was caused by the system property
conditionon on the device.

Bug: 193422327
Test: atest OverlayConfigTest
Change-Id: I5650d796d92e3c4825b0d035e8e3b18f36d4cb47
This commit is contained in:
Rhed Jao
2021-09-16 12:04:28 +08:00
parent c1349106f9
commit b62f02cb14
8 changed files with 278 additions and 54 deletions

View File

@@ -35,6 +35,8 @@ import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
@@ -116,14 +118,16 @@ public class OverlayConfig {
}
boolean foundConfigFile = false;
ArrayList<ParsedOverlayInfo> packageManagerOverlayInfos = null;
final Map<String, ParsedOverlayInfo> packageManagerOverlayInfos =
packageProvider == null ? null : getOverlayPackageInfos(packageProvider);
final ArrayList<ParsedConfiguration> overlays = new ArrayList<>();
for (int i = 0, n = partitions.size(); i < n; i++) {
final OverlayPartition partition = partitions.get(i);
final OverlayScanner scanner = (scannerFactory == null) ? null : scannerFactory.get();
final ArrayList<ParsedConfiguration> partitionOverlays =
OverlayConfigParser.getConfigurations(partition, scanner);
OverlayConfigParser.getConfigurations(partition, scanner,
packageManagerOverlayInfos);
if (partitionOverlays != null) {
foundConfigFile = true;
overlays.addAll(partitionOverlays);
@@ -138,12 +142,8 @@ public class OverlayConfig {
if (scannerFactory != null) {
partitionOverlayInfos = new ArrayList<>(scanner.getAllParsedInfos());
} else {
if (packageManagerOverlayInfos == null) {
packageManagerOverlayInfos = getOverlayPackageInfos(packageProvider);
}
// Filter out overlays not present in the partition.
partitionOverlayInfos = new ArrayList<>(packageManagerOverlayInfos);
partitionOverlayInfos = new ArrayList<>(packageManagerOverlayInfos.values());
for (int j = partitionOverlayInfos.size() - 1; j >= 0; j--) {
if (!partition.containsFile(partitionOverlayInfos.get(j).path)) {
partitionOverlayInfos.remove(j);
@@ -289,14 +289,14 @@ public class OverlayConfig {
}
@NonNull
private static ArrayList<ParsedOverlayInfo> getOverlayPackageInfos(
private static Map<String, ParsedOverlayInfo> getOverlayPackageInfos(
@NonNull PackageProvider packageManager) {
final ArrayList<ParsedOverlayInfo> overlays = new ArrayList<>();
final HashMap<String, ParsedOverlayInfo> overlays = new HashMap<>();
packageManager.forEachPackage((ParsingPackageRead p, Boolean isSystem) -> {
if (p.getOverlayTarget() != null && isSystem) {
overlays.add(new ParsedOverlayInfo(p.getPackageName(), p.getOverlayTarget(),
p.getTargetSdkVersion(), p.isOverlayIsStatic(), p.getOverlayPriority(),
new File(p.getBaseApkPath())));
overlays.put(p.getPackageName(), new ParsedOverlayInfo(p.getPackageName(),
p.getOverlayTarget(), p.getTargetSdkVersion(), p.isOverlayIsStatic(),
p.getOverlayPriority(), new File(p.getBaseApkPath())));
}
});
return overlays;

View File

@@ -28,6 +28,7 @@ import android.util.Log;
import android.util.Xml;
import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import libcore.io.IoUtils;
@@ -40,6 +41,7 @@ import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;
/**
* Responsible for parsing configurations of Runtime Resource Overlays that control mutability,
@@ -192,7 +194,8 @@ final class OverlayConfigParser {
*/
@Nullable
static ArrayList<ParsedConfiguration> getConfigurations(
@NonNull OverlayPartition partition, @Nullable OverlayScanner scanner) {
@NonNull OverlayPartition partition, @Nullable OverlayScanner scanner,
@Nullable Map<String, ParsedOverlayInfo> packageManagerOverlayInfos) {
if (partition.getOverlayFolder() == null) {
return null;
}
@@ -207,11 +210,12 @@ final class OverlayConfigParser {
}
final ParsingContext parsingContext = new ParsingContext(partition);
readConfigFile(configFile, scanner, parsingContext);
readConfigFile(configFile, scanner, packageManagerOverlayInfos, parsingContext);
return parsingContext.mOrderedConfigurations;
}
private static void readConfigFile(@NonNull File configFile, @Nullable OverlayScanner scanner,
@Nullable Map<String, ParsedOverlayInfo> packageManagerOverlayInfos,
@NonNull ParsingContext parsingContext) {
FileReader configReader;
try {
@@ -231,10 +235,12 @@ final class OverlayConfigParser {
final String name = parser.getName();
switch (name) {
case "merge":
parseMerge(configFile, parser, scanner, parsingContext);
parseMerge(configFile, parser, scanner, packageManagerOverlayInfos,
parsingContext);
break;
case "overlay":
parseOverlay(configFile, parser, scanner, parsingContext);
parseOverlay(configFile, parser, scanner, packageManagerOverlayInfos,
parsingContext);
break;
default:
Log.w(TAG, String.format("Tag %s is unknown in %s at %s",
@@ -258,7 +264,9 @@ final class OverlayConfigParser {
* configuration files.
*/
private static void parseMerge(@NonNull File configFile, @NonNull XmlPullParser parser,
@Nullable OverlayScanner scanner, @NonNull ParsingContext parsingContext) {
@Nullable OverlayScanner scanner,
@Nullable Map<String, ParsedOverlayInfo> packageManagerOverlayInfos,
@NonNull ParsingContext parsingContext) {
final String path = parser.getAttributeValue(null, "path");
if (path == null) {
throw new IllegalStateException(String.format("<merge> without path in %s at %s"
@@ -304,7 +312,7 @@ final class OverlayConfigParser {
parser.getPositionDescription()));
}
readConfigFile(includedConfigFile, scanner, parsingContext);
readConfigFile(includedConfigFile, scanner, packageManagerOverlayInfos, parsingContext);
parsingContext.mMergeDepth--;
}
@@ -330,7 +338,12 @@ final class OverlayConfigParser {
* order of non-configured overlays when enabled by the OverlayManagerService is undefined.
*/
private static void parseOverlay(@NonNull File configFile, @NonNull XmlPullParser parser,
@Nullable OverlayScanner scanner, @NonNull ParsingContext parsingContext) {
@Nullable OverlayScanner scanner,
@Nullable Map<String, ParsedOverlayInfo> packageManagerOverlayInfos,
@NonNull ParsingContext parsingContext) {
Preconditions.checkArgument((scanner == null) != (packageManagerOverlayInfos == null),
"scanner and packageManagerOverlayInfos cannot be both null or both non-null");
final String packageName = parser.getAttributeValue(null, "package");
if (packageName == null) {
throw new IllegalStateException(String.format("\"<overlay> without package in %s at %s",
@@ -338,16 +351,30 @@ final class OverlayConfigParser {
}
// Ensure the overlay being configured is present in the partition during zygote
// initialization.
// initialization, unless the package is an excluded overlay package.
ParsedOverlayInfo info = null;
if (scanner != null) {
info = scanner.getParsedInfo(packageName);
if (info == null|| !parsingContext.mPartition.containsOverlay(info.path)) {
if (info == null
&& scanner.isExcludedOverlayPackage(packageName, parsingContext.mPartition)) {
Log.d(TAG, "overlay " + packageName + " in partition "
+ parsingContext.mPartition.getOverlayFolder() + " is ignored.");
return;
} else if (info == null || !parsingContext.mPartition.containsOverlay(info.path)) {
throw new IllegalStateException(
String.format("overlay %s not present in partition %s in %s at %s",
packageName, parsingContext.mPartition.getOverlayFolder(),
configFile, parser.getPositionDescription()));
}
} else {
// Zygote shall have crashed itself, if there's an overlay apk not present in the
// partition. For the overlay package not found in the package manager, we can assume
// that it's an excluded overlay package.
if (packageManagerOverlayInfos.get(packageName) == null) {
Log.d(TAG, "overlay " + packageName + " in partition "
+ parsingContext.mPartition.getOverlayFolder() + " is ignored.");
return;
}
}
if (parsingContext.mConfiguredOverlays.contains(packageName)) {

View File

@@ -16,6 +16,9 @@
package com.android.internal.content.om;
import static android.content.pm.parsing.ParsingPackageUtils.PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY;
import static android.content.pm.parsing.ParsingPackageUtils.checkRequiredSystemProperties;
import static com.android.internal.content.om.OverlayConfig.TAG;
import android.annotation.NonNull;
@@ -24,13 +27,17 @@ import android.content.pm.parsing.ApkLite;
import android.content.pm.parsing.ApkLiteParseUtils;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* This class scans a directory containing overlay APKs and extracts information from the overlay
@@ -73,6 +80,14 @@ public class OverlayScanner {
*/
private final ArrayMap<String, ParsedOverlayInfo> mParsedOverlayInfos = new ArrayMap<>();
/**
* A list of pair<packageName, apkFile> which is excluded from the system based on the
* system property condition.
*
* @see #isExcludedOverlayPackage(String, OverlayConfigParser.OverlayPartition)
*/
private final List<Pair<String, File>> mExcludedOverlayPackages = new ArrayList<>();
/** Retrieves information parsed from the overlay with the package name. */
@Nullable
public final ParsedOverlayInfo getParsedInfo(String packageName) {
@@ -85,6 +100,25 @@ public class OverlayScanner {
return mParsedOverlayInfos.values();
}
/**
* Returns {@code true} if the given package name on the given overlay partition is an
* excluded overlay package.
* <p>
* An excluded overlay package declares overlay attributes of required system property in its
* manifest that do not match the corresponding values on the device.
*/
final boolean isExcludedOverlayPackage(@NonNull String packageName,
@NonNull OverlayConfigParser.OverlayPartition overlayPartition) {
for (int i = 0; i < mExcludedOverlayPackages.size(); i++) {
final Pair<String, File> pair = mExcludedOverlayPackages.get(i);
if (pair.first.equals(packageName)
&& overlayPartition.containsOverlay(pair.second)) {
return true;
}
}
return false;
}
/**
* Recursively searches the directory for overlay APKs. If an overlay is found with the same
* package name as a previously scanned overlay, the info of the new overlay will replace the
@@ -115,7 +149,7 @@ public class OverlayScanner {
continue;
}
final ParsedOverlayInfo info = parseOverlayManifest(f);
final ParsedOverlayInfo info = parseOverlayManifest(f, mExcludedOverlayPackages);
if (info == null) {
continue;
}
@@ -124,20 +158,37 @@ public class OverlayScanner {
}
}
/** Extracts information about the overlay from its manifest. */
/**
* Extracts information about the overlay from its manifest. Adds the package name and apk file
* into the {@code outExcludedOverlayPackages} if the apk is excluded from the system based
* on the system property condition.
*/
@VisibleForTesting
public ParsedOverlayInfo parseOverlayManifest(File overlayApk) {
public ParsedOverlayInfo parseOverlayManifest(File overlayApk,
List<Pair<String, File>> outExcludedOverlayPackages) {
final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat();
final ParseResult<ApkLite> ret = ApkLiteParseUtils.parseApkLite(input.reset(),
overlayApk, /* flags */ 0);
overlayApk, PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY);
if (ret.isError()) {
Log.w(TAG, "Got exception loading overlay.", ret.getException());
return null;
}
final ApkLite apkLite = ret.getResult();
return apkLite.getTargetPackageName() == null ? null :
new ParsedOverlayInfo(apkLite.getPackageName(), apkLite.getTargetPackageName(),
apkLite.getTargetSdkVersion(), apkLite.isOverlayIsStatic(),
apkLite.getOverlayPriority(), new File(apkLite.getPath()));
if (apkLite.getTargetPackageName() == null) {
// Not an overlay package
return null;
}
final String propName = apkLite.getRequiredSystemPropertyName();
final String propValue = apkLite.getRequiredSystemPropertyValue();
if ((!TextUtils.isEmpty(propName) || !TextUtils.isEmpty(propValue))
&& !checkRequiredSystemProperties(propName, propValue)) {
// The overlay package should be excluded. Adds it into the outExcludedOverlayPackages
// for overlay configuration parser to ignore it.
outExcludedOverlayPackages.add(Pair.create(apkLite.getPackageName(), overlayApk));
return null;
}
return new ParsedOverlayInfo(apkLite.getPackageName(), apkLite.getTargetPackageName(),
apkLite.getTargetSdkVersion(), apkLite.isOverlayIsStatic(),
apkLite.getOverlayPriority(), new File(apkLite.getPath()));
}
}