From 427ce30d9bc7909df09668c24e74e92a6612bb60 Mon Sep 17 00:00:00 2001 From: quh4gko8 <88831734+quh4gko8@users.noreply.github.com> Date: Thu, 16 Nov 2023 17:52:54 +0000 Subject: [PATCH] Support for getting the calling app's permission state for activities This allows for apps that can already access the calling app identity that opens their activity, along with system browser, to check whenever a permission is granted, similar to the permission entry in activity section of AndroidManifest.xml. --- core/java/android/app/Activity.java | 13 ++++ core/java/android/app/ActivityClient.java | 8 ++ .../app/IActivityClientController.aidl | 7 ++ .../server/wm/ActivityClientController.java | 12 +++ .../android/server/wm/WindowManagerHooks.java | 77 +++++++++++++++++++ 5 files changed, 117 insertions(+) create mode 100644 services/core/java/com/android/server/wm/WindowManagerHooks.java diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 64c0e85df975..abe98ffbc21e 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -6931,6 +6931,19 @@ public String getLaunchedFromPackage() { return ActivityClient.getInstance().getLaunchedFromPackage(getActivityToken()); } + /** + * Returns if the permission is granted on app that initially launched this activity. + * + *
In order to receive the launching app's package name permission state, + * the current activity is from a system browser, otherwise {@code SecurityException} is thrown. + * @return the permission state of a permission if it's granted + * @hide + */ + @UnsupportedAppUsage + public int checkLaunchedFromPackagePermission(String permission) { + return ActivityClient.getInstance().checkLaunchedFromPackagePermission(getActivityToken(), permission); + } + /** * Control whether this activity's main window is visible. This is intended * only for the special case of an activity that is not going to show a diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java index b35e87b541d3..acc4ef775209 100644 --- a/core/java/android/app/ActivityClient.java +++ b/core/java/android/app/ActivityClient.java @@ -294,6 +294,14 @@ public String getLaunchedFromPackage(IBinder token) { } } + public int checkLaunchedFromPackagePermission(IBinder token, String permission) { + try { + return getActivityClientController().checkLaunchedFromPackagePermission(token, permission); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + public void setRequestedOrientation(IBinder token, int requestedOrientation) { try { getActivityClientController().setRequestedOrientation(token, requestedOrientation); diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl index a3c5e1c67e1b..7bf7c0b2d531 100644 --- a/core/java/android/app/IActivityClientController.aidl +++ b/core/java/android/app/IActivityClientController.aidl @@ -191,4 +191,11 @@ interface IActivityClientController { */ boolean isRequestedToLaunchInTaskFragment(in IBinder activityToken, in IBinder taskFragmentToken); + + /** + * Return {@code true} if the calling package had a permission granted. + * Only accessible to system browser app and apps that can already access + * the app launching the activity via getLaunchedFrom{Uid,Package} + */ + int checkLaunchedFromPackagePermission(in IBinder token, String permission); } diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index aafff2c70b8b..60862bb1b3d5 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -705,6 +705,18 @@ public String getLaunchedFromPackage(IBinder token) { return null; } + @Override + public int checkLaunchedFromPackagePermission(IBinder token, String permission) { + if (!WindowManagerHooks.canAccessLaunchedFromPackagePermission()) { + throw new SecurityException(); + } + + synchronized (mGlobalLock) { + final ActivityRecord r = ActivityRecord.forTokenLocked(token); + return WindowManagerHooks.checkLaunchedFromPackagePermission(r, permission); + } + } + /** Whether the call to one of the getLaunchedFrom APIs is performed by an internal caller. */ private boolean isInternalCallerGetLaunchedFrom(int uid) { if (UserHandle.getAppId(uid) == SYSTEM_UID) { diff --git a/services/core/java/com/android/server/wm/WindowManagerHooks.java b/services/core/java/com/android/server/wm/WindowManagerHooks.java new file mode 100644 index 000000000000..471c266769cc --- /dev/null +++ b/services/core/java/com/android/server/wm/WindowManagerHooks.java @@ -0,0 +1,77 @@ +package com.android.server.wm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityThread; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; +import android.ext.BrowserUtils; +import android.os.Binder; +import android.os.Build; +import android.os.SystemProperties; +import android.os.UserHandle; + +import com.android.internal.util.ArrayUtils; + +import com.android.server.LocalServices; +import com.android.server.pm.permission.PermissionManagerServiceInternal; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageStateInternal; + +class WindowManagerHooks { + + static boolean canAccessLaunchedFromPackagePermission() { + final int callingUid = Binder.getCallingUid(); + var pmi = LocalServices.getService(PackageManagerInternal.class); + AndroidPackage callingPkg = pmi.getPackage(callingUid); + if (callingPkg == null) { + return false; + } + + String callingPkgName = callingPkg.getPackageName(); + if (canAccessForDebuggingPurposes(callingPkgName)) { + return true; + } + + Context ctx = ActivityThread.currentActivityThread().getSystemContext(); + if (!BrowserUtils.isSystemBrowser(ctx, callingPkgName)) { + return false; + } + + PackageStateInternal callingPsi = pmi.getPackageStateInternal(callingPkg.getPackageName()); + if (callingPsi == null) { + return false; + } + + return callingPsi.isSystem(); + } + + static boolean canAccessForDebuggingPurposes(@NonNull String packageName) { + if (!Build.isDebuggable()) { + return false; + } + + String testPkgs = SystemProperties.get("persist.launchedFromPackagePermission_test_pkgs"); + return ArrayUtils.contains(testPkgs.split(","), packageName); + } + + static int checkLaunchedFromPackagePermission(@Nullable ActivityRecord r, @NonNull String permission) { + if (r == null) { + return PackageManager.PERMISSION_DENIED; + } + + String packageName = r.launchedFromPackage; + final int userId = UserHandle.getUserId(r.launchedFromUid); + var permService = LocalServices.getService(PermissionManagerServiceInternal.class); + + // Do not take into account of package visibility here. + long token = Binder.clearCallingIdentity(); + try { + return permService.checkPermission(packageName, permission, userId); + } finally { + Binder.restoreCallingIdentity(token); + } + + } +}