From d44d46aa507de39b7160b5e1358db9faad3da7e5 Mon Sep 17 00:00:00 2001 From: Svetoslav Ganov Date: Thu, 18 Aug 2016 18:39:29 -0700 Subject: [PATCH] Add resource based mechanism to grant default permissions This CL adds a mechanism for an OEM to grant default permissions using the build system. This allows permissions for which they got default grant exceptions to be added without modifying the platform code. The format is a simple XML listing the packages and the permissions to be granted for a package. The XML file is placed in etc/default-permissions. bug:29546655 bug:30929033 Change-Id: Ib6d671dac9201812bd2c47010c2c4b1330ef6b6e --- .../pm/DefaultPermissionGrantPolicy.java | 212 ++++++++++++++++++ .../server/pm/PackageManagerService.java | 14 +- 2 files changed, 222 insertions(+), 4 deletions(-) diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java index 9c9e97e31f299..d1dbdd8b1945f 100644 --- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java @@ -17,6 +17,7 @@ package com.android.server.pm; import android.Manifest; +import android.annotation.NonNull; import android.app.DownloadManager; import android.app.admin.DevicePolicyManager; import android.content.Intent; @@ -30,6 +31,9 @@ import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Build; +import android.os.Environment; +import android.os.Handler; +import android.os.Message; import android.os.UserHandle; import android.os.storage.StorageManager; import android.print.PrintManager; @@ -39,12 +43,23 @@ import android.provider.MediaStore; import android.provider.Telephony.Sms.Intents; import android.telephony.TelephonyManager; import android.security.Credentials; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import android.util.Slog; +import android.util.Xml; +import com.android.internal.util.XmlUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import java.io.BufferedInputStream; import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Set; import static android.os.Process.FIRST_APPLICATION_UID; @@ -65,6 +80,13 @@ final class DefaultPermissionGrantPolicy { private static final String AUDIO_MIME_TYPE = "audio/mpeg"; + private static final String TAG_EXCEPTIONS = "exceptions"; + private static final String TAG_EXCEPTION = "exception"; + private static final String TAG_PERMISSION = "permission"; + private static final String ATTR_PACKAGE = "package"; + private static final String ATTR_NAME = "name"; + private static final String ATTR_FIXED = "fixed"; + private static final Set PHONE_PERMISSIONS = new ArraySet<>(); static { PHONE_PERMISSIONS.add(Manifest.permission.READ_PHONE_STATE); @@ -126,7 +148,10 @@ final class DefaultPermissionGrantPolicy { STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); } + private static final int MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS = 1; + private final PackageManagerService mService; + private final Handler mHandler; private PackagesProvider mLocationPackagesProvider; private PackagesProvider mVoiceInteractionPackagesProvider; @@ -135,8 +160,22 @@ final class DefaultPermissionGrantPolicy { private PackagesProvider mSimCallManagerPackagesProvider; private SyncAdapterPackagesProvider mSyncAdapterPackagesProvider; + private ArrayMap> mGrantExceptions; + public DefaultPermissionGrantPolicy(PackageManagerService service) { mService = service; + mHandler = new Handler(mService.mHandlerThread.getLooper()) { + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS) { + synchronized (mService.mPackages) { + if (mGrantExceptions == null) { + mGrantExceptions = readDefaultPermissionExceptionsLPw(); + } + } + } + } + }; } public void setLocationPackagesProviderLPw(PackagesProvider provider) { @@ -166,6 +205,11 @@ final class DefaultPermissionGrantPolicy { public void grantDefaultPermissions(int userId) { grantPermissionsToSysComponentsAndPrivApps(userId); grantDefaultSystemHandlerPermissions(userId); + grantDefaultPermissionExceptions(userId); + } + + public void scheduleReadDefaultPermissionExceptions() { + mHandler.sendEmptyMessage(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS); } private void grantPermissionsToSysComponentsAndPrivApps(int userId) { @@ -916,7 +960,175 @@ final class DefaultPermissionGrantPolicy { pkg.mSignatures) == PackageManager.SIGNATURE_MATCH; } + private void grantDefaultPermissionExceptions(int userId) { + synchronized (mService.mPackages) { + mHandler.removeMessages(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS); + + if (mGrantExceptions == null) { + mGrantExceptions = readDefaultPermissionExceptionsLPw(); + } + + // mGrantExceptions is null only before the first read and then + // it serves as a cache of the default grants that should be + // performed for every user. If there is an entry then the app + // is on the system image and supports runtime permissions. + Set permissions = null; + final int exceptionCount = mGrantExceptions.size(); + for (int i = 0; i < exceptionCount; i++) { + String packageName = mGrantExceptions.keyAt(i); + PackageParser.Package pkg = getSystemPackageLPr(packageName); + List permissionGrants = mGrantExceptions.valueAt(i); + final int permissionGrantCount = permissionGrants.size(); + for (int j = 0; j < permissionGrantCount; j++) { + DefaultPermissionGrant permissionGrant = permissionGrants.get(j); + if (permissions == null) { + permissions = new ArraySet<>(); + } else { + permissions.clear(); + } + permissions.add(permissionGrant.name); + grantRuntimePermissionsLPw(pkg, permissions, false, + permissionGrant.fixed, userId); + } + } + } + } + + private @NonNull ArrayMap> + readDefaultPermissionExceptionsLPw() { + File dir = new File(Environment.getRootDirectory(), "etc/default-permissions"); + if (!dir.exists() || !dir.isDirectory() || !dir.canRead()) { + return new ArrayMap<>(0); + } + + File[] files = dir.listFiles(); + if (files == null) { + return new ArrayMap<>(0); + } + + ArrayMap> grantExceptions = new ArrayMap<>(); + + // Iterate over the files in the directory and scan .xml files + for (File file : files) { + if (!file.getPath().endsWith(".xml")) { + Slog.i(TAG, "Non-xml file " + file + " in " + dir + " directory, ignoring"); + continue; + } + if (!file.canRead()) { + Slog.w(TAG, "Default permissions file " + file + " cannot be read"); + continue; + } + try ( + InputStream str = new BufferedInputStream(new FileInputStream(file)) + ) { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(str, null); + parse(parser, grantExceptions); + } catch (XmlPullParserException | IOException e) { + Slog.w(TAG, "Error reading default permissions file " + file, e); + } + } + + return grantExceptions; + } + + private void parse(XmlPullParser parser, Map> + outGrantExceptions) throws IOException, XmlPullParserException { + final int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + if (TAG_EXCEPTIONS.equals(parser.getName())) { + parseExceptions(parser, outGrantExceptions); + } else { + Log.e(TAG, "Unknown tag " + parser.getName()); + } + } + } + + private void parseExceptions(XmlPullParser parser, Map> + outGrantExceptions) throws IOException, XmlPullParserException { + final int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + if (TAG_EXCEPTION.equals(parser.getName())) { + String packageName = parser.getAttributeValue(null, ATTR_PACKAGE); + + List packageExceptions = + outGrantExceptions.get(packageName); + if (packageExceptions == null) { + // The package must be on the system image + PackageParser.Package pkg = getSystemPackageLPr(packageName); + if (pkg == null) { + Log.w(TAG, "Unknown package:" + packageName); + XmlUtils.skipCurrentTag(parser); + return; + } + + // The package must support runtime permissions + if (!doesPackageSupportRuntimePermissions(pkg)) { + Log.w(TAG, "Skipping non supporting runtime permissions package:" + + packageName); + XmlUtils.skipCurrentTag(parser); + return; + } + packageExceptions = new ArrayList<>(); + outGrantExceptions.put(packageName, packageExceptions); + } + + parsePermission(parser, packageExceptions); + } else { + Log.e(TAG, "Unknown tag " + parser.getName() + "under "); + } + } + } + + private void parsePermission(XmlPullParser parser, List + outPackageExceptions) throws IOException, XmlPullParserException { + final int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + if (TAG_PERMISSION.contains(parser.getName())) { + String name = parser.getAttributeValue(null, ATTR_NAME); + if (name == null) { + Log.w(TAG, "Mandatory name attribute missing for permission tag"); + XmlUtils.skipCurrentTag(parser); + continue; + } + + final boolean fixed = XmlUtils.readBooleanAttribute(parser, ATTR_FIXED); + + DefaultPermissionGrant exception = new DefaultPermissionGrant(name, fixed); + outPackageExceptions.add(exception); + } else { + Log.e(TAG, "Unknown tag " + parser.getName() + "under "); + } + } + } + private static boolean doesPackageSupportRuntimePermissions(PackageParser.Package pkg) { return pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1; } + + private static final class DefaultPermissionGrant { + final String name; + final boolean fixed; + + public DefaultPermissionGrant(String name, boolean fixed) { + this.name = name; + this.fixed = fixed; + } + } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 2ed64f18349c0..f326555ee00ab 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -35,7 +35,6 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET; import static android.content.pm.PackageManager.INSTALL_EXTERNAL; import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS; import static android.content.pm.PackageManager.INSTALL_FAILED_CONFLICTING_PROVIDER; -import static android.content.pm.PackageManager.INSTALL_FAILED_DEXOPT; import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE; import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION; import static android.content.pm.PackageManager.INSTALL_FAILED_EPHEMERAL_INVALID; @@ -101,7 +100,6 @@ import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCES import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.IActivityManager; @@ -737,8 +735,7 @@ public class PackageManagerService extends IPackageManager.Stub { final SparseArray mIntentFilterVerificationStates = new SparseArray(); - final DefaultPermissionGrantPolicy mDefaultPermissionPolicy = - new DefaultPermissionGrantPolicy(this); + final DefaultPermissionGrantPolicy mDefaultPermissionPolicy; // List of packages names to keep cached, even if they are uninstalled for all users private List mKeepUninstalledPackages; @@ -2115,6 +2112,8 @@ public class PackageManagerService extends IPackageManager.Stub { mProcessLoggingHandler = new ProcessLoggingHandler(); Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT); + mDefaultPermissionPolicy = new DefaultPermissionGrantPolicy(this); + File dataDir = Environment.getDataDirectory(); mAppInstallDir = new File(dataDir, "app"); mAppLib32InstallDir = new File(dataDir, "app-lib"); @@ -17937,6 +17936,13 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); mDefaultPermissionPolicy.grantDefaultPermissions(userId); } + // If we did not grant default permissions, we preload from this the + // default permission exceptions lazily to ensure we don't hit the + // disk on a new user creation. + if (grantPermissionsUserIds == EMPTY_INT_ARRAY) { + mDefaultPermissionPolicy.scheduleReadDefaultPermissionExceptions(); + } + // Kick off any messages waiting for system ready if (mPostSystemReadyMessages != null) { for (Message msg : mPostSystemReadyMessages) {