From 76e669069c68e73d6f5404f415cc5fb0cc0e02af Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Wed, 12 Feb 2020 12:44:05 -0800 Subject: [PATCH] Extract system partitions into standalone class Extract the order system partition list from package manager so OverlayConfig can reuse the PMS partition order. Bug: 119442586 Bug: 135048762 Test: ScanTests and PackageManagerServiceTest Change-Id: If281c5d47e3551eb635c466af6d2400c514518eb --- .../android/content/pm/PackagePartitions.java | 213 ++++++++++++++++++ .../server/pm/PackageManagerService.java | 133 ++++------- .../server/pm/PackageManagerServiceTest.java | 6 +- 3 files changed, 266 insertions(+), 86 deletions(-) create mode 100644 core/java/android/content/pm/PackagePartitions.java diff --git a/core/java/android/content/pm/PackagePartitions.java b/core/java/android/content/pm/PackagePartitions.java new file mode 100644 index 0000000000000..9b8396e340ea5 --- /dev/null +++ b/core/java/android/content/pm/PackagePartitions.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Environment; +import android.os.FileUtils; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.File; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.function.Function; + +/** + * Exposes {@link #SYSTEM_PARTITIONS} which represents the partitions in which application packages + * can be installed. The partitions are ordered from most generic (lowest priority) to most specific + * (greatest priority). + * + * @hide + **/ +public class PackagePartitions { + public static final int PARTITION_SYSTEM = 0; + public static final int PARTITION_VENDOR = 1; + public static final int PARTITION_ODM = 2; + public static final int PARTITION_OEM = 3; + public static final int PARTITION_PRODUCT = 4; + public static final int PARTITION_SYSTEM_EXT = 5; + + @IntDef(flag = true, prefix = { "PARTITION_" }, value = { + PARTITION_SYSTEM, + PARTITION_VENDOR, + PARTITION_ODM, + PARTITION_OEM, + PARTITION_PRODUCT, + PARTITION_SYSTEM_EXT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PartitionType {} + + /** + * The list of all system partitions that may contain packages in ascending order of + * specificity (the more generic, the earlier in the list a partition appears). + */ + private static final ArrayList SYSTEM_PARTITIONS = + new ArrayList<>(Arrays.asList( + new SystemPartition(Environment.getRootDirectory(), PARTITION_SYSTEM, + true /* containsPrivApp */, false /* containsOverlay */), + new SystemPartition(Environment.getVendorDirectory(), PARTITION_VENDOR, + true /* containsPrivApp */, true /* containsOverlay */), + new SystemPartition(Environment.getOdmDirectory(), PARTITION_ODM, + true /* containsPrivApp */, true /* containsOverlay */), + new SystemPartition(Environment.getOemDirectory(), PARTITION_OEM, + false /* containsPrivApp */, true /* containsOverlay */), + new SystemPartition(Environment.getProductDirectory(), PARTITION_PRODUCT, + true /* containsPrivApp */, true /* containsOverlay */), + new SystemPartition(Environment.getSystemExtDirectory(), PARTITION_SYSTEM_EXT, + true /* containsPrivApp */, true /* containsOverlay */))); + + /** + * Returns a list in which the elements are products of the specified function applied to the + * list of {@link #SYSTEM_PARTITIONS} in increasing specificity order. + */ + public static ArrayList getOrderedPartitions( + @NonNull Function producer) { + final ArrayList out = new ArrayList<>(); + for (int i = 0, n = SYSTEM_PARTITIONS.size(); i < n; i++) { + final T v = producer.apply(SYSTEM_PARTITIONS.get(i)); + if (v != null) { + out.add(v); + } + } + return out; + } + + /** Represents a partition that contains application packages. */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public static class SystemPartition { + @NonNull + public final File folder; + + @PartitionType + public final int type; + + @Nullable + private final DeferredCanonicalFile mAppFolder; + + @Nullable + private final DeferredCanonicalFile mPrivAppFolder; + + @Nullable + private final DeferredCanonicalFile mOverlayFolder; + + private SystemPartition(@NonNull File folder, @PartitionType int type, + boolean containsPrivApp, boolean containsOverlay) { + this.folder = folder; + this.type = type; + this.mAppFolder = new DeferredCanonicalFile(folder, "app"); + this.mPrivAppFolder = containsPrivApp ? + new DeferredCanonicalFile(folder, "priv-app") : null; + this.mOverlayFolder = containsOverlay ? + new DeferredCanonicalFile(folder, "overlay") : null; + } + + public SystemPartition(@NonNull SystemPartition original) { + this.folder = original.folder; + this.type = original.type; + this.mAppFolder = original.mAppFolder; + this.mPrivAppFolder = original.mPrivAppFolder; + this.mOverlayFolder = original.mOverlayFolder; + } + + /** + * Creates a partition containing the same folders as the original partition but with a + * different root folder. + */ + public SystemPartition(@NonNull File rootFolder, @NonNull SystemPartition partition) { + this(rootFolder, partition.type, partition.mPrivAppFolder != null, + partition.mOverlayFolder != null); + } + + /** Returns the canonical app folder of the partition. */ + @Nullable + public File getAppFolder() { + return mAppFolder == null ? null : mAppFolder.getFile(); + } + + /** Returns the canonical priv-app folder of the partition, if one exists. */ + @Nullable + public File getPrivAppFolder() { + return mPrivAppFolder == null ? null : mPrivAppFolder.getFile(); + } + + /** Returns the canonical overlay folder of the partition, if one exists. */ + @Nullable + public File getOverlayFolder() { + return mOverlayFolder == null ? null : mOverlayFolder.getFile(); + } + + /** Returns whether the partition contains the specified file in its priv-app folder. */ + public boolean containsPrivApp(@NonNull File scanFile) { + return FileUtils.contains(mPrivAppFolder.getFile(), scanFile); + } + + /** Returns whether the partition contains the specified file in its app folder. */ + public boolean containsApp(@NonNull File scanFile) { + return FileUtils.contains(mAppFolder.getFile(), scanFile); + } + + /** Returns whether the partition contains the specified file in its overlay folder. */ + public boolean containsOverlay(@NonNull File scanFile) { + return FileUtils.contains(mOverlayFolder.getFile(), scanFile); + } + + /** Returns whether the partition contains the specified file. */ + public boolean containsPath(@NonNull String path) { + return path.startsWith(folder.getPath() + "/"); + } + + /** Returns whether the partition contains the specified file in its priv-app folder. */ + public boolean containsPrivPath(@NonNull String path) { + return mPrivAppFolder != null + && path.startsWith(mPrivAppFolder.getFile().getPath() + "/"); + } + } + + /** + * A class that defers the canonicalization of its underlying file. This must be done so + * processes do not attempt to canonicalize files in directories for which the process does not + * have the correct selinux policies. + */ + private static class DeferredCanonicalFile { + private boolean mIsCanonical; + private File mFile; + private DeferredCanonicalFile(File dir, String fileName) { + mFile = new File(dir, fileName); + mIsCanonical = false; + } + + private File getFile() { + if (mIsCanonical) { + return mFile; + } + mIsCanonical = true; + try { + mFile = mFile.getCanonicalFile(); + } catch (IOException ignore) { + // failed to look up canonical path, continue with original one + } + return mFile; + } + } +} diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 88c048cea2671..85c7f4b41b61d 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -185,6 +185,8 @@ import android.content.pm.PackageParser.PackageParserException; import android.content.pm.PackageParser.ParseFlags; import android.content.pm.PackageParser.SigningDetails; import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion; +import android.content.pm.PackagePartitions; +import android.content.pm.PackagePartitions.SystemPartition; import android.content.pm.PackageStats; import android.content.pm.PackageUserState; import android.content.pm.ParceledListSlice; @@ -792,22 +794,10 @@ public class PackageManagerService extends IPackageManager.Stub * specificity (the more generic, the earlier in the list a partition appears). */ @VisibleForTesting(visibility = Visibility.PRIVATE) - public static final List SYSTEM_PARTITIONS = Collections.unmodifiableList( - Arrays.asList( - new SystemPartition(Environment.getRootDirectory(), 0 /* scanFlag */, - false /* hasOverlays */), - new SystemPartition(Environment.getVendorDirectory(), SCAN_AS_VENDOR, - true /* hasOverlays */), - new SystemPartition(Environment.getOdmDirectory(), SCAN_AS_ODM, - true /* hasOverlays */), - new SystemPartition(Environment.getOemDirectory(), SCAN_AS_OEM, - true /* hasOverlays */), - new SystemPartition(Environment.getProductDirectory(), SCAN_AS_PRODUCT, - true /* hasOverlays */), - new SystemPartition(Environment.getSystemExtDirectory(), SCAN_AS_SYSTEM_EXT, - true /* hasOverlays */))); + public static final List SYSTEM_PARTITIONS = Collections.unmodifiableList( + PackagePartitions.getOrderedPartitions(ScanPartition::new)); - private final List mDirsToScanAsSystem; + private final List mDirsToScanAsSystem; /** * Unit tests will instantiate, extend and/or mock to mock dependencies / behaviors. @@ -2588,66 +2578,44 @@ public class PackageManagerService extends IPackageManager.Stub } } - @VisibleForTesting(visibility = Visibility.PRIVATE) - public static class SystemPartition { - public final File folder; + @VisibleForTesting + public static class ScanPartition extends SystemPartition { + @ScanFlags public final int scanFlag; - public final File appFolder; - @Nullable - public final File privAppFolder; - @Nullable - public final File overlayFolder; - - private static boolean shouldScanPrivApps(@ScanFlags int scanFlags) { - if ((scanFlags & SCAN_AS_OEM) != 0) { - return false; - } - if (scanFlags == 0) { // /system partition - return true; - } - if ((scanFlags - & (SCAN_AS_VENDOR | SCAN_AS_ODM | SCAN_AS_PRODUCT | SCAN_AS_SYSTEM_EXT)) != 0) { - return true; - } - if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) { - return true; - } - return false; + public ScanPartition(@NonNull SystemPartition partition) { + super(partition); + scanFlag = scanFlagForPartition(partition); } - private SystemPartition(File folder, int scanFlag, boolean hasOverlays) { - this.folder = folder; - this.scanFlag = scanFlag; - this.appFolder = toCanonical(new File(folder, "app")); - this.privAppFolder = shouldScanPrivApps(scanFlag) - ? toCanonical(new File(folder, "priv-app")) - : null; - this.overlayFolder = hasOverlays ? toCanonical(new File(folder, "overlay")) : null; + /** + * Creates a partition containing the same folders as the original partition but with a + * different root folder. The new partition will include the scan flags of the original + * partition along with any specified additional scan flags. + */ + public ScanPartition(@NonNull File folder, @NonNull ScanPartition original, + @ScanFlags int additionalScanFlag) { + super(folder, original); + this.scanFlag = original.scanFlag | additionalScanFlag; } - public boolean containsPrivApp(File scanFile) { - return FileUtils.contains(privAppFolder, scanFile); - } - - public boolean containsApp(File scanFile) { - return FileUtils.contains(appFolder, scanFile); - } - - public boolean containsPath(String path) { - return path.startsWith(folder.getPath() + "/"); - } - - public boolean containsPrivPath(String path) { - return privAppFolder != null && path.startsWith(privAppFolder.getPath() + "/"); - } - - private static File toCanonical(File dir) { - try { - return dir.getCanonicalFile(); - } catch (IOException e) { - // failed to look up canonical path, continue with original one - return dir; + private static int scanFlagForPartition(PackagePartitions.SystemPartition partition) { + switch (partition.type) { + case PackagePartitions.PARTITION_SYSTEM: + return 0; + case PackagePartitions.PARTITION_VENDOR: + return SCAN_AS_VENDOR; + case PackagePartitions.PARTITION_ODM: + return SCAN_AS_ODM; + case PackagePartitions.PARTITION_OEM: + return SCAN_AS_OEM; + case PackagePartitions.PARTITION_PRODUCT: + return SCAN_AS_PRODUCT; + case PackagePartitions.PARTITION_SYSTEM_EXT: + return SCAN_AS_SYSTEM_EXT; + default: + throw new IllegalStateException("Unable to determine scan flag for " + + partition.folder); } } } @@ -2751,7 +2719,7 @@ public class PackageManagerService extends IPackageManager.Stub mDirsToScanAsSystem = new ArrayList<>(); mDirsToScanAsSystem.addAll(SYSTEM_PARTITIONS); mDirsToScanAsSystem.addAll(mApexManager.getActiveApexInfos().stream() - .map(ai -> resolveApexToSystemPartition(ai)) + .map(PackageManagerService::resolveApexToScanPartition) .filter(Objects::nonNull).collect(Collectors.toList())); Slog.d(TAG, "Directories scanned as system partitions: [" + mDirsToScanAsSystem.stream().map( @@ -2900,11 +2868,11 @@ public class PackageManagerService extends IPackageManager.Stub // For security and version matching reason, only consider overlay packages if they // reside in the right directory. for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) { - final SystemPartition partition = mDirsToScanAsSystem.get(i); - if (partition.overlayFolder == null) { + final ScanPartition partition = mDirsToScanAsSystem.get(i); + if (partition.getOverlayFolder() == null) { continue; } - scanDirTracedLI(partition.overlayFolder, systemParseFlags, + scanDirTracedLI(partition.getOverlayFolder(), systemParseFlags, systemScanFlags | partition.scanFlag, 0, packageParser, executorService); } @@ -2917,13 +2885,13 @@ public class PackageManagerService extends IPackageManager.Stub "Failed to load frameworks package; check log for warnings"); } for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) { - final SystemPartition partition = mDirsToScanAsSystem.get(i); - if (partition.privAppFolder != null) { - scanDirTracedLI(partition.privAppFolder, systemParseFlags, + final ScanPartition partition = mDirsToScanAsSystem.get(i); + if (partition.getPrivAppFolder() != null) { + scanDirTracedLI(partition.getPrivAppFolder(), systemParseFlags, systemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0, packageParser, executorService); } - scanDirTracedLI(partition.appFolder, systemParseFlags, + scanDirTracedLI(partition.getAppFolder(), systemParseFlags, systemScanFlags | partition.scanFlag, 0, packageParser, executorService); } @@ -3105,7 +3073,7 @@ public class PackageManagerService extends IPackageManager.Stub @ParseFlags int reparseFlags = 0; @ScanFlags int rescanFlags = 0; for (int i1 = 0, size = mDirsToScanAsSystem.size(); i1 < size; i1++) { - SystemPartition partition = mDirsToScanAsSystem.get(i1); + final ScanPartition partition = mDirsToScanAsSystem.get(i1); if (partition.containsPrivApp(scanFile)) { reparseFlags = systemParseFlags; rescanFlags = systemScanFlags | SCAN_AS_PRIVILEGED @@ -17957,14 +17925,13 @@ public class PackageManagerService extends IPackageManager.Stub } } - private static @Nullable SystemPartition resolveApexToSystemPartition( + private static @Nullable ScanPartition resolveApexToScanPartition( ApexManager.ActiveApexInfo apexInfo) { for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) { - SystemPartition sp = SYSTEM_PARTITIONS.get(i); + ScanPartition sp = SYSTEM_PARTITIONS.get(i); if (apexInfo.preInstalledApexPath.getAbsolutePath().startsWith( sp.folder.getAbsolutePath())) { - return new SystemPartition(apexInfo.apexDirectory, - sp.scanFlag | SCAN_AS_APK_IN_APEX, false /* hasOverlays */); + return new ScanPartition(apexInfo.apexDirectory, sp, SCAN_AS_APK_IN_APEX); } } return null; @@ -18065,7 +18032,7 @@ public class PackageManagerService extends IPackageManager.Stub | PackageParser.PARSE_IS_SYSTEM_DIR; @ScanFlags int scanFlags = SCAN_AS_SYSTEM; for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) { - SystemPartition partition = mDirsToScanAsSystem.get(i); + ScanPartition partition = mDirsToScanAsSystem.get(i); if (partition.containsPath(codePathString)) { scanFlags |= partition.scanFlag; if (partition.containsPrivPath(codePathString)) { diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java index 85840e1359093..5c153228415a8 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java @@ -118,15 +118,15 @@ public class PackageManagerServiceTest { String[] partitions = { "system", "vendor", "odm", "oem", "product", "system_ext" }; String[] appdir = { "app", "priv-app" }; for (int i = 0; i < partitions.length; i++) { - final PackageManagerService.SystemPartition systemPartition = + final PackageManagerService.ScanPartition scanPartition = PackageManagerService.SYSTEM_PARTITIONS.get(i); for (int j = 0; j < appdir.length; j++) { String canonical = new File("/" + partitions[i]).getCanonicalPath(); String path = String.format("%s/%s/A.apk", canonical, appdir[j]); - Assert.assertEquals(j == 1 && i != 3, systemPartition.containsPrivPath(path)); + Assert.assertEquals(j == 1 && i != 3, scanPartition.containsPrivPath(path)); - final int scanFlag = systemPartition.scanFlag; + final int scanFlag = scanPartition.scanFlag; Assert.assertEquals(i == 1, scanFlag == PackageManagerService.SCAN_AS_VENDOR); Assert.assertEquals(i == 2, scanFlag == PackageManagerService.SCAN_AS_ODM); Assert.assertEquals(i == 3, scanFlag == PackageManagerService.SCAN_AS_OEM);