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); } }