From c2841ec7feb095cc36d2540844228ee72da3dfca Mon Sep 17 00:00:00 2001 From: Svet Ganov Date: Mon, 25 Jun 2018 16:39:23 -0700 Subject: [PATCH] Allow UiAutomation to adopt the shell permission indentity For testing we often need to run shell commands. This can be done today via running a shell command from an instrumentation test started from the shell. However, this requires adding shell commands which are not in the API contract, involve boilerplate code, require string parsing, etc. This change allows an instrumentation started from the shell to adopt the shell UID permission state. As a result one can call APIs protected by permissions normal apps cannot get by are granted to the shell. This enables adding dedicated test APIs protected by signatures permissions granted to the shell. Test: cts-tradefed run cts-dev -m CtsUiAutomationTestCases -t android.app.uiautomation.cts.UiAutomationTest#testAdoptShellPermissions bug:80415658 Merged-In: I4bfd4b475225125512abf80ea98cd8fcacb6a1be Change-Id: I4bfd4b475225125512abf80ea98cd8fcacb6a1be --- api/current.txt | 2 + api/test-current.txt | 2 + core/java/android/app/AppOpsManager.java | 2 +- .../android/app/AppOpsManagerInternal.java | 43 ++++++ core/java/android/app/IActivityManager.aidl | 15 ++ .../android/app/IUiAutomationConnection.aidl | 3 +- core/java/android/app/UiAutomation.java | 60 +++++-- .../android/app/UiAutomationConnection.java | 35 ++++- .../android/content/pm/PackageManager.java | 2 + .../content/pm/PackageManagerInternal.java | 43 ++++++ .../android/internal/app/IAppOpsService.aidl | 2 +- .../com/android/server/AppOpsService.java | 96 +++++++++--- .../server/am/ActivityManagerService.java | 146 ++++++++++++++++++ .../server/pm/PackageManagerService.java | 55 +++++++ .../permission/PermissionManagerInternal.java | 8 - test-mock/api/test-current.txt | 2 + 16 files changed, 466 insertions(+), 50 deletions(-) diff --git a/api/current.txt b/api/current.txt index 172140fd653fe..3ab18c76d59a7 100755 --- a/api/current.txt +++ b/api/current.txt @@ -6117,8 +6117,10 @@ package android.app { } public final class UiAutomation { + method public void adoptShellPermissionIdentity(); method public void clearWindowAnimationFrameStats(); method public boolean clearWindowContentFrameStats(int); + method public void dropShellPermissionIdentity(); method public android.view.accessibility.AccessibilityEvent executeAndWaitForEvent(Runnable, android.app.UiAutomation.AccessibilityEventFilter, long) throws java.util.concurrent.TimeoutException; method public android.os.ParcelFileDescriptor executeShellCommand(String); method public android.view.accessibility.AccessibilityNodeInfo findFocus(int); diff --git a/api/test-current.txt b/api/test-current.txt index dc97dd061578c..400046bad74ae 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -263,7 +263,9 @@ package android.content.pm { method public abstract String getPermissionControllerPackageName(); method @NonNull public abstract String getServicesSystemSharedLibraryPackageName(); method @NonNull public abstract String getSharedSystemSharedLibraryPackageName(); + method @RequiresPermission("android.permission.GRANT_RUNTIME_PERMISSIONS") public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); method public abstract boolean isPermissionReviewModeEnabled(); + method @RequiresPermission("android.permission.REVOKE_RUNTIME_PERMISSIONS") public abstract void revokeRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage"; field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption"; field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000 diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index de84281328b2e..398b4064d15a9 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -2513,7 +2513,7 @@ public class AppOpsManager { */ public int noteProxyOpNoThrow(int op, String proxiedPackageName) { try { - return mService.noteProxyOperation(op, mContext.getOpPackageName(), + return mService.noteProxyOperation(op, Process.myUid(), mContext.getOpPackageName(), Binder.getCallingUid(), proxiedPackageName); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java index 24c5d234c1209..f5d5e6e9a9505 100644 --- a/core/java/android/app/AppOpsManagerInternal.java +++ b/core/java/android/app/AppOpsManagerInternal.java @@ -18,12 +18,55 @@ package android.app; import android.util.SparseIntArray; +import com.android.internal.util.function.QuadFunction; +import com.android.internal.util.function.TriFunction; + /** * App ops service local interface. * * @hide Only for use within the system server. */ public abstract class AppOpsManagerInternal { + /** Interface to override app ops checks via composition */ + public interface CheckOpsDelegate { + /** + * Allows overriding check operation behavior. + * + * @param code The op code to check. + * @param uid The UID for which to check. + * @param packageName The package for which to check. + * @param superImpl The super implementation. + * @return The app op check result. + */ + int checkOperation(int code, int uid, String packageName, + TriFunction superImpl); + + /** + * Allows overriding check audio operation behavior. + * + * @param code The op code to check. + * @param usage The audio op usage. + * @param uid The UID for which to check. + * @param packageName The package for which to check. + * @param superImpl The super implementation. + * @return The app op check result. + */ + int checkAudioOperation(int code, int usage, int uid, String packageName, + QuadFunction superImpl); + + /** + * Allows overriding note operation behavior. + * + * @param code The op code to note. + * @param uid The UID for which to note. + * @param packageName The package for which to note. + * @param superImpl The super implementation. + * @return The app op note result. + */ + int noteOperation(int code, int uid, String packageName, + TriFunction superImpl); + } + /** * Set the currently configured device and profile owners. Specifies the package uid (value) * that has been configured for each user (key) that has one. These will be allowed privileged diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index b192021f821b9..52fd84113fdfa 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -714,4 +714,19 @@ interface IActivityManager { /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */ void alwaysShowUnsupportedCompileSdkWarning(in ComponentName activity); + + /** + * Method for the shell UID to start deletating its permission identity to an + * active instrumenation. The shell can delegate permissions only to one active + * instrumentation at a time. An active instrumentation is one running and + * started from the shell. + */ + void startDelegateShellPermissionIdentity(int uid); + + /** + * Method for the shell UID to stop deletating its permission identity to an + * active instrumenation. An active instrumentation is one running and + * started from the shell. + */ + void stopDelegateShellPermissionIdentity(); } diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl index d01938b123b17..ac4bf7d9c2c51 100644 --- a/core/java/android/app/IUiAutomationConnection.aidl +++ b/core/java/android/app/IUiAutomationConnection.aidl @@ -47,7 +47,8 @@ interface IUiAutomationConnection { in ParcelFileDescriptor source); void grantRuntimePermission(String packageName, String permission, int userId); void revokeRuntimePermission(String packageName, String permission, int userId); - + void adoptShellPermissionIdentity(int uid); + void dropShellPermissionIdentity(); // Called from the system process. oneway void shutdown(); } diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index c850c85b6e5b5..c0903b65737cf 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -35,6 +35,7 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.ParcelFileDescriptor; +import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; @@ -52,6 +53,7 @@ import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; import com.android.internal.util.function.pooled.PooledLambda; + import libcore.io.IoUtils; import java.io.IOException; @@ -351,6 +353,46 @@ public final class UiAutomation { mIsDestroyed = true; } + /** + * Adopt the permission identity of the shell UID. This allows you to call APIs protected + * permissions which normal apps cannot hold but are granted to the shell UID. If you + * already adopted the shell permission identity this method would be a no-op. + * Note that your permission state becomes that of the shell UID and it is not a + * combination of your and the shell UID permissions. + * + * @see #dropShellPermissionIdentity() + */ + public void adoptShellPermissionIdentity() { + synchronized (mLock) { + throwIfNotConnectedLocked(); + } + try { + // Calling out without a lock held. + mUiAutomationConnection.adoptShellPermissionIdentity(Process.myUid()); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error executing adopting shell permission identity!", re); + } + } + + /** + * Drop the shell permission identity adopted by a previous call to + * {@link #adoptShellPermissionIdentity()}. If you did not adopt the shell permission + * identity this method would be a no-op. + * + * @see #adoptShellPermissionIdentity() + */ + public void dropShellPermissionIdentity() { + synchronized (mLock) { + throwIfNotConnectedLocked(); + } + try { + // Calling out without a lock held. + mUiAutomationConnection.dropShellPermissionIdentity(); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error executing dropping shell permission identity!", re); + } + } + /** * Performs a global action. Such an action can be performed at any moment * regardless of the current application or user location in that application. @@ -1004,6 +1046,8 @@ public final class UiAutomation { * * @param command The command to execute. * @return A file descriptor to the standard output stream. + * + * @see #adoptShellPermissionIdentity() */ public ParcelFileDescriptor executeShellCommand(String command) { synchronized (mLock) { @@ -1086,22 +1130,6 @@ public final class UiAutomation { return result; } - private static float getDegreesForRotation(int value) { - switch (value) { - case Surface.ROTATION_90: { - return 360f - 90f; - } - case Surface.ROTATION_180: { - return 360f - 180f; - } - case Surface.ROTATION_270: { - return 360f - 270f; - } default: { - return 0; - } - } - } - private boolean isConnectedLocked() { return mConnectionId != CONNECTION_ID_UNDEFINED; } diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index e6347354b723e..b406d9e30a539 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -31,6 +31,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.util.Log; import android.view.IWindowManager; import android.view.InputEvent; import android.view.SurfaceControl; @@ -38,7 +39,6 @@ import android.view.WindowAnimationFrameStats; import android.view.WindowContentFrameStats; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.IAccessibilityManager; -import android.util.Log; import libcore.io.IoUtils; @@ -72,6 +72,9 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { private final IPackageManager mPackageManager = IPackageManager.Stub .asInterface(ServiceManager.getService("package")); + private final IActivityManager mActivityManager = IActivityManager.Stub + .asInterface(ServiceManager.getService("activity")); + private final Object mLock = new Object(); private final Binder mToken = new Binder(); @@ -275,6 +278,36 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { } } + @Override + public void adoptShellPermissionIdentity(int uid) throws RemoteException { + synchronized (mLock) { + throwIfCalledByNotTrustedUidLocked(); + throwIfShutdownLocked(); + throwIfNotConnectedLocked(); + } + final long identity = Binder.clearCallingIdentity(); + try { + mActivityManager.startDelegateShellPermissionIdentity(uid); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void dropShellPermissionIdentity() throws RemoteException { + synchronized (mLock) { + throwIfCalledByNotTrustedUidLocked(); + throwIfShutdownLocked(); + throwIfNotConnectedLocked(); + } + final long identity = Binder.clearCallingIdentity(); + try { + mActivityManager.stopDelegateShellPermissionIdentity(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + public class Repeater implements Runnable { // Continuously read readFrom and write back to writeTo until EOF is encountered private final InputStream readFrom; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 92c757ceaa29d..72981a7732773 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3623,6 +3623,7 @@ public abstract class PackageManager { * * @hide */ + @TestApi @SystemApi @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public abstract void grantRuntimePermission(@NonNull String packageName, @@ -3649,6 +3650,7 @@ public abstract class PackageManager { * * @hide */ + @TestApi @SystemApi @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public abstract void revokeRuntimePermission(@NonNull String packageName, diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index 755232c6a6b60..7c9943b61ea4c 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -29,9 +29,12 @@ import android.os.Bundle; import android.os.PersistableBundle; import android.util.SparseArray; +import com.android.internal.util.function.TriFunction; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.function.BiFunction; /** * Package manager local system service interface. @@ -64,6 +67,32 @@ public abstract class PackageManagerInternal { void onPackageRemoved(@NonNull String packageName); } + /** Interface to override permission checks via composition */ + public interface CheckPermissionDelegate { + /** + * Allows overriding check permission behavior. + * + * @param permName The permission to check. + * @param pkgName The package for which to check. + * @param userId The user for which to check. + * @param superImpl The super implementation. + * @return The check permission result. + */ + int checkPermission(String permName, String pkgName, int userId, + TriFunction superImpl); + + /** + * Allows overriding check UID permission behavior. + * + * @param permName The permission to check. + * @param uid The UID for which to check. + * @param superImpl The super implementation. + * @return The check permission result. + */ + int checkUidPermission(String permName, int uid, + BiFunction superImpl); + } + /** * Provider for package names. */ @@ -633,4 +662,18 @@ public abstract class PackageManagerInternal { * Ask the package manager to compile layouts in the given package. */ public abstract boolean compileLayouts(String packageName); + + /** + * Get the delegate to influence permission checking. + * + * @return The delegate instance or null to clear. + */ + public abstract @Nullable CheckPermissionDelegate getCheckPermissionDelegate(); + + /** + * Set a delegate to influence permission checking. + * + * @param delegate A delegate instance or null to clear. + */ + public abstract void setCheckPermissionDelegate(@Nullable CheckPermissionDelegate delegate); } diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl index 0ed972477123a..768dddd35a6de 100644 --- a/core/java/com/android/internal/app/IAppOpsService.aidl +++ b/core/java/com/android/internal/app/IAppOpsService.aidl @@ -33,7 +33,7 @@ interface IAppOpsService { void stopWatchingMode(IAppOpsCallback callback); IBinder getToken(IBinder clientToken); int permissionToOpCode(String permission); - int noteProxyOperation(int code, String proxyPackageName, + int noteProxyOperation(int code, int proxyUid, String proxyPackageName, int callingUid, String callingPackageName); // Remaining methods are only used in Java. diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java index 786d757557d16..5c273de237618 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -22,6 +22,7 @@ import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.AppOpsManagerInternal; +import android.app.AppOpsManagerInternal.CheckOpsDelegate; import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -207,6 +208,8 @@ public class AppOpsService extends IAppOpsService.Stub { SparseIntArray mProfileOwners; + private CheckOpsDelegate mCheckOpsDelegate; + /** * All times are in milliseconds. These constants are kept synchronized with the system * global Settings. Any access to this class or its fields should be done while @@ -1411,15 +1414,39 @@ public class AppOpsService extends IAppOpsService.Stub { } } + public CheckOpsDelegate getAppOpsServiceDelegate() { + synchronized (this) { + return mCheckOpsDelegate; + } + } + + public void setAppOpsServiceDelegate(CheckOpsDelegate delegate) { + synchronized (this) { + mCheckOpsDelegate = delegate; + } + } + @Override public int checkOperation(int code, int uid, String packageName) { - verifyIncomingUid(uid); - verifyIncomingOp(code); - String resolvedPackageName = resolvePackageName(uid, packageName); - if (resolvedPackageName == null) { - return AppOpsManager.MODE_IGNORED; - } + final CheckOpsDelegate delegate; synchronized (this) { + if (mCheckOpsDelegate == null) { + return checkOperationImpl(code, uid, packageName); + } + delegate = mCheckOpsDelegate; + } + return delegate.checkOperation(code, uid, packageName, + AppOpsService.this::checkOperationImpl); + } + + private int checkOperationImpl(int code, int uid, String packageName) { + synchronized (this) { + verifyIncomingUid(uid); + verifyIncomingOp(code); + String resolvedPackageName = resolvePackageName(uid, packageName); + if (resolvedPackageName == null) { + return AppOpsManager.MODE_IGNORED; + } if (isOpRestrictedLocked(uid, code, resolvedPackageName)) { return AppOpsManager.MODE_IGNORED; } @@ -1439,20 +1466,33 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public int checkAudioOperation(int code, int usage, int uid, String packageName) { - boolean suspended; - try { - suspended = isPackageSuspendedForUser(packageName, uid); - } catch (IllegalArgumentException ex) { - // Package not found. - suspended = false; - } - - if (suspended) { - Slog.i(TAG, "Audio disabled for suspended package=" + packageName + " for uid=" + uid); - return AppOpsManager.MODE_IGNORED; - } - + final CheckOpsDelegate delegate; synchronized (this) { + if (mCheckOpsDelegate == null) { + return checkAudioOperationImpl(code, usage, uid, packageName); + } + delegate = mCheckOpsDelegate; + } + return delegate.checkAudioOperation(code, usage, uid, packageName, + AppOpsService.this::checkAudioOperationImpl); + } + + private int checkAudioOperationImpl(int code, int usage, int uid, String packageName) { + synchronized (this) { + boolean suspended; + try { + suspended = isPackageSuspendedForUser(packageName, uid); + } catch (IllegalArgumentException ex) { + // Package not found. + suspended = false; + } + + if (suspended) { + Slog.i(TAG, "Audio disabled for suspended package=" + packageName + + " for uid=" + uid); + return AppOpsManager.MODE_IGNORED; + } + final int mode = checkRestrictionLocked(code, usage, uid, packageName); if (mode != AppOpsManager.MODE_ALLOWED) { return mode; @@ -1530,10 +1570,10 @@ public class AppOpsService extends IAppOpsService.Stub { } @Override - public int noteProxyOperation(int code, String proxyPackageName, - int proxiedUid, String proxiedPackageName) { + public int noteProxyOperation(int code, int proxyUid, + String proxyPackageName, int proxiedUid, String proxiedPackageName) { + verifyIncomingUid(proxyUid); verifyIncomingOp(code); - final int proxyUid = Binder.getCallingUid(); String resolveProxyPackageName = resolvePackageName(proxyUid, proxyPackageName); if (resolveProxyPackageName == null) { return AppOpsManager.MODE_IGNORED; @@ -1553,6 +1593,18 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public int noteOperation(int code, int uid, String packageName) { + final CheckOpsDelegate delegate; + synchronized (this) { + if (mCheckOpsDelegate == null) { + return noteOperationImpl(code, uid, packageName); + } + delegate = mCheckOpsDelegate; + } + return delegate.noteOperation(code, uid, packageName, + AppOpsService.this::noteOperationImpl); + } + + private int noteOperationImpl(int code, int uid, String packageName) { verifyIncomingUid(uid); verifyIncomingOp(code); String resolvedPackageName = resolvePackageName(uid, packageName); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 52a3536e63d45..0eec8e9cb7179 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -233,6 +233,7 @@ import android.app.ActivityThread; import android.app.AlertDialog; import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.AppOpsManagerInternal.CheckOpsDelegate; import android.app.ApplicationErrorReport; import android.app.ApplicationThreadConstants; import android.app.BroadcastOptions; @@ -296,6 +297,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; +import android.content.pm.PackageManagerInternal.CheckPermissionDelegate; import android.content.pm.ParceledListSlice; import android.content.pm.PathPermission; import android.content.pm.PermissionInfo; @@ -432,6 +434,8 @@ import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.MemInfoReader; import com.android.internal.util.Preconditions; +import com.android.internal.util.function.QuadFunction; +import com.android.internal.util.function.TriFunction; import com.android.server.AlarmManagerInternal; import com.android.server.AppOpsService; import com.android.server.AttributeCache; @@ -516,6 +520,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiFunction; public class ActivityManagerService extends IActivityManager.Stub implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback { @@ -22177,6 +22182,8 @@ public class ActivityManagerService extends IActivityManager.Stub // Can't call out of the system process with a lock held, so post a message. if (app.instr.mUiAutomationConnection != null) { + mAppOpsService.setAppOpsServiceDelegate(null); + getPackageManagerInternalLocked().setCheckPermissionDelegate(null); mHandler.obtainMessage(SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG, app.instr.mUiAutomationConnection).sendToTarget(); } @@ -27189,4 +27196,143 @@ public class ActivityManagerService extends IActivityManager.Stub } } } + + @Override + public void startDelegateShellPermissionIdentity(int delegateUid) { + if (UserHandle.getCallingAppId() != Process.SHELL_UID + && UserHandle.getCallingAppId() != Process.ROOT_UID) { + throw new SecurityException("Only the shell can delegate its permissions"); + } + + // We allow delegation only to one instrumentation started from the shell + synchronized (ActivityManagerService.this) { + // If there is a delegate it should be the same instance for app ops and permissions. + if (mAppOpsService.getAppOpsServiceDelegate() + != getPackageManagerInternalLocked().getCheckPermissionDelegate()) { + throw new IllegalStateException("Bad shell delegate state"); + } + + // If the delegate is already set up for the target UID, nothing to do. + if (mAppOpsService.getAppOpsServiceDelegate() != null) { + if (!(mAppOpsService.getAppOpsServiceDelegate() instanceof ShellDelegate)) { + throw new IllegalStateException("Bad shell delegate state"); + } + if (((ShellDelegate) mAppOpsService.getAppOpsServiceDelegate()) + .getDelegateUid() != delegateUid) { + throw new SecurityException("Shell can delegate permissions only " + + "to one instrumentation at a time"); + } + return; + } + + final int instrCount = mActiveInstrumentation.size(); + for (int i = 0; i < instrCount; i++) { + final ActiveInstrumentation instr = mActiveInstrumentation.get(i); + if (instr.mTargetInfo.uid != delegateUid) { + continue; + } + // If instrumentation started from the shell the connection is not null + if (instr.mUiAutomationConnection == null) { + throw new SecurityException("Shell can delegate its permissions" + + " only to an instrumentation started from the shell"); + } + + // Hook them up... + final ShellDelegate shellDelegate = new ShellDelegate( + instr.mTargetInfo.packageName, delegateUid); + mAppOpsService.setAppOpsServiceDelegate(shellDelegate); + getPackageManagerInternalLocked().setCheckPermissionDelegate(shellDelegate); + return; + } + } + } + + @Override + public void stopDelegateShellPermissionIdentity() { + if (UserHandle.getCallingAppId() != Process.SHELL_UID + && UserHandle.getCallingAppId() != Process.ROOT_UID) { + throw new SecurityException("Only the shell can delegate its permissions"); + } + synchronized (ActivityManagerService.this) { + mAppOpsService.setAppOpsServiceDelegate(null); + getPackageManagerInternalLocked().setCheckPermissionDelegate(null); + } + } + + private class ShellDelegate implements CheckOpsDelegate, CheckPermissionDelegate { + private final String mTargetPackageName; + private final int mTargetUid; + + ShellDelegate(String targetPacakgeName, int targetUid) { + mTargetPackageName = targetPacakgeName; + mTargetUid = targetUid; + } + + int getDelegateUid() { + return mTargetUid; + } + + @Override + public int checkOperation(int code, int uid, String packageName, + TriFunction superImpl) { + if (uid == mTargetUid) { + final long identity = Binder.clearCallingIdentity(); + try { + return superImpl.apply(code, Process.SHELL_UID, + "com.android.shell"); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + return superImpl.apply(code, uid, packageName); + } + + @Override + public int checkAudioOperation(int code, int usage, int uid, String packageName, + QuadFunction superImpl) { + if (uid == mTargetUid) { + final long identity = Binder.clearCallingIdentity(); + try { + return superImpl.apply(code, usage, Process.SHELL_UID, + "com.android.shell"); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + return superImpl.apply(code, usage, uid, packageName); + } + + @Override + public int noteOperation(int code, int uid, String packageName, + TriFunction superImpl) { + if (uid == mTargetUid) { + final long identity = Binder.clearCallingIdentity(); + try { + return mAppOpsService.noteProxyOperation(code, Process.SHELL_UID, + "com.android.shell", uid, packageName); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + return superImpl.apply(code, uid, packageName); + } + + @Override + public int checkPermission(String permName, String pkgName, int userId, + TriFunction superImpl) { + if (mTargetPackageName.equals(pkgName)) { + return superImpl.apply(permName, "com.android.shell", userId); + } + return superImpl.apply(permName, pkgName, userId); + } + + @Override + public int checkUidPermission(String permName, int uid, + BiFunction superImpl) { + if (uid == mTargetUid) { + return superImpl.apply(permName, Process.SHELL_UID); + } + return superImpl.apply(permName, uid); + } + } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 894897705d44f..3e36dae528e13 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -177,6 +177,7 @@ import android.content.pm.PackageList; import android.content.pm.PackageManager; import android.content.pm.PackageManager.LegacyPackageDeleteObserver; import android.content.pm.PackageManagerInternal; +import android.content.pm.PackageManagerInternal.CheckPermissionDelegate; import android.content.pm.PackageManagerInternal.PackageListObserver; import android.content.pm.PackageParser; import android.content.pm.PackageParser.ActivityIntentInfo; @@ -297,6 +298,8 @@ import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; +import com.android.internal.util.function.QuadFunction; +import com.android.internal.util.function.TriFunction; import com.android.server.AttributeCache; import com.android.server.DeviceIdleController; import com.android.server.EventLogTags; @@ -376,6 +379,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; +import java.util.function.BiFunction; import java.util.function.Predicate; /** @@ -1043,6 +1047,9 @@ public class PackageManagerService extends IPackageManager.Stub void receiveVerificationResponse(int verificationId); } + @GuardedBy("mPackages") + private CheckPermissionDelegate mCheckPermissionDelegate; + private class IntentVerifierProxy implements IntentFilterVerifier { private Context mContext; private ComponentName mIntentFilterVerifierComponent; @@ -5352,11 +5359,35 @@ public class PackageManagerService extends IPackageManager.Stub @Override public int checkPermission(String permName, String pkgName, int userId) { + final CheckPermissionDelegate checkPermissionDelegate; + synchronized (mPackages) { + if (mCheckPermissionDelegate == null) { + return checkPermissionImpl(permName, pkgName, userId); + } + checkPermissionDelegate = mCheckPermissionDelegate; + } + return checkPermissionDelegate.checkPermission(permName, pkgName, userId, + PackageManagerService.this::checkPermissionImpl); + } + + private int checkPermissionImpl(String permName, String pkgName, int userId) { return mPermissionManager.checkPermission(permName, pkgName, getCallingUid(), userId); } @Override public int checkUidPermission(String permName, int uid) { + final CheckPermissionDelegate checkPermissionDelegate; + synchronized (mPackages) { + if (mCheckPermissionDelegate == null) { + return checkUidPermissionImpl(permName, uid); + } + checkPermissionDelegate = mCheckPermissionDelegate; + } + return checkPermissionDelegate.checkUidPermission(permName, uid, + PackageManagerService.this::checkUidPermissionImpl); + } + + private int checkUidPermissionImpl(String permName, int uid) { synchronized (mPackages) { final String[] packageNames = getPackagesForUid(uid); final PackageParser.Package pkg = (packageNames != null && packageNames.length > 0) @@ -9236,6 +9267,16 @@ public class PackageManagerService extends IPackageManager.Stub } } + @GuardedBy("mPackages") + public CheckPermissionDelegate getCheckPermissionDelegateLocked() { + return mCheckPermissionDelegate; + } + + @GuardedBy("mPackages") + public void setCheckPermissionDelegateLocked(CheckPermissionDelegate delegate) { + mCheckPermissionDelegate = delegate; + } + @GuardedBy("mPackages") private void notifyPackageUseLocked(String packageName, int reason) { final PackageParser.Package p = mPackages.get(packageName); @@ -24511,6 +24552,20 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } return mArtManagerService.compileLayouts(pkg); } + + @Override + public CheckPermissionDelegate getCheckPermissionDelegate() { + synchronized (mPackages) { + return PackageManagerService.this.getCheckPermissionDelegateLocked(); + } + } + + @Override + public void setCheckPermissionDelegate(CheckPermissionDelegate delegate) { + synchronized (mPackages) { + PackageManagerService.this.setCheckPermissionDelegateLocked(delegate); + } + } } @Override diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java index a042fedf8b470..ec15c16981a8b 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java @@ -21,19 +21,11 @@ import android.annotation.Nullable; import android.content.pm.PackageParser; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.PermissionInfoFlags; -import android.content.pm.PackageParser.Permission; - -import com.android.server.pm.SharedUserSetting; -import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback; import java.util.ArrayList; import java.util.Collection; -import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.Set; /** * Internal interfaces to be used by other components within the system server. diff --git a/test-mock/api/test-current.txt b/test-mock/api/test-current.txt index 14cb9e3a8f718..9768c622f19dc 100644 --- a/test-mock/api/test-current.txt +++ b/test-mock/api/test-current.txt @@ -10,7 +10,9 @@ package android.test.mock { method public String getPermissionControllerPackageName(); method @NonNull public String getServicesSystemSharedLibraryPackageName(); method @NonNull public String getSharedSystemSharedLibraryPackageName(); + method public void grantRuntimePermission(String, String, android.os.UserHandle); method public boolean isPermissionReviewModeEnabled(); + method public void revokeRuntimePermission(String, String, android.os.UserHandle); } }