From 66ebced16e415caf0e791df206a59b693e07906b Mon Sep 17 00:00:00 2001 From: Daniel Norman Date: Tue, 21 Sep 2021 13:47:36 -0700 Subject: [PATCH] Fix RRO loading from inside APEXes. APEXes can already contain RRO APKs by using the 'rro' apex Soong module field. However, these RROs were not being loaded properly by Zygote or PackageManagerService. For all RROs inside APEXes, the RRO uses the same overlay config that is used for other RROs on the APEX's preinstalled partition. For RROs targeting 'android', which are installed by Zygote using AssetManager: 1. OverlayConfig looks for active APEXes in the apex-info-list file, which is already accessible to Zygote. 2. OverlayConfig passes the APEX module names to OverlayConfigParser, for each preinstalled-partition. 3. OverlayConfigParser uses OverlayScanner to scan the each /apex//overlay directory. For other RROs: 1. PackageManagerService already parses and provides RROs inside APEXes to OverlayConfig. 2. RROs inside APEXes used to have no config rule applied because their path prefix (/apex/) did not match any partition rule. Now, their preinstalled path is used instead. Bug: 199200417 Test: Define a static RRO targeting 'android' inside a /vendor APEX. Define a static RRO for settings provider inside a /vendor APEX. Observe APEXes are enabled by default. Test: Make a change to an RRO inside a /vendor APEX. m ; adb install ; adb reboot; Observe change has taken effect. Change-Id: I2bce9bc704789329b8c6aac6d476f17ff6718e0f --- Android.bp | 1 + .../internal/content/om/OverlayConfig.java | 56 +++++++++++++++++-- .../content/om/OverlayConfigParser.java | 17 ++++-- .../internal/content/om/OverlayScanner.java | 23 ++++++-- core/jni/fd_utils.cpp | 8 +++ .../res/OverlayConfigIterationRule.java | 11 ++-- .../server/pm/InitAndSystemPackageHelper.java | 9 ++- 7 files changed, 104 insertions(+), 21 deletions(-) diff --git a/Android.bp b/Android.bp index 0c1095d82be84..e1cb037b252e0 100644 --- a/Android.bp +++ b/Android.bp @@ -297,6 +297,7 @@ java_defaults { srcs: [ ":framework-non-updatable-sources", "core/java/**/*.logtags", + ":apex-info-list", ], aidl: { generate_get_transaction_name: true, diff --git a/core/java/com/android/internal/content/om/OverlayConfig.java b/core/java/com/android/internal/content/om/OverlayConfig.java index 6c73e7073caeb..29b31d37e862f 100644 --- a/core/java/com/android/internal/content/om/OverlayConfig.java +++ b/core/java/com/android/internal/content/om/OverlayConfig.java @@ -26,20 +26,25 @@ import android.util.ArrayMap; import android.util.IndentingPrintWriter; import android.util.Log; +import com.android.apex.ApexInfo; +import com.android.apex.XmlParser; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.om.OverlayConfigParser.OverlayPartition; import com.android.internal.content.om.OverlayConfigParser.ParsedConfiguration; import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo; import com.android.internal.util.Preconditions; +import com.android.internal.util.function.TriConsumer; import java.io.File; +import java.io.FileInputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.function.BiConsumer; import java.util.function.Supplier; /** @@ -77,7 +82,7 @@ public class OverlayConfig { public interface PackageProvider { /** Performs the given action for each package. */ - void forEachPackage(BiConsumer p); + void forEachPackage(TriConsumer p); } private static final Comparator sStaticOverlayComparator = (c1, c2) -> { @@ -119,6 +124,8 @@ public class OverlayConfig { p))); } + ArrayMap> activeApexesPerPartition = getActiveApexes(partitions); + boolean foundConfigFile = false; final Map packageManagerOverlayInfos = packageProvider == null ? null : getOverlayPackageInfos(packageProvider); @@ -129,7 +136,9 @@ public class OverlayConfig { final OverlayScanner scanner = (scannerFactory == null) ? null : scannerFactory.get(); final ArrayList partitionOverlays = OverlayConfigParser.getConfigurations(partition, scanner, - packageManagerOverlayInfos); + packageManagerOverlayInfos, + activeApexesPerPartition.getOrDefault(partition.type, + Collections.emptyList())); if (partitionOverlays != null) { foundConfigFile = true; overlays.addAll(partitionOverlays); @@ -147,7 +156,8 @@ public class OverlayConfig { // Filter out overlays not present in the partition. partitionOverlayInfos = new ArrayList<>(packageManagerOverlayInfos.values()); for (int j = partitionOverlayInfos.size() - 1; j >= 0; j--) { - if (!partition.containsFile(partitionOverlayInfos.get(j).path)) { + if (!partition.containsFile(partitionOverlayInfos.get(j) + .getOriginalPartitionPath())) { partitionOverlayInfos.remove(j); } } @@ -294,16 +304,50 @@ public class OverlayConfig { private static Map getOverlayPackageInfos( @NonNull PackageProvider packageManager) { final HashMap overlays = new HashMap<>(); - packageManager.forEachPackage((ParsingPackageRead p, Boolean isSystem) -> { + packageManager.forEachPackage((ParsingPackageRead p, Boolean isSystem, + @Nullable File preInstalledApexPath) -> { if (p.getOverlayTarget() != null && isSystem) { overlays.put(p.getPackageName(), new ParsedOverlayInfo(p.getPackageName(), p.getOverlayTarget(), p.getTargetSdkVersion(), p.isOverlayIsStatic(), - p.getOverlayPriority(), new File(p.getBaseApkPath()))); + p.getOverlayPriority(), new File(p.getBaseApkPath()), + preInstalledApexPath)); } }); return overlays; } + /** Returns a map of PartitionType to List of active APEX module names. */ + @NonNull + private static ArrayMap> getActiveApexes( + @NonNull List partitions) { + // An Overlay in an APEX, which is an update of an APEX in a given partition, + // is considered as belonging to that partition. + ArrayMap> result = new ArrayMap<>(); + for (OverlayPartition partition : partitions) { + result.put(partition.type, new ArrayList()); + } + // Read from apex-info-list because ApexManager is not accessible to zygote. + File apexInfoList = new File("/apex/apex-info-list.xml"); + if (apexInfoList.exists() && apexInfoList.canRead()) { + try (FileInputStream stream = new FileInputStream(apexInfoList)) { + List apexInfos = XmlParser.readApexInfoList(stream).getApexInfo(); + for (ApexInfo info : apexInfos) { + if (info.getIsActive()) { + for (OverlayPartition partition : partitions) { + if (partition.containsPath(info.getPreinstalledModulePath())) { + result.get(partition.type).add(info.getModuleName()); + break; + } + } + } + } + } catch (Exception e) { + Log.w(TAG, "Error reading apex-info-list: " + e); + } + } + return result; + } + /** Represents a single call to idmap create-multiple. */ @VisibleForTesting public static class IdmapInvocation { diff --git a/core/java/com/android/internal/content/om/OverlayConfigParser.java b/core/java/com/android/internal/content/om/OverlayConfigParser.java index 053a341dc83ea..0ab7b3d2ef867 100644 --- a/core/java/com/android/internal/content/om/OverlayConfigParser.java +++ b/core/java/com/android/internal/content/om/OverlayConfigParser.java @@ -41,6 +41,7 @@ import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; +import java.util.List; import java.util.Map; /** @@ -195,13 +196,19 @@ final class OverlayConfigParser { @Nullable static ArrayList getConfigurations( @NonNull OverlayPartition partition, @Nullable OverlayScanner scanner, - @Nullable Map packageManagerOverlayInfos) { - if (partition.getOverlayFolder() == null) { - return null; + @Nullable Map packageManagerOverlayInfos, + @NonNull List activeApexes) { + if (scanner != null) { + if (partition.getOverlayFolder() != null) { + scanner.scanDir(partition.getOverlayFolder()); + } + for (String apex : activeApexes) { + scanner.scanDir(new File("/apex/" + apex + "/overlay/")); + } } - if (scanner != null) { - scanner.scanDir(partition.getOverlayFolder()); + if (partition.getOverlayFolder() == null) { + return null; } final File configFile = new File(partition.getOverlayFolder(), CONFIG_DEFAULT_FILENAME); diff --git a/core/java/com/android/internal/content/om/OverlayScanner.java b/core/java/com/android/internal/content/om/OverlayScanner.java index 4387d1bd12009..e4e0228a04291 100644 --- a/core/java/com/android/internal/content/om/OverlayScanner.java +++ b/core/java/com/android/internal/content/om/OverlayScanner.java @@ -54,23 +54,38 @@ public class OverlayScanner { public final boolean isStatic; public final int priority; public final File path; + @Nullable public final File preInstalledApexPath; public ParsedOverlayInfo(String packageName, String targetPackageName, - int targetSdkVersion, boolean isStatic, int priority, File path) { + int targetSdkVersion, boolean isStatic, int priority, File path, + @Nullable File preInstalledApexPath) { this.packageName = packageName; this.targetPackageName = targetPackageName; this.targetSdkVersion = targetSdkVersion; this.isStatic = isStatic; this.priority = priority; this.path = path; + this.preInstalledApexPath = preInstalledApexPath; } @Override public String toString() { return getClass().getSimpleName() + String.format("{packageName=%s" + ", targetPackageName=%s, targetSdkVersion=%s, isStatic=%s" - + ", priority=%s, path=%s}", - packageName, targetPackageName, targetSdkVersion, isStatic, priority, path); + + ", priority=%s, path=%s, preInstalledApexPath=%s}", + packageName, targetPackageName, targetSdkVersion, isStatic, + priority, path, preInstalledApexPath); + } + + /** + * Retrieves the path of the overlay in its original installation partition. + * + * An Overlay in an APEX, which is an update of an APEX in a given partition, + * is considered as belonging to that partition. + */ + @NonNull + public File getOriginalPartitionPath() { + return preInstalledApexPath != null ? preInstalledApexPath : path; } } @@ -189,6 +204,6 @@ public class OverlayScanner { } return new ParsedOverlayInfo(apkLite.getPackageName(), apkLite.getTargetPackageName(), apkLite.getTargetSdkVersion(), apkLite.isOverlayIsStatic(), - apkLite.getOverlayPriority(), new File(apkLite.getPath())); + apkLite.getOverlayPriority(), new File(apkLite.getPath()), null); } } diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index 6f5cc5314d0be..40f6e4f63cd77 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -138,6 +138,14 @@ bool FileDescriptorAllowlist::IsAllowed(const std::string& path) const { return true; } + // Allow Runtime Resource Overlays inside APEXes. + static const char* kOverlayPathSuffix = "/overlay"; + if (android::base::StartsWith(path, kApexPrefix) && + android::base::EndsWith(android::base::Dirname(path), kOverlayPathSuffix) && + android::base::EndsWith(path, kApkSuffix) && path.find("/../") == std::string::npos) { + return true; + } + static const char* kOverlayIdmapPrefix = "/data/resource-cache/"; static const char* kOverlayIdmapSuffix = ".apk@idmap"; if (android::base::StartsWith(path, kOverlayIdmapPrefix) && diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java index 3c093d89723d5..2c31b08bebdc6 100644 --- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java +++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java @@ -30,6 +30,7 @@ import android.util.Pair; import com.android.internal.content.om.OverlayConfig.PackageProvider; import com.android.internal.content.om.OverlayScanner; import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo; +import com.android.internal.util.function.TriConsumer; import org.junit.Assert; import org.junit.rules.TestRule; @@ -42,7 +43,6 @@ import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; -import java.util.function.BiConsumer; import java.util.function.Supplier; /** @@ -74,7 +74,7 @@ public class OverlayConfigIterationRule implements TestRule { TestOverlayInfo(String packageName, String targetPackageName, int targetSdkVersion, boolean isStatic, int priority, File path, String requiredSystemPropertyName, String requiredSystemPropertyValue) { - super(packageName, targetPackageName, targetSdkVersion, isStatic, priority, path); + super(packageName, targetPackageName, targetSdkVersion, isStatic, priority, path, null); this.requiredSystemPropertyName = requiredSystemPropertyName; this.requiredSystemPropertyValue = requiredSystemPropertyValue; } @@ -174,8 +174,8 @@ public class OverlayConfigIterationRule implements TestRule { mIteration = Iteration.SYSTEM_SERVER; doAnswer((InvocationOnMock invocation) -> { final Object[] args = invocation.getArguments(); - final BiConsumer f = - (BiConsumer) args[0]; + final TriConsumer f = + (TriConsumer) args[0]; for (Map.Entry overlay : mTestOverlayInfos.entrySet()) { final ParsingPackageRead a = Mockito.mock(ParsingPackageRead.class); @@ -191,7 +191,8 @@ public class OverlayConfigIterationRule implements TestRule { when(a.isOverlayIsStatic()).thenReturn(info.isStatic); when(a.getOverlayPriority()).thenReturn(info.priority); when(a.getBaseApkPath()).thenReturn(info.path.getPath()); - f.accept(a, !info.path.getPath().contains("data/overlay")); + f.accept(a, !info.path.getPath().contains("data/overlay"), + /*preInstalledApexPath=*/null); } return null; }).when(mPkgProvider).forEachPackage(any()); diff --git a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java index 722198f362df7..5ff0a6f7a59e1 100644 --- a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java +++ b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java @@ -172,9 +172,16 @@ final class InitAndSystemPackageHelper { scanSystemDirs(packageParser, executorService); // Parse overlay configuration files to set default enable state, mutability, and // priority of system overlays. + final ArrayMap apkInApexPreInstalledPaths = new ArrayMap<>(); + for (ApexManager.ActiveApexInfo apexInfo : mPm.mApexManager.getActiveApexInfos()) { + for (String packageName : mPm.mApexManager.getApksInApex(apexInfo.apexModuleName)) { + apkInApexPreInstalledPaths.put(packageName, apexInfo.preInstalledApexPath); + } + } OverlayConfig overlayConfig = OverlayConfig.initializeSystemInstance( consumer -> mPm.forEachPackage( - pkg -> consumer.accept(pkg, pkg.isSystem()))); + pkg -> consumer.accept(pkg, pkg.isSystem(), + apkInApexPreInstalledPaths.get(pkg.getPackageName())))); cleanupSystemPackagesAndInstallStubs(packageParser, executorService, packageSettings, startTime, userIds); packageParser.close();