Handle compressed APK enable/disable

There are two scenarios handled with this change:

* When updates are removed for a compressed APK, we re-scan
  the stub and disable the package. The stub must never be
  enabled by its own

* When a stub is enabled programatically [eg. via settings],
  extract the compressed APK to the data partition, scan
  the extracted APKs and only then enable the application

Bug: 64386107
Test: Manual; create a test stub app and verify enable/disable work as expected from the settings UI
Test: Manual; chagne the code to trigger various failures and verify the system remains stable
Change-Id: Ia4cb8e25ce8da11edc910d531eedaa37a9093002
This commit is contained in:
Todd Kennedy
2017-08-18 14:22:50 -07:00
parent 003bd7e3ec
commit 7098c1cbec

View File

@@ -85,6 +85,8 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.pm.PackageParser.PARSE_IS_PRIVILEGED;
import static android.content.pm.PackageParser.isApkFile;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDWR;
@@ -11050,7 +11052,7 @@ public class PackageManagerService extends IPackageManager.Stub
r.info.encryptionAware = r.info.directBootAware = true;
}
}
if (compressedFileExists(pkg.baseCodePath)) {
if (compressedFileExists(pkg.codePath)) {
pkg.isStub = true;
}
} else {
@@ -19669,6 +19671,10 @@ public class PackageManagerService extends IPackageManager.Stub
// writer
synchronized (mPackages) {
// NOTE: The system package always needs to be enabled; even if it's for
// a compressed stub. If we don't, installing the system package fails
// during scan [scanning checks the disabled packages]. We will reverse
// this later, after we've "installed" the stub.
// Reinstate the old system package
enableSystemPackageLPw(disabledPs.pkg);
// Remove any native libraries from the upgraded package.
@@ -19677,23 +19683,38 @@ public class PackageManagerService extends IPackageManager.Stub
// Install the system package
if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
int parseFlags = mDefParseFlags
| PackageParser.PARSE_MUST_BE_APK
| PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR;
if (locationIsPrivileged(disabledPs.codePath)) {
parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
}
final PackageParser.Package newPkg;
try {
newPkg = scanPackageTracedLI(disabledPs.codePath, parseFlags, 0 /* scanFlags */,
0 /* currentTime */, null);
installPackageFromSystemLIF(disabledPs.codePath, false /*isPrivileged*/, allUserHandles,
outInfo.origUsers, deletedPs.getPermissionsState(), writeSettings);
} catch (PackageManagerException e) {
Slog.w(TAG, "Failed to restore system package:" + deletedPkg.packageName + ": "
+ e.getMessage());
return false;
} finally {
if (disabledPs.pkg.isStub) {
mSettings.disableSystemPackageLPw(disabledPs.name, true /*replaced*/);
}
}
return true;
}
/**
* Installs a package that's already on the system partition.
*/
private PackageParser.Package installPackageFromSystemLIF(@NonNull File codePath,
boolean isPrivileged, @Nullable int[] allUserHandles, @Nullable int[] origUserHandles,
@Nullable PermissionsState origPermissionState, boolean writeSettings)
throws PackageManagerException {
int parseFlags = mDefParseFlags
| PackageParser.PARSE_MUST_BE_APK
| PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR;
if (isPrivileged || locationIsPrivileged(codePath)) {
parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
}
final PackageParser.Package newPkg =
scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/, 0 /*currentTime*/, null);
try {
// update shared libraries for the newly re-installed system package
@@ -19711,17 +19732,21 @@ public class PackageManagerService extends IPackageManager.Stub
// Propagate the permissions state as we do not want to drop on the floor
// runtime permissions. The update permissions method below will take
// care of removing obsolete permissions and grant install permissions.
ps.getPermissionsState().copyFrom(deletedPs.getPermissionsState());
if (origPermissionState != null) {
ps.getPermissionsState().copyFrom(origPermissionState);
}
updatePermissionsLPw(newPkg.packageName, newPkg,
UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG);
final boolean applyUserRestrictions
= (allUserHandles != null) && (origUserHandles != null);
if (applyUserRestrictions) {
boolean installedStateChanged = false;
if (DEBUG_REMOVE) {
Slog.d(TAG, "Propagating install state across reinstall");
}
for (int userId : allUserHandles) {
final boolean installed = ArrayUtils.contains(outInfo.origUsers, userId);
final boolean installed = ArrayUtils.contains(origUserHandles, userId);
if (DEBUG_REMOVE) {
Slog.d(TAG, " user " + userId + " => " + installed);
}
@@ -19744,7 +19769,7 @@ public class PackageManagerService extends IPackageManager.Stub
mSettings.writeLPr();
}
}
return true;
return newPkg;
}
private boolean deleteInstalledPackageLIF(PackageSetting ps,
@@ -21675,77 +21700,183 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
}
}
synchronized (mPackages) {
if (callingUid == Process.SHELL_UID
&& (pkgSetting.pkgFlags & ApplicationInfo.FLAG_TEST_ONLY) == 0) {
// Shell can only change whole packages between ENABLED and DISABLED_USER states
// unless it is a test package.
int oldState = pkgSetting.getEnabled(userId);
if (className == null
&&
(oldState == COMPONENT_ENABLED_STATE_DISABLED_USER
|| oldState == COMPONENT_ENABLED_STATE_DEFAULT
|| oldState == COMPONENT_ENABLED_STATE_ENABLED)
&&
(newState == COMPONENT_ENABLED_STATE_DISABLED_USER
|| newState == COMPONENT_ENABLED_STATE_DEFAULT
|| newState == COMPONENT_ENABLED_STATE_ENABLED)) {
// ok
} else {
throw new SecurityException(
"Shell cannot change component state for " + packageName + "/"
+ className + " to " + newState);
}
}
if (className == null) {
// We're dealing with an application/package level state change
if (pkgSetting.getEnabled(userId) == newState) {
// Nothing to do
return;
}
if (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
|| newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
// Don't care about who enables an app.
callingPackage = null;
}
pkgSetting.setEnabled(newState, userId, callingPackage);
// pkgSetting.pkg.mSetEnabled = newState;
if (callingUid == Process.SHELL_UID
&& (pkgSetting.pkgFlags & ApplicationInfo.FLAG_TEST_ONLY) == 0) {
// Shell can only change whole packages between ENABLED and DISABLED_USER states
// unless it is a test package.
int oldState = pkgSetting.getEnabled(userId);
if (className == null
&&
(oldState == COMPONENT_ENABLED_STATE_DISABLED_USER
|| oldState == COMPONENT_ENABLED_STATE_DEFAULT
|| oldState == COMPONENT_ENABLED_STATE_ENABLED)
&&
(newState == COMPONENT_ENABLED_STATE_DISABLED_USER
|| newState == COMPONENT_ENABLED_STATE_DEFAULT
|| newState == COMPONENT_ENABLED_STATE_ENABLED)) {
// ok
} else {
// We're dealing with a component level state change
// First, verify that this is a valid class name.
PackageParser.Package pkg = pkgSetting.pkg;
if (pkg == null || !pkg.hasComponentClassName(className)) {
if (pkg != null &&
pkg.applicationInfo.targetSdkVersion >=
Build.VERSION_CODES.JELLY_BEAN) {
throw new IllegalArgumentException("Component class " + className
+ " does not exist in " + packageName);
} else {
Slog.w(TAG, "Failed setComponentEnabledSetting: component class "
+ className + " does not exist in " + packageName);
}
}
switch (newState) {
case COMPONENT_ENABLED_STATE_ENABLED:
if (!pkgSetting.enableComponentLPw(className, userId)) {
return;
}
break;
case COMPONENT_ENABLED_STATE_DISABLED:
if (!pkgSetting.disableComponentLPw(className, userId)) {
return;
}
break;
case COMPONENT_ENABLED_STATE_DEFAULT:
if (!pkgSetting.restoreComponentLPw(className, userId)) {
return;
}
break;
default:
Slog.e(TAG, "Invalid new component state: " + newState);
throw new SecurityException(
"Shell cannot change component state for " + packageName + "/"
+ className + " to " + newState);
}
}
if (className == null) {
// We're dealing with an application/package level state change
if (pkgSetting.getEnabled(userId) == newState) {
// Nothing to do
return;
}
// If we're enabling a system stub, there's a little more work to do.
// Prior to enabling the package, we need to decompress the APK(s) to the
// data partition and then replace the version on the system partition.
final PackageParser.Package deletedPkg = pkgSetting.pkg;
final boolean isSystemStub = deletedPkg.isStub
&& deletedPkg.isSystemApp();
if (isSystemStub
&& (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
|| newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)) {
final File codePath = decompressPackage(deletedPkg);
if (codePath == null) {
Slog.e(TAG, "couldn't decompress pkg: " + pkgSetting.name);
return;
}
// TODO remove direct parsing of the package object during internal cleanup
// of scan package
// We need to call parse directly here for no other reason than we need
// the new package in order to disable the old one [we use the information
// for some internal optimization to optionally create a new package setting
// object on replace]. However, we can't get the package from the scan
// because the scan modifies live structures and we need to remove the
// old [system] package from the system before a scan can be attempted.
// Once scan is indempotent we can remove this parse and use the package
// object we scanned, prior to adding it to package settings.
final PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setDisplayMetrics(mMetrics);
pp.setCallback(mPackageParserCallback);
final PackageParser.Package tmpPkg;
try {
final int parseFlags = mDefParseFlags
| PackageParser.PARSE_MUST_BE_APK
| PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR;
tmpPkg = pp.parsePackage(codePath, parseFlags);
} catch (PackageParserException e) {
Slog.w(TAG, "Failed to parse compressed system package:" + pkgSetting.name, e);
return;
}
synchronized (mInstallLock) {
// Disable the stub and remove any package entries
removePackageLI(deletedPkg, true);
synchronized (mPackages) {
disableSystemPackageLPw(deletedPkg, tmpPkg);
}
final PackageParser.Package newPkg;
try (PackageFreezer freezer =
freezePackage(deletedPkg.packageName, "setEnabledSetting")) {
final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
| PackageParser.PARSE_ENFORCE_CODE;
newPkg = scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/,
0 /*currentTime*/, null /*user*/);
prepareAppDataAfterInstallLIF(newPkg);
synchronized (mPackages) {
try {
updateSharedLibrariesLPr(newPkg, null);
} catch (PackageManagerException e) {
Slog.e(TAG, "updateAllSharedLibrariesLPw failed: ", e);
}
updatePermissionsLPw(newPkg.packageName, newPkg,
UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG);
mSettings.writeLPr();
}
} catch (PackageManagerException e) {
// Whoops! Something went wrong; try to roll back to the stub
Slog.w(TAG, "Failed to install compressed system package:"
+ pkgSetting.name, e);
// Remove the failed install
removeCodePathLI(codePath);
// Install the system package
try (PackageFreezer freezer =
freezePackage(deletedPkg.packageName, "setEnabledSetting")) {
synchronized (mPackages) {
// NOTE: The system package always needs to be enabled; even
// if it's for a compressed stub. If we don't, installing the
// system package fails during scan [scanning checks the disabled
// packages]. We will reverse this later, after we've "installed"
// the stub.
// This leaves us in a fragile state; the stub should never be
// enabled, so, cross your fingers and hope nothing goes wrong
// until we can disable the package later.
enableSystemPackageLPw(deletedPkg);
}
installPackageFromSystemLIF(new File(deletedPkg.codePath),
false /*isPrivileged*/, null /*allUserHandles*/,
null /*origUserHandles*/, null /*origPermissionsState*/,
true /*writeSettings*/);
} catch (PackageManagerException pme) {
Slog.w(TAG, "Failed to restore system package:"
+ deletedPkg.packageName, pme);
} finally {
synchronized (mPackages) {
mSettings.disableSystemPackageLPw(
deletedPkg.packageName, true /*replaced*/);
mSettings.writeLPr();
}
}
return;
}
clearAppDataLIF(newPkg, UserHandle.USER_ALL, FLAG_STORAGE_DE
| FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
clearAppProfilesLIF(newPkg, UserHandle.USER_ALL);
mDexManager.notifyPackageUpdated(newPkg.packageName,
newPkg.baseCodePath, newPkg.splitCodePaths);
}
}
if (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
|| newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
// Don't care about who enables an app.
callingPackage = null;
}
pkgSetting.setEnabled(newState, userId, callingPackage);
} else {
// We're dealing with a component level state change
// First, verify that this is a valid class name.
PackageParser.Package pkg = pkgSetting.pkg;
if (pkg == null || !pkg.hasComponentClassName(className)) {
if (pkg != null &&
pkg.applicationInfo.targetSdkVersion >=
Build.VERSION_CODES.JELLY_BEAN) {
throw new IllegalArgumentException("Component class " + className
+ " does not exist in " + packageName);
} else {
Slog.w(TAG, "Failed setComponentEnabledSetting: component class "
+ className + " does not exist in " + packageName);
}
}
switch (newState) {
case COMPONENT_ENABLED_STATE_ENABLED:
if (!pkgSetting.enableComponentLPw(className, userId)) {
return;
}
break;
case COMPONENT_ENABLED_STATE_DISABLED:
if (!pkgSetting.disableComponentLPw(className, userId)) {
return;
}
break;
case COMPONENT_ENABLED_STATE_DEFAULT:
if (!pkgSetting.restoreComponentLPw(className, userId)) {
return;
}
break;
default:
Slog.e(TAG, "Invalid new component state: " + newState);
return;
}
}
synchronized (mPackages) {
scheduleWritePackageRestrictionsLocked(userId);
updateSequenceNumberLP(pkgSetting, new int[] { userId });
final long callingId = Binder.clearCallingIdentity();