diff options
6 files changed, 374 insertions, 28 deletions
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index a79fe775e07b..1254f1a3afa0 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -169,6 +169,7 @@ message Atom { BluetoothSmpPairingEventReported bluetooth_smp_pairing_event_reported = 167; ProcessStartTime process_start_time = 169; BluetoothSocketConnectionStateChanged bluetooth_socket_connection_state_changed = 171; + DeviceIdentifierAccessDenied device_identifier_access_denied = 172; NetworkStackReported network_stack_reported = 182 [(log_from_module) = "network_stack"]; } @@ -3098,3 +3099,22 @@ message NetworkStackReported { optional android.stats.connectivity.NetworkStackEventData network_stack_event = 2 [(log_mode) = MODE_BYTES]; } +/** + * Logs when a package is denied access to a device identifier based on the new access requirements. + * + * Logged from: + * frameworks/base/telephony/java/com/android/internal/telephony/TelephonyPermissions.java + */ +message DeviceIdentifierAccessDenied { + // The name of the package denied access to the requested device identifier. + optional string package_name = 1; + + // The name of the device identifier method the package attempted to invoke. + optional string method_name = 2; + + // True if the package is preinstalled. + optional bool is_preinstalled = 3; + + // True if the package is privileged. + optional bool is_priv_app = 4; +}
\ No newline at end of file diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index e2322f3e2eeb..79a23d605377 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -5654,6 +5654,31 @@ public class DevicePolicyManager { } /** + * Returns whether the specified package can read the device identifiers. + * + * @param packageName The package name of the app to check for device identifier access. + * @param pid The process id of the package to be checked. + * @param uid The uid of the package to be checked. + * @return whether the package can read the device identifiers. + * + * @hide + */ + public boolean checkDeviceIdentifierAccess(String packageName, int pid, int uid) { + throwIfParentInstance("checkDeviceIdentifierAccess"); + if (packageName == null) { + return false; + } + if (mService != null) { + try { + return mService.checkDeviceIdentifierAccess(packageName, pid, uid); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + return false; + } + + /** * @hide * @return the human readable name of the organisation associated with this DPM or {@code null} * if one is not set. diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 0e95e6392733..d74943ae129e 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -146,6 +146,7 @@ interface IDevicePolicyManager { int getDeviceOwnerUserId(); boolean setProfileOwner(in ComponentName who, String ownerName, int userHandle); + ComponentName getProfileOwnerAsUser(int userHandle); ComponentName getProfileOwner(int userHandle); String getProfileOwnerName(int userHandle); void setProfileEnabled(in ComponentName who); @@ -153,6 +154,8 @@ interface IDevicePolicyManager { void clearProfileOwner(in ComponentName who); boolean hasUserSetupCompleted(); + boolean checkDeviceIdentifierAccess(in String packageName, int pid, int uid); + void setDeviceOwnerLockScreenInfo(in ComponentName who, CharSequence deviceOwnerInfo); CharSequence getDeviceOwnerLockScreenInfo(); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index 1c9782fa5565..f2560b3728eb 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -15,7 +15,6 @@ */ package com.android.server.devicepolicy; -import android.annotation.UserIdInt; import android.app.admin.IDevicePolicyManager; import android.content.ComponentName; import android.os.PersistableBundle; @@ -159,4 +158,9 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { @Override public void setDefaultSmsApplication(ComponentName admin, String packageName) { } + + @Override + public boolean checkDeviceIdentifierAccess(String packageName, int pid, int uid) { + return false; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index d3b25fd1afa0..d1128af1f7c3 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -60,7 +60,6 @@ import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; - import static android.provider.Telephony.Carriers.DPC_URI; import static android.provider.Telephony.Carriers.ENFORCE_KEY; import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI; @@ -69,11 +68,10 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent .PROVISIONING_ENTRY_POINT_ADB; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker .STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; - -import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER; -import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER; - - +import static com.android.server.devicepolicy.TransferOwnershipMetadataManager + .ADMIN_TYPE_DEVICE_OWNER; +import static com.android.server.devicepolicy.TransferOwnershipMetadataManager + .ADMIN_TYPE_PROFILE_OWNER; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; @@ -219,11 +217,11 @@ import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.FunctionalUtils.ThrowingRunnable; import com.android.internal.util.JournaledFile; import com.android.internal.util.Preconditions; +import com.android.internal.util.StatLogger; import com.android.internal.util.XmlUtils; import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; import com.android.server.LockGuard; -import com.android.internal.util.StatLogger; import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo; @@ -5188,7 +5186,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void enforceCanManageCaCerts(ComponentName who, String callerPackage) { if (who == null) { - if (!isCallerDelegate(callerPackage, DELEGATION_CERT_INSTALL)) { + if (!isCallerDelegate(callerPackage, mInjector.binderGetCallingUid(), + DELEGATION_CERT_INSTALL)) { mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null); } } else { @@ -5364,7 +5363,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (UserHandle.getUserId(callerUid) != mOwners.getDeviceOwnerUserId()) { throw new SecurityException("Caller not from device owner user"); } - if (!isCallerDelegate(callerPackage, DELEGATION_CERT_INSTALL)) { + if (!isCallerDelegate(callerPackage, mInjector.binderGetCallingUid(), + DELEGATION_CERT_INSTALL)) { throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid() + "has no permission to generate keys."); } @@ -5766,15 +5766,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * @param scope the delegation scope to be checked. * @return {@code true} if the calling process is a delegate of {@code scope}. */ - private boolean isCallerDelegate(String callerPackage, String scope) { + private boolean isCallerDelegate(String callerPackage, int callerUid, String scope) { Preconditions.checkNotNull(callerPackage, "callerPackage is null"); if (!Arrays.asList(DELEGATIONS).contains(scope)) { throw new IllegalArgumentException("Unexpected delegation scope: " + scope); } // Retrieve the UID and user ID of the calling process. - final int callingUid = mInjector.binderGetCallingUid(); - final int userId = UserHandle.getUserId(callingUid); + final int userId = UserHandle.getUserId(callerUid); synchronized (getLockObject()) { // Retrieve user policy data. final DevicePolicyData policy = getUserData(userId); @@ -5787,7 +5786,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final int uid = mInjector.getPackageManager() .getPackageUidAsUser(callerPackage, userId); // Return true if the caller is actually callerPackage. - return uid == callingUid; + return uid == callerUid; } catch (NameNotFoundException e) { // Ignore. } @@ -5818,7 +5817,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { getActiveAdminForCallerLocked(who, reqPolicy); } // If no ComponentName is given ensure calling process has scope delegation. - } else if (!isCallerDelegate(callerPackage, scope)) { + } else if (!isCallerDelegate(callerPackage, mInjector.binderGetCallingUid(), scope)) { throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid() + " is not a delegate of scope " + scope + "."); } @@ -7783,6 +7782,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + public ComponentName getProfileOwnerAsUser(int userHandle) { + enforceCrossUsersPermission(userHandle); + + return getProfileOwner(userHandle); + } + + @Override public ComponentName getProfileOwner(int userHandle) { if (!mHasFeature) { return null; @@ -7825,6 +7831,68 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return getApplicationLabel(profileOwner.getPackageName(), userHandle); } + @Override + public boolean checkDeviceIdentifierAccess(String packageName, int pid, int uid) { + // If the caller is not a system app then it should only be able to check its own device + // identifier access. + int callingUid = mInjector.binderGetCallingUid(); + int callingPid = mInjector.binderGetCallingPid(); + if (UserHandle.getAppId(callingUid) >= Process.FIRST_APPLICATION_UID + && (callingUid != uid || callingPid != pid)) { + String message = String.format( + "Calling uid %d, pid %d cannot check device identifier access for package %s " + + "(uid=%d, pid=%d)", callingUid, callingPid, packageName, uid, pid); + Log.w(LOG_TAG, message); + throw new SecurityException(message); + } + // Verify that the specified packages matches the provided uid. + int userId = UserHandle.getUserId(uid); + try { + ApplicationInfo appInfo = mIPackageManager.getApplicationInfo(packageName, 0, userId); + // Since this call goes directly to PackageManagerService a NameNotFoundException is not + // thrown but null data can be returned; if the appInfo for the specified package cannot + // be found then return false to prevent crashing the app. + if (appInfo == null) { + Log.w(LOG_TAG, + String.format("appInfo could not be found for package %s", packageName)); + return false; + } else if (uid != appInfo.uid) { + String message = String.format("Package %s (uid=%d) does not match provided uid %d", + packageName, appInfo.uid, uid); + Log.w(LOG_TAG, message); + throw new SecurityException(message); + } + } catch (RemoteException e) { + // If an exception is caught obtaining the appInfo just return false to prevent crashing + // apps due to an internal error. + Log.e(LOG_TAG, "Exception caught obtaining appInfo for package " + packageName, e); + return false; + } + // A device or profile owner must also have the READ_PHONE_STATE permission to access device + // identifiers. If the package being checked does not have this permission then deny access. + if (mContext.checkPermission(android.Manifest.permission.READ_PHONE_STATE, pid, uid) + != PackageManager.PERMISSION_GRANTED) { + return false; + } + + // Allow access to the device owner or delegate cert installer. + ComponentName deviceOwner = getDeviceOwnerComponent(true); + if (deviceOwner != null && (deviceOwner.getPackageName().equals(packageName) + || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) { + return true; + } + // Allow access to the profile owner for the specified user, or delegate cert installer + ComponentName profileOwner = getProfileOwnerAsUser(userId); + if (profileOwner != null && (profileOwner.getPackageName().equals(packageName) + || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) { + return true; + } + + Log.w(LOG_TAG, String.format("Package %s (uid=%d, pid=%d) cannot access Device IDs", + packageName, uid, pid)); + return false; + } + /** * Canonical name for a given package. */ @@ -8266,7 +8334,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean isCallerApplicationRestrictionsManagingPackage(String callerPackage) { - return isCallerDelegate(callerPackage, DELEGATION_APP_RESTRICTIONS); + return isCallerDelegate(callerPackage, mInjector.binderGetCallingUid(), + DELEGATION_APP_RESTRICTIONS); } @Override diff --git a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java index 73d49dd16eaf..2d66427dd599 100644 --- a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java @@ -19,17 +19,28 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.Manifest; import android.app.AppOpsManager; +import android.app.admin.DevicePolicyManager; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.os.Binder; +import android.os.Build; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.telephony.Rlog; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.util.Log; +import android.util.StatsLog; import com.android.internal.annotations.VisibleForTesting; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import java.util.function.Supplier; /** Utility class for Telephony permission enforcement. */ @@ -41,6 +52,20 @@ public final class TelephonyPermissions { private static final Supplier<ITelephony> TELEPHONY_SUPPLIER = () -> ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE)); + /** + * Whether to disable the new device identifier access restrictions. + */ + private static final String PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED = + "device_identifier_access_restrictions_disabled"; + + // Contains a mapping of packages that did not meet the new requirements to access device + // identifiers and the methods they were attempting to invoke; used to prevent duplicate + // reporting of packages / methods. + private static final Map<String, Set<String>> sReportedDeviceIDPackages; + static { + sReportedDeviceIDPackages = new HashMap<>(); + } + private TelephonyPermissions() {} /** @@ -112,6 +137,19 @@ public final class TelephonyPermissions { context, TELEPHONY_SUPPLIER, subId, pid, uid, callingPackage, message); } + /** + * Check whether the calling packages has carrier privileges for the passing subscription. + * @return {@code true} if the caller has carrier privileges, {@false} otherwise. + */ + public static boolean checkCarrierPrivilegeForSubId(int subId) { + if (SubscriptionManager.isValidSubscriptionId(subId) + && getCarrierPrivilegeStatus(TELEPHONY_SUPPLIER, subId, Binder.getCallingUid()) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { + return true; + } + return false; + } + @VisibleForTesting public static boolean checkReadPhoneState( Context context, Supplier<ITelephony> telephonySupplier, int subId, int pid, int uid, @@ -179,18 +217,9 @@ public final class TelephonyPermissions { context.enforcePermission( android.Manifest.permission.READ_PHONE_STATE, pid, uid, message); } catch (SecurityException phoneStateException) { - SubscriptionManager sm = (SubscriptionManager) context.getSystemService( - Context.TELEPHONY_SUBSCRIPTION_SERVICE); - int[] activeSubIds = sm.getActiveSubscriptionIdList(); - for (int activeSubId : activeSubIds) { - // If we don't have the runtime permission, but do have carrier privileges, that - // suffices for reading phone state. - if (getCarrierPrivilegeStatus(telephonySupplier, activeSubId, uid) - == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { - return true; - } - } - return false; + // If we don't have the runtime permission, but do have carrier privileges, that + // suffices for reading phone state. + return checkCarrierPrivilegeForAnySubId(context, telephonySupplier, uid); } } @@ -202,6 +231,182 @@ public final class TelephonyPermissions { } /** + * Check whether the caller (or self, if not processing an IPC) can read device identifiers. + * + * <p>This method behaves in one of the following ways: + * <ul> + * <li>return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the calling + * package passes a DevicePolicyManager Device Owner / Profile Owner device identifier + * access check, or the calling package has carrier privileges. + * <li>throw SecurityException: if the caller does not meet any of the requirements and is + * targeting Q or is targeting pre-Q and does not have the READ_PHONE_STATE permission. + * <li>return false: if the caller is targeting pre-Q and does have the READ_PHONE_STATE + * permission. In this case the caller would expect to have access to the device + * identifiers so false is returned instead of throwing a SecurityException to indicate + * the calling function should return dummy data. + * </ul> + */ + public static boolean checkCallingOrSelfReadDeviceIdentifiers(Context context, + String callingPackage, String message) { + return checkCallingOrSelfReadDeviceIdentifiers(context, + SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage, message); + } + + /** + * Check whether the caller (or self, if not processing an IPC) can read device identifiers. + * + * <p>This method behaves in one of the following ways: + * <ul> + * <li>return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the calling + * package passes a DevicePolicyManager Device Owner / Profile Owner device identifier + * access check, or the calling package has carrier privileges. + * <li>throw SecurityException: if the caller does not meet any of the requirements and is + * targeting Q or is targeting pre-Q and does not have the READ_PHONE_STATE permission + * or carrier privileges. + * <li>return false: if the caller is targeting pre-Q and does have the READ_PHONE_STATE + * permission or carrier privileges. In this case the caller would expect to have access + * to the device identifiers so false is returned instead of throwing a SecurityException + * to indicate the calling function should return dummy data. + * </ul> + */ + public static boolean checkCallingOrSelfReadDeviceIdentifiers(Context context, int subId, + String callingPackage, String message) { + return checkReadDeviceIdentifiers(context, TELEPHONY_SUPPLIER, subId, + Binder.getCallingPid(), Binder.getCallingUid(), callingPackage, message); + } + + /** + * Check whether the caller (or self, if not processing an IPC) can read subscriber identifiers. + * + * <p>This method behaves in one of the following ways: + * <ul> + * <li>return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the calling + * package passes a DevicePolicyManager Device Owner / Profile Owner device identifier + * access check, or the calling package has carrier privileges. + * <li>throw SecurityException: if the caller does not meet any of the requirements and is + * targeting Q or is targeting pre-Q and does not have the READ_PHONE_STATE permission. + * <li>return false: if the caller is targeting pre-Q and does have the READ_PHONE_STATE + * permission. In this case the caller would expect to have access to the device + * identifiers so false is returned instead of throwing a SecurityException to indicate + * the calling function should return dummy data. + * </ul> + */ + public static boolean checkCallingOrSelfReadSubscriberIdentifiers(Context context, int subId, + String callingPackage, String message) { + return checkReadDeviceIdentifiers(context, TELEPHONY_SUPPLIER, subId, + Binder.getCallingPid(), Binder.getCallingUid(), callingPackage, message); + } + + /** + * Checks whether the app with the given pid/uid can read device identifiers. + * + * @returns true if the caller has the READ_PRIVILEGED_PHONE_STATE permission or the calling + * package passes a DevicePolicyManager Device Owner / Profile Owner device identifier access + * check. + */ + @VisibleForTesting + public static boolean checkReadDeviceIdentifiers(Context context, + Supplier<ITelephony> telephonySupplier, int subId, int pid, int uid, + String callingPackage, String message) { + // Allow system and root access to the device identifiers. + final int appId = UserHandle.getAppId(uid); + if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID) { + return true; + } + // Allow access to packages that have the READ_PRIVILEGED_PHONE_STATE permission. + if (context.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pid, + uid) == PackageManager.PERMISSION_GRANTED) { + return true; + } + // If the calling package has carrier privileges for any subscription then allow access. + if (checkCarrierPrivilegeForAnySubId(context, telephonySupplier, uid)) { + return true; + } + // if the calling package is not null then perform the DevicePolicyManager device / + // profile owner and Appop checks. + if (callingPackage != null) { + // Allow access to a device / profile owner app. + DevicePolicyManager devicePolicyManager = + (DevicePolicyManager) context.getSystemService( + Context.DEVICE_POLICY_SERVICE); + if (devicePolicyManager != null && devicePolicyManager.checkDeviceIdentifierAccess( + callingPackage, pid, uid)) { + return true; + } + } + return reportAccessDeniedToReadIdentifiers(context, subId, pid, uid, callingPackage, + message); + } + + /** + * Reports a failure when the app with the given pid/uid cannot access the requested identifier. + * + * @returns false if the caller is targeting pre-Q and does have the READ_PHONE_STATE + * permission or carrier privileges. + * @throws SecurityException if the caller does not meet any of the requirements for the + * requested identifier and is targeting Q or is targeting pre-Q + * and does not have the READ_PHONE_STATE permission or carrier + * privileges. + */ + private static boolean reportAccessDeniedToReadIdentifiers(Context context, int subId, int pid, + int uid, String callingPackage, String message) { + boolean isPreinstalled = false; + boolean isPrivApp = false; + ApplicationInfo callingPackageInfo = null; + try { + callingPackageInfo = context.getPackageManager().getApplicationInfoAsUser( + callingPackage, 0, UserHandle.getUserId(uid)); + if (callingPackageInfo != null) { + if (callingPackageInfo.isSystemApp()) { + isPreinstalled = true; + if (callingPackageInfo.isPrivilegedApp()) { + isPrivApp = true; + } + } + } + } catch (PackageManager.NameNotFoundException e) { + // If the application info for the calling package could not be found then assume the + // calling app is a non-preinstalled app to detect any issues with the check + Log.e(LOG_TAG, "Exception caught obtaining package info for package " + callingPackage, + e); + } + // The current package should only be reported in StatsLog if it has not previously been + // reported for the currently invoked device identifier method. + boolean packageReported = sReportedDeviceIDPackages.containsKey(callingPackage); + if (!packageReported || !sReportedDeviceIDPackages.get(callingPackage).contains( + message)) { + Set invokedMethods; + if (!packageReported) { + invokedMethods = new HashSet<String>(); + sReportedDeviceIDPackages.put(callingPackage, invokedMethods); + } else { + invokedMethods = sReportedDeviceIDPackages.get(callingPackage); + } + invokedMethods.add(message); + StatsLog.write(StatsLog.DEVICE_IDENTIFIER_ACCESS_DENIED, callingPackage, message, + isPreinstalled, isPrivApp); + } + Log.w(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message + + ":isPreinstalled=" + isPreinstalled + ":isPrivApp=" + isPrivApp); + // if the target SDK is pre-Q then check if the calling package would have previously + // had access to device identifiers. + if (callingPackageInfo != null && ( + callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q)) { + if (context.checkPermission( + android.Manifest.permission.READ_PHONE_STATE, + pid, + uid) == PackageManager.PERMISSION_GRANTED) { + return false; + } + if (checkCarrierPrivilegeForSubId(subId)) { + return false; + } + } + throw new SecurityException(message + ": The user " + uid + + " does not meet the requirements to access device identifiers."); + } + + /** * Check whether the app with the given pid/uid can read the call log. * @return {@code true} if the specified app has the read call log permission and AppOpp granted * to it, {@code false} otherwise. @@ -383,6 +588,26 @@ public final class TelephonyPermissions { } } + /** + * Returns whether the provided uid has carrier privileges for any active subscription ID. + */ + private static boolean checkCarrierPrivilegeForAnySubId(Context context, + Supplier<ITelephony> telephonySupplier, int uid) { + SubscriptionManager sm = (SubscriptionManager) context.getSystemService( + Context.TELEPHONY_SUBSCRIPTION_SERVICE); + int[] activeSubIds = sm.getActiveSubscriptionIdList(); + if (activeSubIds != null) { + for (int activeSubId : activeSubIds) { + if (getCarrierPrivilegeStatus(telephonySupplier, activeSubId, uid) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { + return true; + } + } + } + return false; + } + + private static int getCarrierPrivilegeStatus( Supplier<ITelephony> telephonySupplier, int subId, int uid) { ITelephony telephony = telephonySupplier.get(); |