diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java index 6954b312f91cb..33bd839a53f69 100644 --- a/core/java/android/content/PermissionChecker.java +++ b/core/java/android/content/PermissionChecker.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; import android.content.pm.PackageManager; +import android.content.pm.PermissionInfo; import android.os.Binder; import android.os.Process; @@ -67,22 +68,30 @@ import java.lang.annotation.RetentionPolicy; * @hide */ public final class PermissionChecker { - /** Permission result: The permission is granted. */ + /** The permission is granted. */ public static final int PERMISSION_GRANTED = PackageManager.PERMISSION_GRANTED; - /** Permission result: The permission is denied. */ - public static final int PERMISSION_DENIED = PackageManager.PERMISSION_DENIED; + /** Returned when: + * + * + */ + public static final int PERMISSION_HARD_DENIED = PackageManager.PERMISSION_DENIED; - /** Permission result: The permission is denied because the app op is not allowed. */ - public static final int PERMISSION_DENIED_APP_OP = PackageManager.PERMISSION_DENIED - 1; + /** Only for runtime permissions, its returned when the runtime permission + * is granted, but the corresponding app op is denied. */ + public static final int PERMISSION_SOFT_DENIED = PackageManager.PERMISSION_DENIED - 1; /** Constant when the PID for which we check permissions is unknown. */ public static final int PID_UNKNOWN = -1; /** @hide */ @IntDef({PERMISSION_GRANTED, - PERMISSION_DENIED, - PERMISSION_DENIED_APP_OP}) + PERMISSION_SOFT_DENIED, + PERMISSION_HARD_DENIED}) @Retention(RetentionPolicy.SOURCE) public @interface PermissionResult {} @@ -116,7 +125,7 @@ public final class PermissionChecker { * the first package for the calling UID will be used. * @param featureId Feature in the package * @return The permission check result which is either {@link #PERMISSION_GRANTED} - * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. * @param message A message describing the reason the permission was checked * * @see #checkPermissionForPreflight(Context, String, int, int, String) @@ -155,7 +164,7 @@ public final class PermissionChecker { * @param packageName The package name for which to check. If null the * the first package for the calling UID will be used. * @return The permission check result which is either {@link #PERMISSION_GRANTED} - * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. * * @see #checkPermissionForDataDelivery(Context, String, int, int, String, String) */ @@ -189,7 +198,7 @@ public final class PermissionChecker { * @param context Context for accessing resources. * @param permission The permission to check. * @return The permission check result which is either {@link #PERMISSION_GRANTED} - * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. * @param message A message describing the reason the permission was checked * * @see #checkSelfPermissionForPreflight(Context, String) @@ -225,7 +234,7 @@ public final class PermissionChecker { * @param context Context for accessing resources. * @param permission The permission to check. * @return The permission check result which is either {@link #PERMISSION_GRANTED} - * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. * * @see #checkSelfPermissionForDataDelivery(Context, String, String) */ @@ -259,7 +268,7 @@ public final class PermissionChecker { * the first package for the calling UID will be used. * @param featureId The feature inside of the app * @return The permission check result which is either {@link #PERMISSION_GRANTED} - * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. * @param message A message describing the reason the permission was checked * * @see #checkCallingPermissionForPreflight(Context, String, String) @@ -269,7 +278,7 @@ public final class PermissionChecker { @NonNull String permission, @Nullable String packageName, @Nullable String featureId, @Nullable String message) { if (Binder.getCallingPid() == Process.myPid()) { - return PERMISSION_DENIED; + return PERMISSION_HARD_DENIED; } return checkPermissionForDataDelivery(context, permission, Binder.getCallingPid(), Binder.getCallingUid(), packageName, featureId, message); @@ -299,7 +308,7 @@ public final class PermissionChecker { * @param packageName The package name making the IPC. If null the * the first package for the calling UID will be used. * @return The permission check result which is either {@link #PERMISSION_GRANTED} - * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. * * @see #checkCallingPermissionForDataDelivery(Context, String, String, String) */ @@ -307,7 +316,7 @@ public final class PermissionChecker { public static int checkCallingPermissionForPreflight(@NonNull Context context, @NonNull String permission, @Nullable String packageName) { if (Binder.getCallingPid() == Process.myPid()) { - return PERMISSION_DENIED; + return PERMISSION_HARD_DENIED; } return checkPermissionForPreflight(context, permission, Binder.getCallingPid(), Binder.getCallingUid(), packageName); @@ -333,7 +342,7 @@ public final class PermissionChecker { * @param context Context for accessing resources. * @param permission The permission to check. * @return The permission check result which is either {@link #PERMISSION_GRANTED} - * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. * @param featureId feature Id of caller (if not self) * @param message A message describing the reason the permission was checked * @@ -372,7 +381,7 @@ public final class PermissionChecker { * @param context Context for accessing resources. * @param permission The permission to check. * @return The permission check result which is either {@link #PERMISSION_GRANTED} - * or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}. + * or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}. * * @see #checkCallingOrSelfPermissionForDataDelivery(Context, String, String, String) */ @@ -385,39 +394,85 @@ public final class PermissionChecker { Binder.getCallingUid(), packageName); } - private static int checkPermissionCommon(@NonNull Context context, @NonNull String permission, + static int checkPermissionCommon(@NonNull Context context, @NonNull String permission, int pid, int uid, @Nullable String packageName, @Nullable String featureId, @Nullable String message, boolean forDataDelivery) { - if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) { - return PERMISSION_DENIED; - } - - AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); - String op = appOpsManager.permissionToOp(permission); - if (op == null) { - return PERMISSION_GRANTED; + final PermissionInfo permissionInfo; + try { + // TODO(b/147869157): Cache platform defined app op and runtime permissions to avoid + // calling into the package manager every time. + permissionInfo = context.getPackageManager().getPermissionInfo(permission, 0); + } catch (PackageManager.NameNotFoundException ignored) { + return PERMISSION_HARD_DENIED; } if (packageName == null) { String[] packageNames = context.getPackageManager().getPackagesForUid(uid); - if (packageNames == null || packageNames.length <= 0) { - return PERMISSION_DENIED; + if (packageNames != null && packageNames.length > 0) { + packageName = packageNames[0]; } - packageName = packageNames[0]; } - if (forDataDelivery) { - if (appOpsManager.noteProxyOpNoThrow(op, packageName, uid, featureId, message) - != AppOpsManager.MODE_ALLOWED) { - return PERMISSION_DENIED_APP_OP; + if (permissionInfo.isAppOp()) { + return checkAppOpPermission(context, permission, pid, uid, packageName, featureId, + message, forDataDelivery); + } + if (permissionInfo.isRuntime()) { + return checkRuntimePermission(context, permission, pid, uid, packageName, featureId, + message, forDataDelivery); + } + return context.checkPermission(permission, pid, uid); + } + + private static int checkAppOpPermission(@NonNull Context context, @NonNull String permission, + int pid, int uid, @Nullable String packageName, @Nullable String featureId, + @Nullable String message, boolean forDataDelivery) { + final String op = AppOpsManager.permissionToOp(permission); + if (op == null || packageName == null) { + return PERMISSION_HARD_DENIED; + } + + final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); + final int opMode = (forDataDelivery) + ? appOpsManager.noteProxyOpNoThrow(op, packageName, uid, featureId, message) + : appOpsManager.unsafeCheckOpNoThrow(op, uid, packageName); + + switch (opMode) { + case AppOpsManager.MODE_ALLOWED: { + return PERMISSION_GRANTED; } + case AppOpsManager.MODE_DEFAULT: { + return context.checkPermission(permission, pid, uid) + == PackageManager.PERMISSION_GRANTED + ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED; + } + default: { + return PERMISSION_HARD_DENIED; + } + } + } + + private static int checkRuntimePermission(@NonNull Context context, @NonNull String permission, + int pid, int uid, @Nullable String packageName, @Nullable String featureId, + @Nullable String message, boolean forDataDelivery) { + if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) { + return PERMISSION_HARD_DENIED; + } + + final String op = AppOpsManager.permissionToOp(permission); + if (op == null || packageName == null) { + return PERMISSION_GRANTED; + } + + final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); + final int opMode = (forDataDelivery) + ? appOpsManager.noteProxyOpNoThrow(op, packageName, uid, featureId, message) + : appOpsManager.unsafeCheckOpNoThrow(op, uid, packageName); + + if (opMode == AppOpsManager.MODE_ALLOWED) { + return PERMISSION_GRANTED; } else { - final int mode = appOpsManager.unsafeCheckOpRawNoThrow(op, uid, packageName); - if (mode != AppOpsManager.MODE_ALLOWED && mode != AppOpsManager.MODE_FOREGROUND) { - return PERMISSION_DENIED_APP_OP; - } + return PERMISSION_SOFT_DENIED; } - - return PERMISSION_GRANTED; } } diff --git a/core/tests/coretests/src/android/content/PermissionCheckerTest.java b/core/tests/coretests/src/android/content/PermissionCheckerTest.java new file mode 100644 index 0000000000000..cb04a7497d3b5 --- /dev/null +++ b/core/tests/coretests/src/android/content/PermissionCheckerTest.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.AppOpsManager; +import android.app.UiAutomation; +import android.os.Binder; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; + +// TODO(b/147877945): Add missing tests. +@RunWith(AndroidJUnit4.class) +public class PermissionCheckerTest { + private static final String INTERACT_ACROSS_PROFILES_PERMISSION = + "android.permission.INTERACT_ACROSS_PROFILES"; + private static final String MANAGE_APP_OPS_MODE = "android.permission.MANAGE_APP_OPS_MODES"; + + private final Context mContext = InstrumentationRegistry.getContext();; + private final AppOpsManager mAppOpsManager = mContext.getSystemService(AppOpsManager.class); + private UiAutomation mUiAutomation = + InstrumentationRegistry.getInstrumentation().getUiAutomation(); + + @After + public void tearDown() { + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .dropShellPermissionIdentity(); + } + + @Test + public void testCheckPermissionForPreflight_appOpPermission_modeDefaultAndPermissionGranted_returnsGranted() { + mUiAutomation.adoptShellPermissionIdentity( + INTERACT_ACROSS_PROFILES_PERMISSION, MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_DEFAULT); + + assertThat(PermissionChecker.checkPermissionForPreflight( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), mContext.getPackageName())) + .isEqualTo(PermissionChecker.PERMISSION_GRANTED); + } + + @Test + public void testCheckPermissionForPreflight_appOpPermission_modeDefaultAndPermissionNotGranted_returnsHardDenied() { + mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_DEFAULT); + + assertThat(PermissionChecker.checkPermissionForPreflight( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), mContext.getPackageName())) + .isEqualTo(PermissionChecker.PERMISSION_HARD_DENIED); + } + + @Test + public void testCheckPermissionForPreflight_appOpPermission_modeAllowed_returnsGranted() { + mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_ALLOWED); + + assertThat(PermissionChecker.checkPermissionForPreflight( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), mContext.getPackageName())) + .isEqualTo(PermissionChecker.PERMISSION_GRANTED); + } + + @Test + public void testCheckPermissionForPreflight_appOpPermission_packageNameIsNull_returnsGranted() { + mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_ALLOWED); + + assertThat(PermissionChecker.checkPermissionForPreflight( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), /* packageName= */ null)) + .isEqualTo(PermissionChecker.PERMISSION_GRANTED); + } + + @Test + public void testCheckPermissionForPreflight_appOpPermission_modeIgnored_returnsHardDenied() { + mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_IGNORED); + + assertThat(PermissionChecker.checkPermissionForPreflight( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), mContext.getPackageName())) + .isEqualTo(PermissionChecker.PERMISSION_HARD_DENIED); + } + + @Test + public void testCheckPermissionForPreflight_appOpPermission_modeErrored_returnsHardDenied() { + mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_ERRORED); + + assertThat(PermissionChecker.checkPermissionForPreflight( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), mContext.getPackageName())) + .isEqualTo(PermissionChecker.PERMISSION_HARD_DENIED); + } + + @Test + public void testCheckPermissionForDataDelivery_appOpPermission_modeDefaultAndPermissionGranted_returnsGranted() { + mUiAutomation.adoptShellPermissionIdentity( + INTERACT_ACROSS_PROFILES_PERMISSION, MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_DEFAULT); + + assertThat(PermissionChecker.checkPermissionForDataDelivery( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), mContext.getPackageName(), /* featureId= */null, + /* message= */null)).isEqualTo(PermissionChecker.PERMISSION_GRANTED); + } + + @Test + public void testCheckPermissionForDataDelivery_appOpPermission_modeDefaultAndPermissionNotGranted_returnsHardDenied() { + mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_DEFAULT); + + assertThat(PermissionChecker.checkPermissionForDataDelivery( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), mContext.getPackageName(), /* featureId= */null, + /* message= */null)).isEqualTo(PermissionChecker.PERMISSION_HARD_DENIED); + } + + @Test + public void testCheckPermissionForDataDelivery_appOpPermission_modeAllowed_returnsGranted() { + mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_ALLOWED); + + assertThat(PermissionChecker.checkPermissionForDataDelivery( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), mContext.getPackageName(), /* featureId= */null, + /* message= */null)).isEqualTo(PermissionChecker.PERMISSION_GRANTED); + } + + @Test + public void testCheckPermissionForDataDelivery_appOpPermission_packageNameIsNull_returnsGranted() { + mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_ALLOWED); + + assertThat(PermissionChecker.checkPermissionForDataDelivery( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), /* packageName= */ null, /* featureId= */null, + /* message= */null)).isEqualTo(PermissionChecker.PERMISSION_GRANTED); + } + + @Test + public void testCheckPermissionForDataDelivery_appOpPermission_modeIgnored_returnsHardDenied() { + mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_IGNORED); + + assertThat(PermissionChecker.checkPermissionForDataDelivery( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), mContext.getPackageName(), /* featureId= */null, + /* message= */null)).isEqualTo(PermissionChecker.PERMISSION_HARD_DENIED); + } + + @Test + public void testCheckPermissionForDataDelivery_appOpPermission_modeErrored_returnsHardDenied() { + mUiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE); + mAppOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION), + Binder.getCallingUid(), mContext.getPackageName(), AppOpsManager.MODE_ERRORED); + + assertThat(PermissionChecker.checkPermissionForDataDelivery( + mContext, INTERACT_ACROSS_PROFILES_PERMISSION, Binder.getCallingPid(), + Binder.getCallingUid(), mContext.getPackageName(), /* featureId= */null, + /* message= */null)).isEqualTo(PermissionChecker.PERMISSION_HARD_DENIED); + } +} diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java index 12f9fd936fbaa..1ed97becb7769 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java @@ -257,10 +257,10 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic switch (status) { case PermissionChecker.PERMISSION_GRANTED: return; - case PermissionChecker.PERMISSION_DENIED: + case PermissionChecker.PERMISSION_HARD_DENIED: throw new SecurityException( String.format("Caller must have the %s permission.", permission)); - case PermissionChecker.PERMISSION_DENIED_APP_OP: + case PermissionChecker.PERMISSION_SOFT_DENIED: throw new ServiceSpecificException(Status.TEMPORARY_PERMISSION_DENIED, String.format("Caller must have the %s permission.", permission)); default: