import android.graphics.Bitmap;
import android.hardware.display.DisplayManager;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
private static final String PACKAGE_SCHEME = "package";
private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
+ /**
+ * If VENDOR_OVERLAY_THEME_PROPERTY is set, search for runtime resource overlay APKs also in
+ * VENDOR_OVERLAY_DIR/<value of VENDOR_OVERLAY_THEME_PROPERTY> in addition to
+ * VENDOR_OVERLAY_DIR.
+ */
+ private static final String VENDOR_OVERLAY_THEME_PROPERTY = "ro.boot.vendor.overlay.theme";
private static int DEFAULT_EPHEMERAL_HASH_PREFIX_MASK = 0xFFFFF000;
private static int DEFAULT_EPHEMERAL_HASH_PREFIX_COUNT = 5;
final @NonNull String mServicesSystemSharedLibraryPackageName;
final @NonNull String mSharedSystemSharedLibraryPackageName;
+ final boolean mPermissionReviewRequired;
+
private final PackageUsage mPackageUsage = new PackageUsage();
private final CompilerStats mCompilerStats = new CompilerStats();
}
mContext = context;
+
+ mPermissionReviewRequired = context.getResources().getBoolean(
+ R.bool.config_permissionReviewRequired);
+
mFactoryTest = factoryTest;
mOnlyCore = onlyCore;
mMetrics = new DisplayMetrics();
}
}
- // Collect vendor overlay packages.
- // (Do this before scanning any apps.)
+ // Collect vendor overlay packages. (Do this before scanning any apps.)
// For security and version matching reason, only consider
- // overlay packages if they reside in VENDOR_OVERLAY_DIR.
- File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);
- scanDirTracedLI(vendorOverlayDir, mDefParseFlags
+ // overlay packages if they reside in the right directory.
+ String overlayThemeDir = SystemProperties.get(VENDOR_OVERLAY_THEME_PROPERTY);
+ if (!overlayThemeDir.isEmpty()) {
+ scanDirTracedLI(new File(VENDOR_OVERLAY_DIR, overlayThemeDir), mDefParseFlags
+ | PackageParser.PARSE_IS_SYSTEM
+ | PackageParser.PARSE_IS_SYSTEM_DIR
+ | PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
+ }
+ scanDirTracedLI(new File(VENDOR_OVERLAY_DIR), mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR
| PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
// their permissions as always granted runtime ones since we need
// to keep the review required permission flag per user while an
// install permission's state is shared across all users.
- if (Build.PERMISSIONS_REVIEW_REQUIRED
+ if ((mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED)
&& pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
&& bp.isRuntime()) {
return;
// their permissions as always granted runtime ones since we need
// to keep the review required permission flag per user while an
// install permission's state is shared across all users.
- if (Build.PERMISSIONS_REVIEW_REQUIRED
+ if ((mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED)
&& pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
&& bp.isRuntime()) {
return;
killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
}
+ /**
+ * We might auto-grant permissions if any permission of the group is already granted. Hence if
+ * the group of a granted permission changes we need to revoke it to avoid having permissions of
+ * the new group auto-granted.
+ *
+ * @param newPackage The new package that was installed
+ * @param oldPackage The old package that was updated
+ * @param allPackageNames All package names
+ */
+ private void revokeRuntimePermissionsIfGroupChanged(
+ PackageParser.Package newPackage,
+ PackageParser.Package oldPackage,
+ ArrayList<String> allPackageNames) {
+ final int numOldPackagePermissions = oldPackage.permissions.size();
+ final ArrayMap<String, String> oldPermissionNameToGroupName
+ = new ArrayMap<>(numOldPackagePermissions);
+
+ for (int i = 0; i < numOldPackagePermissions; i++) {
+ final PackageParser.Permission permission = oldPackage.permissions.get(i);
+
+ if (permission.group != null) {
+ oldPermissionNameToGroupName.put(permission.info.name,
+ permission.group.info.name);
+ }
+ }
+
+ final int numNewPackagePermissions = newPackage.permissions.size();
+ for (int newPermissionNum = 0; newPermissionNum < numNewPackagePermissions;
+ newPermissionNum++) {
+ final PackageParser.Permission newPermission =
+ newPackage.permissions.get(newPermissionNum);
+ final int newProtection = newPermission.info.protectionLevel;
+
+ if ((newProtection & PermissionInfo.PROTECTION_DANGEROUS) != 0) {
+ final String permissionName = newPermission.info.name;
+ final String newPermissionGroupName =
+ newPermission.group == null ? null : newPermission.group.info.name;
+ final String oldPermissionGroupName = oldPermissionNameToGroupName.get(
+ permissionName);
+
+ if (newPermissionGroupName != null
+ && !newPermissionGroupName.equals(oldPermissionGroupName)) {
+ final List<UserInfo> users = mContext.getSystemService(UserManager.class)
+ .getUsers();
+
+ final int numUsers = users.size();
+ for (int userNum = 0; userNum < numUsers; userNum++) {
+ final int userId = users.get(userNum).id;
+ final int numPackages = allPackageNames.size();
+ for (int packageNum = 0; packageNum < numPackages; packageNum++) {
+ final String packageName = allPackageNames.get(packageNum);
+
+ if (checkPermission(permissionName, packageName, userId)
+ == PackageManager.PERMISSION_GRANTED) {
+ EventLog.writeEvent(0x534e4554, "72710897",
+ newPackage.applicationInfo.uid,
+ "Revoking permission", permissionName, "from package",
+ packageName, "as the group changed from",
+ oldPermissionGroupName, "to", newPermissionGroupName);
+
+ try {
+ revokeRuntimePermission(packageName, permissionName, userId);
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Could not revoke " + permissionName + " from "
+ + packageName, e);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+
@Override
public void resetRuntimePermissions() {
mContext.enforceCallingOrSelfPermission(
result.remove(xpResolveInfo);
}
if (result.size() == 0 && !addEphemeral) {
+ // No result in current profile, but found candidate in parent user.
+ // And we are not going to add emphemeral app, so we can return the
+ // result straight away.
result.add(xpDomainInfo.resolveInfo);
return result;
}
- }
- if (result.size() > 1 || addEphemeral) {
- result = filterCandidatesWithDomainPreferredActivitiesLPr(
- intent, flags, result, xpDomainInfo, userId);
- sortResult = true;
- }
+ } else if (result.size() <= 1 && !addEphemeral) {
+ // No result in parent user and <= 1 result in current profile, and we
+ // are not going to add emphemeral app, so we can return the result without
+ // further processing.
+ return result;
+ }
+ // We have more than one candidate (combining results from current and parent
+ // profile), so we need filtering and sorting.
+ result = filterCandidatesWithDomainPreferredActivitiesLPr(
+ intent, flags, result, xpDomainInfo, userId);
+ sortResult = true;
}
} else {
final PackageParser.Package pkg = mPackages.get(pkgName);
Log.d(TAG, "Scanning package " + pkg.packageName);
}
+ final PackageParser.Package oldPkg;
+
synchronized (mPackages) {
if (mPackages.containsKey(pkg.packageName)
|| mSharedLibraries.containsKey(pkg.packageName)) {
+ " already installed. Skipping duplicate.");
}
+ final PackageSetting oldPkgSetting = mSettings.peekPackageLPr(pkg.packageName);
+ if (oldPkgSetting == null) {
+ oldPkg = null;
+ } else {
+ oldPkg = oldPkgSetting.pkg;
+ }
+
// If we're only installing presumed-existing packages, require that the
// scanned APK is both already known and at the path previously established
// for it. Previously unknown packages we pick up normally, but if we have an
// This is a regular package, with one or more known overlay packages.
createIdmapsForPackageLI(pkg);
}
+
+ if (oldPkg != null) {
+ // We need to call revokeRuntimePermissionsIfGroupChanged async as permission
+ // revokation from this method might need to kill apps which need the
+ // mPackages lock on a different thread. This would dead lock.
+ //
+ // Hence create a copy of all package names and pass it into
+ // revokeRuntimePermissionsIfGroupChanged. Only for those permissions might get
+ // revoked. If a new package is added before the async code runs the permission
+ // won't be granted yet, hence new packages are no problem.
+ final ArrayList<String> allPackageNames = new ArrayList<>(mPackages.keySet());
+
+ AsyncTask.execute(new Runnable() {
+ public void run() {
+ revokeRuntimePermissionsIfGroupChanged(pkg, oldPkg, allPackageNames);
+ }
+ });
+ }
}
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
// their permissions as always granted runtime ones since we need
// to keep the review required permission flag per user while an
// install permission's state is shared across all users.
- if (!appSupportsRuntimePermissions && !Build.PERMISSIONS_REVIEW_REQUIRED) {
+ if (!appSupportsRuntimePermissions && !mPermissionReviewRequired
+ && !Build.PERMISSIONS_REVIEW_REQUIRED) {
// For legacy apps dangerous permissions are install time ones.
grant = GRANT_INSTALL;
} else if (origPermissions.hasInstallPermission(bp.name)) {
int flags = permissionState != null
? permissionState.getFlags() : 0;
if (origPermissions.hasRuntimePermission(bp.name, userId)) {
- if (permissionsState.grantRuntimePermission(bp, userId) ==
- PermissionsState.PERMISSION_OPERATION_FAILURE) {
- // If we cannot put the permission as it was, we have to write.
+ // Don't propagate the permission in a permission review mode if
+ // the former was revoked, i.e. marked to not propagate on upgrade.
+ // Note that in a permission review mode install permissions are
+ // represented as constantly granted runtime ones since we need to
+ // keep a per user state associated with the permission. Also the
+ // revoke on upgrade flag is no longer applicable and is reset.
+ final boolean revokeOnUpgrade = (flags & PackageManager
+ .FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0;
+ if (revokeOnUpgrade) {
+ flags &= ~PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+ // Since we changed the flags, we have to write.
changedRuntimePermissionUserIds = ArrayUtils.appendInt(
changedRuntimePermissionUserIds, userId);
}
+ if (!mPermissionReviewRequired || !revokeOnUpgrade) {
+ if (permissionsState.grantRuntimePermission(bp, userId) ==
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ // If we cannot put the permission as it was,
+ // we have to write.
+ changedRuntimePermissionUserIds = ArrayUtils.appendInt(
+ changedRuntimePermissionUserIds, userId);
+ }
+ }
+
// If the app supports runtime permissions no need for a review.
- if (Build.PERMISSIONS_REVIEW_REQUIRED
+ if ((mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED)
&& appSupportsRuntimePermissions
&& (flags & PackageManager
.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
changedRuntimePermissionUserIds = ArrayUtils.appendInt(
changedRuntimePermissionUserIds, userId);
}
- } else if (Build.PERMISSIONS_REVIEW_REQUIRED
+ } else if ((mPermissionReviewRequired
+ || Build.PERMISSIONS_REVIEW_REQUIRED)
&& !appSupportsRuntimePermissions) {
// For legacy apps that need a permission review, every new
// runtime permission is granted but it is pending a review.
+ perm.info.name + "; ignoring new declaration");
pkg.permissions.remove(i);
}
+ } else if (!PLATFORM_PACKAGE_NAME.equals(pkg.packageName)) {
+ // Prevent apps to change protection level to dangerous from any other
+ // type as this would allow a privilege escalation where an app adds a
+ // normal/signature permission in other app's group and later redefines
+ // it as dangerous leading to the group auto-grant.
+ if ((perm.info.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
+ == PermissionInfo.PROTECTION_DANGEROUS) {
+ if (bp != null && !bp.isRuntime()) {
+ Slog.w(TAG, "Package " + pkg.packageName + " trying to change a "
+ + "non-runtime permission " + perm.info.name
+ + " to runtime; keeping old protection level");
+ perm.info.protectionLevel = bp.protectionLevel;
+ }
+ }
}
}
}
@Override
public boolean isPackageDeviceAdminOnAnyUser(String packageName) {
+ final int callingUid = Binder.getCallingUid();
+ if (checkUidPermission(android.Manifest.permission.MANAGE_USERS, callingUid)
+ != PERMISSION_GRANTED) {
+ EventLog.writeEvent(0x534e4554, "128599183", -1, "");
+ throw new SecurityException(android.Manifest.permission.MANAGE_USERS
+ + " permission is required to call this API");
+ }
return isPackageDeviceAdmin(packageName, UserHandle.USER_ALL);
}
// If permission review is enabled and this is a legacy app, mark the
// permission as requiring a review as this is the initial state.
int flags = 0;
- if (Build.PERMISSIONS_REVIEW_REQUIRED
+ if ((mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED)
&& ps.pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
}
// permissions to keep per user flag state whether review is needed.
// Hence, if a new user is added we have to propagate dangerous
// permission grants for these legacy apps.
- if (Build.PERMISSIONS_REVIEW_REQUIRED) {
+ if (mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) {
updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL
| UPDATE_PERMISSIONS_REPLACE_ALL);
}
public boolean isPermissionsReviewRequired(String packageName, int userId) {
synchronized (mPackages) {
// If we do not support permission review, done.
- if (!Build.PERMISSIONS_REVIEW_REQUIRED) {
+ if (!mPermissionReviewRequired && !Build.PERMISSIONS_REVIEW_REQUIRED) {
return false;
}
return mSettings.wasPackageEverLaunchedLPr(packageName, userId);
}
}
+
+ @Override
+ public String getNameForUid(int uid) {
+ return PackageManagerService.this.getNameForUid(uid);
+ }
}
@Override