diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl index b0ce6a55e9ba0..12911d6e12324 100644 --- a/core/java/android/content/pm/IPackageInstaller.aidl +++ b/core/java/android/content/pm/IPackageInstaller.aidl @@ -62,6 +62,8 @@ interface IPackageInstaller { void bypassNextStagedInstallerCheck(boolean value); + void bypassNextAllowedApexUpdateCheck(boolean value); + void setAllowUnlimitedSilentUpdates(String installerPackageName); void setSilentUpdatesThrottleTime(long throttleTimeInSeconds); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 33a34be1b9687..2ed00b5d2982d 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1278,6 +1278,13 @@ public abstract class PackageManager { */ public static final int INSTALL_STAGED = 0x00200000; + /** + * Flag parameter for {@link #installPackage} to indicate that check whether given APEX can be + * updated should be disabled for this install. + * @hide + */ + public static final int INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK = 0x00400000; + /** @hide */ @IntDef(flag = true, value = { DONT_KILL_APP, diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index bd0de2984b7fd..6976ace36c115 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -240,6 +240,7 @@ public class SystemConfig { private final ArraySet mRollbackWhitelistedPackages = new ArraySet<>(); private final ArraySet mWhitelistedStagedInstallers = new ArraySet<>(); + private final ArraySet mAllowedPartnerApexes = new ArraySet<>(); /** * Map of system pre-defined, uniquely named actors; keys are namespace, @@ -410,6 +411,10 @@ public class SystemConfig { return mWhitelistedStagedInstallers; } + public Set getAllowedPartnerApexes() { + return mAllowedPartnerApexes; + } + public ArraySet getAppDataIsolationWhitelistedApps() { return mAppDataIsolationWhitelistedApps; } @@ -1212,6 +1217,21 @@ public class SystemConfig { } XmlUtils.skipCurrentTag(parser); } break; + case "allowed-partner-apex": { + // TODO(b/189274479): should this be allowOemPermissions instead? + if (allowAppConfigs) { + String pkgName = parser.getAttributeValue(null, "package"); + if (pkgName == null) { + Slog.w(TAG, "<" + name + "> without package in " + permFile + + " at " + parser.getPositionDescription()); + } else { + mAllowedPartnerApexes.add(pkgName); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } + XmlUtils.skipCurrentTag(parser); + } break; default: { Slog.w(TAG, "Tag " + name + " is unknown in " + permFile + " at " + parser.getPositionDescription()); diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 60a7571182225..d2ed08f669446 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -157,6 +157,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements private volatile boolean mOkToSendBroadcasts = false; private volatile boolean mBypassNextStagedInstallerCheck = false; + private volatile boolean mBypassNextAllowedApexUpdateCheck = false; /** * File storing persisted {@link #mSessions} metadata. @@ -650,6 +651,13 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements throw new IllegalArgumentException( "Non-staged APEX session doesn't support INSTALL_ENABLE_ROLLBACK"); } + if (isCalledBySystemOrShell(callingUid) || mBypassNextAllowedApexUpdateCheck) { + params.installFlags |= PackageManager.INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK; + } else { + // Only specific APEX updates (installed through ADB, or for CTS tests) can disable + // allowed APEX update check. + params.installFlags &= ~PackageManager.INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK; + } } if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0 @@ -674,6 +682,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } mBypassNextStagedInstallerCheck = false; + mBypassNextAllowedApexUpdateCheck = false; + if (!params.isMultiPackage) { // Only system components can circumvent runtime permissions when installing. if ((params.installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0 @@ -1106,6 +1116,14 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements mBypassNextStagedInstallerCheck = value; } + @Override + public void bypassNextAllowedApexUpdateCheck(boolean value) { + if (!isCalledBySystemOrShell(Binder.getCallingUid())) { + throw new SecurityException("Caller not allowed to bypass allowed apex update check"); + } + mBypassNextAllowedApexUpdateCheck = value; + } + /** * Set an installer to allow for the unlimited silent updates. */ diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 4b0eb65468888..c33130037027a 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -147,6 +147,7 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.server.LocalServices; +import com.android.server.SystemConfig; import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.dex.DexManager; import com.android.server.pm.parsing.pkg.AndroidPackage; @@ -2238,6 +2239,26 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { .setAdmin(mInstallSource.installerPackageName) .write(); } + + // Check if APEX update is allowed. We do this check in handleInstall, since this is one of + // the places that: + // * Shared between staged and non-staged APEX update flows. + // * Only is called after boot completes. + // The later is important, since isApexUpdateAllowed check depends on the + // ModuleInfoProvider, which is only populated after device has booted. + if (isApexSession()) { + boolean checkApexUpdateAllowed = + (params.installFlags & PackageManager.INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK) + == 0; + synchronized (mLock) { + if (checkApexUpdateAllowed && !isApexUpdateAllowed(mPackageName)) { + onSessionValidationFailure(PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, + "Update of APEX package " + mPackageName + " is not allowed"); + return; + } + } + } + if (params.isStaged) { mStagingManager.commitSession(mStagedSession); // TODO(b/136257624): CTS test fails if we don't send session finished broadcast, even @@ -2776,6 +2797,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return sessionContains((s) -> !s.isApexSession()); } + private boolean isApexUpdateAllowed(String apexPackageName) { + return mPm.getModuleInfo(apexPackageName, 0) != null + || SystemConfig.getInstance().getAllowedPartnerApexes().contains(apexPackageName); + } + /** * Validate apex install. *

diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 49559f299fa04..1aa80a953d6ce 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -307,6 +307,8 @@ class PackageManagerShellCommand extends ShellCommand { return runLogVisibility(); case "bypass-staged-installer-check": return runBypassStagedInstallerCheck(); + case "bypass-allowed-apex-update-check": + return runBypassAllowedApexUpdateCheck(); case "set-silent-updates-policy": return runSetSilentUpdatesPolicy(); default: { @@ -424,6 +426,20 @@ class PackageManagerShellCommand extends ShellCommand { } } + private int runBypassAllowedApexUpdateCheck() { + final PrintWriter pw = getOutPrintWriter(); + try { + mInterface.getPackageInstaller() + .bypassNextAllowedApexUpdateCheck(Boolean.parseBoolean(getNextArg())); + return 0; + } catch (RemoteException e) { + pw.println("Failure [" + + e.getClass().getName() + " - " + + e.getMessage() + "]"); + return -1; + } + } + private int uninstallSystemUpdates(String packageName) { final PrintWriter pw = getOutPrintWriter(); boolean failedUninstalls = false; diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java index 17d99e6527264..f880563e2880d 100644 --- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java @@ -221,6 +221,64 @@ public class SystemConfigTest { assertThat(mSysConfig.getWhitelistedStagedInstallers()).isEmpty(); } + /** + * Tests that readPermissions works correctly with {@link SystemConfig#ALLOW_APP_CONFIGS} + * permission flag for the tag: {@code allowed-partner-apex}. + */ + @Test + public void readPermissions_allowAppConfigs_parsesPartnerApexAllowList() + throws IOException { + final String contents = + "\n" + + " \n" + + ""; + final File folder = createTempSubfolder("folder"); + createTempFile(folder, "partner-apex-allowlist.xml", contents); + + mSysConfig.readPermissions(folder, /* Grant all permission flags */ ~0); + + assertThat(mSysConfig.getAllowedPartnerApexes()).containsExactly("com.android.apex1"); + } + + /** + * Tests that readPermissions works correctly with {@link SystemConfig#ALLOW_APP_CONFIGS} + * permission flag for the tag: {@code allowed-partner-apex}. + */ + @Test + public void readPermissions_allowAppConfigs_parsesPartnerApexAllowList_noPackage() + throws IOException { + final String contents = + "\n" + + " \n" + + ""; + final File folder = createTempSubfolder("folder"); + createTempFile(folder, "partner-apex-allowlist.xml", contents); + + mSysConfig.readPermissions(folder, /* Grant all permission flags */ ~0); + + assertThat(mSysConfig.getAllowedPartnerApexes()).isEmpty(); + } + + + /** + * Tests that readPermissions works correctly without {@link SystemConfig#ALLOW_APP_CONFIGS} + * permission flag for the tag: {@code allowed-partner-apex}. + */ + @Test + public void readPermissions_notAllowAppConfigs_doesNotParsePartnerApexAllowList() + throws IOException { + final String contents = + "\n" + + " \n" + + ""; + final File folder = createTempSubfolder("folder"); + createTempFile(folder, "partner-apex-allowlist.xml", contents); + + mSysConfig.readPermissions(folder, /* Grant all but ALLOW_APP_CONFIGS flag */ ~0x08); + + assertThat(mSysConfig.getAllowedPartnerApexes()).isEmpty(); + } + /** * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents. *