diff options
author | Svet Ganov <svetoslavganov@google.com> | 2018-06-25 16:39:23 -0700 |
---|---|---|
committer | Sooraj Sasindran <sasindran@google.com> | 2019-02-21 19:59:14 +0000 |
commit | c2841ec7feb095cc36d2540844228ee72da3dfca (patch) | |
tree | a75b7383d0285ee73db59fbc8f650221ef2b3087 | |
parent | a6dda25ac90b7ce6a9afa7da6cc86fade59f9ebf (diff) | |
download | frameworks_base-c2841ec7feb095cc36d2540844228ee72da3dfca.tar.gz frameworks_base-c2841ec7feb095cc36d2540844228ee72da3dfca.tar.bz2 frameworks_base-c2841ec7feb095cc36d2540844228ee72da3dfca.zip |
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
-rwxr-xr-x | api/current.txt | 2 | ||||
-rw-r--r-- | api/test-current.txt | 2 | ||||
-rw-r--r-- | core/java/android/app/AppOpsManager.java | 2 | ||||
-rw-r--r-- | core/java/android/app/AppOpsManagerInternal.java | 43 | ||||
-rw-r--r-- | core/java/android/app/IActivityManager.aidl | 15 | ||||
-rw-r--r-- | core/java/android/app/IUiAutomationConnection.aidl | 3 | ||||
-rw-r--r-- | core/java/android/app/UiAutomation.java | 60 | ||||
-rw-r--r-- | core/java/android/app/UiAutomationConnection.java | 35 | ||||
-rw-r--r-- | core/java/android/content/pm/PackageManager.java | 2 | ||||
-rw-r--r-- | core/java/android/content/pm/PackageManagerInternal.java | 43 | ||||
-rw-r--r-- | core/java/com/android/internal/app/IAppOpsService.aidl | 2 | ||||
-rw-r--r-- | services/core/java/com/android/server/AppOpsService.java | 90 | ||||
-rw-r--r-- | services/core/java/com/android/server/am/ActivityManagerService.java | 146 | ||||
-rw-r--r-- | services/core/java/com/android/server/pm/PackageManagerService.java | 55 | ||||
-rw-r--r-- | services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java | 8 | ||||
-rw-r--r-- | test-mock/api/test-current.txt | 2 |
16 files changed, 463 insertions, 47 deletions
diff --git a/api/current.txt b/api/current.txt index 172140fd653..3ab18c76d59 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 dc97dd06157..400046bad74 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 de84281328b..398b4064d15 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 24c5d234c12..f5d5e6e9a95 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<Integer, Integer, String, Integer> 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<Integer, Integer, Integer, String, Integer> 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<Integer, Integer, String, Integer> 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 b192021f821..52fd84113fd 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 d01938b123b..ac4bf7d9c2c 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 c850c85b6e5..c0903b65737 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; @@ -352,6 +354,46 @@ public final class UiAutomation { } /** + * 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. * For example going back, going home, opening recents, etc. @@ -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 e6347354b72..b406d9e30a5 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 92c757ceaa2..72981a77327 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 755232c6a6b..7c9943b61ea 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<String, String, Integer, Integer> 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<String, Integer, Integer> 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 0ed97247712..768dddd35a6 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 786d757557d..5c273de2376 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 52a3536e63d..0eec8e9cb71 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<Integer, Integer, String, Integer> 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<Integer, Integer, Integer, String, Integer> 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<Integer, Integer, String, Integer> 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<String, String, Integer, Integer> 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<String, Integer, Integer> 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 894897705d4..3e36dae528e 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<ActivityIntentInfo> { 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) @@ -9237,6 +9268,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); if (p == null) { @@ -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 a042fedf8b4..ec15c16981a 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 14cb9e3a8f7..9768c622f19 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); } } |