Allow adopting a subset of shell permissions
Add an API to allow adopting a subset of shell UID permissions.
Test: added - android.app.uiautomation.cts.UiAutomationTest#testAdoptSomeShellPermissions
passed - atest atest android.app.uiautomation.cts.UiAutomationTest
bug:80415658
Change-Id: I5cd8beeed7e586b2ac4e0cb16686cef738641e23
diff --git a/api/current.txt b/api/current.txt
index cc38630..63bb349 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -6180,6 +6180,7 @@
public final class UiAutomation {
method public void adoptShellPermissionIdentity();
+ method public void adoptShellPermissionIdentity(java.lang.String...);
method public void clearWindowAnimationFrameStats();
method public boolean clearWindowContentFrameStats(int);
method public void dropShellPermissionIdentity();
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 519a274..80f512c 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -486,7 +486,7 @@
* instrumentation at a time. An active instrumentation is one running and
* started from the shell.
*/
- void startDelegateShellPermissionIdentity(int uid);
+ void startDelegateShellPermissionIdentity(int uid, in String[] permissions);
/**
* Method for the shell UID to stop deletating its permission identity to an
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index ac4bf7d..96da72a 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -47,7 +47,7 @@
in ParcelFileDescriptor source);
void grantRuntimePermission(String packageName, String permission, int userId);
void revokeRuntimePermission(String packageName, String permission, int userId);
- void adoptShellPermissionIdentity(int uid);
+ void adoptShellPermissionIdentity(int uid, in String[] permissions);
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 5a25f5a..3f9627e 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -354,12 +354,17 @@
}
/**
- * 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.
+ * Adopt the permission identity of the shell UID for all permissions. This allows
+ * you to call APIs protected permissions which normal apps cannot hold but are
+ * granted to the shell UID. If you already adopted all shell permissions by calling
+ * this method or {@link #adoptShellPermissionIdentity(String...)} a subsequent call
+ * 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.
+ * <p>
+ * <strong>Note:<strong/> Calling this method adopts all shell permissions and overrides
+ * any subset of adopted permissions via {@link #adoptShellPermissionIdentity(String...)}.
*
+ * @see #adoptShellPermissionIdentity(String...)
* @see #dropShellPermissionIdentity()
*/
public void adoptShellPermissionIdentity() {
@@ -368,7 +373,33 @@
}
try {
// Calling out without a lock held.
- mUiAutomationConnection.adoptShellPermissionIdentity(Process.myUid());
+ mUiAutomationConnection.adoptShellPermissionIdentity(Process.myUid(), null);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error executing adopting shell permission identity!", re);
+ }
+ }
+
+ /**
+ * Adopt the permission identity of the shell UID only for the provided permissions.
+ * 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 specified shell
+ * permissions by calling this method or {@link #adoptShellPermissionIdentity()} a
+ * subsequent call 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.
+ * <p>
+ * <strong>Note:<strong/> Calling this method adopts only the specified shell permissions
+ * and overrides all adopted permissions via {@link #adoptShellPermissionIdentity()}.
+ *
+ * @see #adoptShellPermissionIdentity()
+ * @see #dropShellPermissionIdentity()
+ */
+ public void adoptShellPermissionIdentity(String... permissions) {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ try {
+ // Calling out without a lock held.
+ mUiAutomationConnection.adoptShellPermissionIdentity(Process.myUid(), permissions);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error executing adopting shell permission identity!", re);
}
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index b406d9e..dc2f983 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -18,7 +18,7 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.IPackageManager;
import android.graphics.Bitmap;
@@ -279,7 +279,8 @@
}
@Override
- public void adoptShellPermissionIdentity(int uid) throws RemoteException {
+ public void adoptShellPermissionIdentity(int uid, @Nullable String[] permissions)
+ throws RemoteException {
synchronized (mLock) {
throwIfCalledByNotTrustedUidLocked();
throwIfShutdownLocked();
@@ -287,7 +288,7 @@
}
final long identity = Binder.clearCallingIdentity();
try {
- mActivityManager.startDelegateShellPermissionIdentity(uid);
+ mActivityManager.startDelegateShellPermissionIdentity(uid, permissions);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e8c6365..5316f4e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19386,7 +19386,8 @@
}
@Override
- public void startDelegateShellPermissionIdentity(int delegateUid) {
+ public void startDelegateShellPermissionIdentity(int delegateUid,
+ @Nullable String[] permissions) {
if (UserHandle.getCallingAppId() != Process.SHELL_UID
&& UserHandle.getCallingAppId() != Process.ROOT_UID) {
throw new SecurityException("Only the shell can delegate its permissions");
@@ -19405,11 +19406,13 @@
if (!(mAppOpsService.getAppOpsServiceDelegate() instanceof ShellDelegate)) {
throw new IllegalStateException("Bad shell delegate state");
}
- if (((ShellDelegate) mAppOpsService.getAppOpsServiceDelegate())
- .getDelegateUid() != delegateUid) {
+ final ShellDelegate delegate = (ShellDelegate) mAppOpsService
+ .getAppOpsServiceDelegate();
+ if (delegate.getDelegateUid() != delegateUid) {
throw new SecurityException("Shell can delegate permissions only "
+ "to one instrumentation at a time");
}
+ delegate.setPermissions(permissions);
return;
}
@@ -19427,7 +19430,7 @@
// Hook them up...
final ShellDelegate shellDelegate = new ShellDelegate(
- instr.mTargetInfo.packageName, delegateUid);
+ instr.mTargetInfo.packageName, delegateUid, permissions);
mAppOpsService.setAppOpsServiceDelegate(shellDelegate);
getPackageManagerInternalLocked().setCheckPermissionDelegate(shellDelegate);
return;
@@ -19450,20 +19453,26 @@
private class ShellDelegate implements CheckOpsDelegate, CheckPermissionDelegate {
private final String mTargetPackageName;
private final int mTargetUid;
+ private @Nullable String[] mPermissions;
- ShellDelegate(String targetPacakgeName, int targetUid) {
+ ShellDelegate(String targetPacakgeName, int targetUid, @Nullable String[] permissions) {
mTargetPackageName = targetPacakgeName;
mTargetUid = targetUid;
+ mPermissions = permissions;
}
int getDelegateUid() {
return mTargetUid;
}
+ void setPermissions(@Nullable String[] permissions) {
+ mPermissions = permissions;
+ }
+
@Override
public int checkOperation(int code, int uid, String packageName,
TriFunction<Integer, Integer, String, Integer> superImpl) {
- if (uid == mTargetUid) {
+ if (uid == mTargetUid && isTargetOp(code)) {
final long identity = Binder.clearCallingIdentity();
try {
return superImpl.apply(code, Process.SHELL_UID,
@@ -19478,7 +19487,7 @@
@Override
public int checkAudioOperation(int code, int usage, int uid, String packageName,
QuadFunction<Integer, Integer, Integer, String, Integer> superImpl) {
- if (uid == mTargetUid) {
+ if (uid == mTargetUid && isTargetOp(code)) {
final long identity = Binder.clearCallingIdentity();
try {
return superImpl.apply(code, usage, Process.SHELL_UID,
@@ -19493,7 +19502,7 @@
@Override
public int noteOperation(int code, int uid, String packageName,
TriFunction<Integer, Integer, String, Integer> superImpl) {
- if (uid == mTargetUid) {
+ if (uid == mTargetUid && isTargetOp(code)) {
final long identity = Binder.clearCallingIdentity();
try {
return mAppOpsService.noteProxyOperation(code, Process.SHELL_UID,
@@ -19508,7 +19517,7 @@
@Override
public int checkPermission(String permName, String pkgName, int userId,
TriFunction<String, String, Integer, Integer> superImpl) {
- if (mTargetPackageName.equals(pkgName)) {
+ if (mTargetPackageName.equals(pkgName) && isTargetPermission(permName)) {
return superImpl.apply(permName, "com.android.shell", userId);
}
return superImpl.apply(permName, pkgName, userId);
@@ -19517,11 +19526,29 @@
@Override
public int checkUidPermission(String permName, int uid,
BiFunction<String, Integer, Integer> superImpl) {
- if (uid == mTargetUid) {
+ if (uid == mTargetUid && isTargetPermission(permName)) {
return superImpl.apply(permName, Process.SHELL_UID);
}
return superImpl.apply(permName, uid);
}
+
+ private boolean isTargetOp(int code) {
+ // null permissions means all ops are targeted
+ if (mPermissions == null) {
+ return true;
+ }
+ // no permission for the op means the op is targeted
+ final String permission = AppOpsManager.opToPermission(code);
+ if (permission == null) {
+ return true;
+ }
+ return isTargetPermission(permission);
+ }
+
+ private boolean isTargetPermission(@NonNull String permission) {
+ // null permissions means all permissions are targeted
+ return (mPermissions == null || ArrayUtils.contains(mPermissions, permission));
+ }
}
/**