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
Change-Id: I4bfd4b475225125512abf80ea98cd8fcacb6a1be
diff --git a/api/current.txt b/api/current.txt
index 20e62c0..f4d0ef4 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6120,8 +6120,10 @@
}
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(java.lang.Runnable, android.app.UiAutomation.AccessibilityEventFilter, long) throws java.util.concurrent.TimeoutException;
method public android.os.ParcelFileDescriptor executeShellCommand(java.lang.String);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index b08ae0e0..36f8459 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -266,7 +266,9 @@
method public abstract java.lang.String getPermissionControllerPackageName();
method public abstract java.lang.String getServicesSystemSharedLibraryPackageName();
method public abstract java.lang.String getSharedSystemSharedLibraryPackageName();
+ method public abstract void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
method public abstract boolean isPermissionReviewModeEnabled();
+ method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
field public static final java.lang.String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
field public static final java.lang.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 8b33963..c3404a5 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2487,7 +2487,7 @@
*/
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 24c5d23..f5d5e6e 100644
--- a/core/java/android/app/AppOpsManagerInternal.java
+++ b/core/java/android/app/AppOpsManagerInternal.java
@@ -18,12 +18,55 @@
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 49d3fffc..7379630 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -499,4 +499,19 @@
* user unlock progress.
*/
boolean startUserInBackgroundWithListener(int userid, IProgressListener unlockProgressListener);
+
+ /**
+ * 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 d01938b..ac4bf7d 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -47,7 +47,8 @@
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 5662aea..44f28796 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -33,6 +33,7 @@
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;
@@ -50,6 +51,7 @@
import android.view.accessibility.IAccessibilityInteractionConnection;
import com.android.internal.util.function.pooled.PooledLambda;
+
import libcore.io.IoUtils;
import java.io.IOException;
@@ -347,6 +349,46 @@
}
/**
+ * 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.
@@ -999,6 +1041,8 @@
*
* @param command The command to execute.
* @return A file descriptor to the standard output stream.
+ *
+ * @see #adoptShellPermissionIdentity()
*/
public ParcelFileDescriptor executeShellCommand(String command) {
synchronized (mLock) {
@@ -1081,22 +1125,6 @@
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 d3828ab..ac3f2e7 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -30,6 +30,7 @@
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;
@@ -37,7 +38,6 @@
import android.view.WindowContentFrameStats;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.IAccessibilityManager;
-import android.util.Log;
import libcore.io.IoUtils;
@@ -71,6 +71,9 @@
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();
@@ -274,6 +277,36 @@
}
}
+ @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 721063a..a058ac6 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3580,6 +3580,7 @@
*
* @hide
*/
+ @TestApi
@SystemApi
@RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
public abstract void grantRuntimePermission(@NonNull String packageName,
@@ -3606,6 +3607,7 @@
*
* @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 f30b3fe..ee752f8 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -29,9 +29,12 @@
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 @@
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.
*/
@@ -628,4 +657,18 @@
*/
public abstract boolean hasSignatureCapability(int serverUid, int clientUid,
@PackageParser.SigningDetails.CertCapabilities int capability);
+
+ /**
+ * 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 0ed9724..768dddd 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -33,7 +33,7 @@
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 1167e1d..b3f6bd1 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.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 @@
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 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 @@
@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 @@
}
@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 @@
@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 89810f9..8555dd4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -170,6 +170,7 @@
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;
@@ -225,6 +226,7 @@
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;
@@ -345,6 +347,8 @@
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;
@@ -425,6 +429,7 @@
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BiFunction;
import dalvik.system.VMRuntime;
import libcore.io.IoUtils;
@@ -18903,6 +18908,8 @@
// 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();
}
@@ -23057,4 +23064,143 @@
return mNmi != null;
}
}
+
+ @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 c536e4d..734a872 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -169,6 +169,7 @@
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;
@@ -289,6 +290,8 @@
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;
@@ -363,6 +366,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
import java.util.function.Predicate;
import dalvik.system.CloseGuard;
@@ -1031,6 +1035,9 @@
void receiveVerificationResponse(int verificationId);
}
+ @GuardedBy("mPackages")
+ private CheckPermissionDelegate mCheckPermissionDelegate;
+
private class IntentVerifierProxy implements IntentFilterVerifier<ActivityIntentInfo> {
private Context mContext;
private ComponentName mIntentFilterVerifierComponent;
@@ -5260,11 +5267,35 @@
@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)
@@ -9150,6 +9181,16 @@
}
@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) {
@@ -24150,6 +24191,20 @@
PackageManagerService.this.notifyPackageUseLocked(packageName, reason);
}
}
+
+ @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 a042fed..ec15c16 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.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.