/* * Copyright (C) 2016 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 com.android.server.wifi.util; import static android.Manifest.permission.ACCESS_COARSE_LOCATION; import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.Manifest.permission.ENTER_CAR_MODE_PRIORITIZED; import static android.Manifest.permission.NEARBY_WIFI_DEVICES; import static android.Manifest.permission.RENOUNCE_PERMISSIONS; import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION; import static android.content.pm.PackageManager.GET_PERMISSIONS; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; import android.app.admin.DevicePolicyManager; import android.app.admin.WifiSsidPolicy; import android.content.AttributionSource; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.location.LocationManager; import android.net.NetworkStack; import android.net.wifi.SecurityParams; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiSsid; import android.os.Binder; import android.os.Build; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.permission.PermissionManager; import android.provider.Settings; import android.util.ArraySet; import android.util.EventLog; import android.util.Log; import android.util.Pair; import android.util.SparseBooleanArray; import androidx.annotation.RequiresApi; import com.android.internal.annotations.GuardedBy; import com.android.modules.utils.build.SdkLevel; import com.android.server.wifi.FrameworkFacade; import com.android.server.wifi.WifiInjector; import com.android.server.wifi.WifiLog; import com.android.wifi.resources.R; import java.util.Arrays; import java.util.Set; /** * A wifi permissions utility assessing permissions * for getting scan results by a package. */ public class WifiPermissionsUtil { private static final String TAG = "WifiPermissionsUtil"; private static final int APP_INFO_FLAGS_SYSTEM_APP = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; private final WifiPermissionsWrapper mWifiPermissionsWrapper; private final Context mContext; private final FrameworkFacade mFrameworkFacade; private final AppOpsManager mAppOps; private final UserManager mUserManager; private final PermissionManager mPermissionManager; private final Object mLock = new Object(); @GuardedBy("mLock") private LocationManager mLocationManager; private WifiLog mLog; private boolean mVerboseLoggingEnabled; private final SparseBooleanArray mOemPrivilegedAdminUidCache = new SparseBooleanArray(); public WifiPermissionsUtil(WifiPermissionsWrapper wifiPermissionsWrapper, Context context, UserManager userManager, WifiInjector wifiInjector) { mWifiPermissionsWrapper = wifiPermissionsWrapper; mContext = context; mFrameworkFacade = wifiInjector.getFrameworkFacade(); mUserManager = userManager; mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); mPermissionManager = mContext.getSystemService(PermissionManager.class); mLog = wifiInjector.makeLog(TAG); } /** * A class to store binder caller information. */ public static final class CallerIdentity { int mUid; int mPid; String mPackageName; String mFeatureId; public CallerIdentity(int uid, int pid, String packageName, String featureId) { mUid = uid; mPid = pid; mPackageName = packageName; mFeatureId = featureId; } public int getUid() { return mUid; } public int getPid() { return mPid; } public String getPackageName() { return mPackageName; } public String getFeatureId() { return mFeatureId; } @NonNull @Override public String toString() { return "CallerIdentity{" + "Uid= " + mUid + ", Pid= " + mPid + ", PackageName= " + mPackageName + ", FeatureId= " + mFeatureId + '}'; } } /** * Checks if the app has the permission to override Wi-Fi network configuration or not. * * @param uid uid of the app. * @return true if the app does have the permission, false otherwise. */ public boolean checkConfigOverridePermission(int uid) { return mWifiPermissionsWrapper.getOverrideWifiConfigPermission(uid) == PackageManager.PERMISSION_GRANTED; } /** * Check and enforce Coarse or Fine Location permission (depending on target SDK). * * @param pkgName PackageName of the application requesting access * @param featureId The feature in the package * @param uid The uid of the package */ public void enforceLocationPermission(String pkgName, @Nullable String featureId, int uid) { if (!checkCallersLocationPermission(pkgName, featureId, uid, /* coarseForTargetSdkLessThanQ */ true, null)) { throw new SecurityException( "UID " + uid + " does not have Coarse/Fine Location permission"); } } /** * Version of enforceNearbyDevicesPermission that do not throw an exception. */ public boolean checkNearbyDevicesPermission(AttributionSource attributionSource, boolean checkForLocation, String message) { try { enforceNearbyDevicesPermission(attributionSource, checkForLocation, message); } catch (SecurityException e) { Log.e(TAG, "checkNearbyDevicesPermission - " + e); return false; } return true; } /** * Check and enforce NEARBY_WIFI_DEVICES permission and optionally enforce for either location * disavowal or location permission. * * Note, this is only callable on SDK version T and later. * * @param attributionSource AttributionSource of the caller. * @param checkForLocation If true will require the caller to either disavow location * or actually have location permission. * @param message String to log as the reason for performing permission checks. */ public void enforceNearbyDevicesPermission(AttributionSource attributionSource, boolean checkForLocation, String message) throws SecurityException { if (!SdkLevel.isAtLeastT()) { Log.wtf(TAG, "enforceNearbyDevicesPermission should never be called on pre-T " + "devices"); throw new SecurityException("enforceNearbyDevicesPermission requires at least " + "Android T"); } if (attributionSource == null) { throw new SecurityException("enforceNearbyDevicesPermission attributionSource is null"); } if (mVerboseLoggingEnabled) { Log.v(TAG, "enforceNearbyDevicesPermission(attributionSource=" + attributionSource + ", checkForLocation=" + checkForLocation); } if (!attributionSource.checkCallingUid()) { throw new SecurityException("enforceNearbyDevicesPermission invalid attribution source=" + attributionSource); } String packageName = attributionSource.getPackageName(); int uid = attributionSource.getUid(); checkPackage(uid, packageName); // Apps with NETWORK_SETTINGS, NETWORK_SETUP_WIZARD, NETWORK_MANAGED_PROVISIONING, // NETWORK_STACK & MAINLINE_NETWORK_STACK, RADIO_SCAN_WITHOUT_LOCATION are granted a bypass. if (checkNetworkSettingsPermission(uid) || checkNetworkSetupWizardPermission(uid) || checkNetworkManagedProvisioningPermission(uid) || checkNetworkStackPermission(uid) || checkMainlineNetworkStackPermission(uid) || checkScanWithoutLocationPermission(uid)) { return; } int permissionCheckResult = mPermissionManager.checkPermissionForDataDelivery( Manifest.permission.NEARBY_WIFI_DEVICES, attributionSource, message); if (permissionCheckResult != PermissionManager.PERMISSION_GRANTED) { throw new SecurityException("package=" + packageName + " UID=" + uid + " does not have nearby devices permission."); } if (mVerboseLoggingEnabled) { Log.v(TAG, "pkg=" + packageName + " has NEARBY_WIFI_DEVICES permission."); } if (!checkForLocation) { // No need to check for location permission. All done now and return. return; } // There are 2 ways to disavow location. Skip location permission check if any of the // 2 ways are used to disavow location usage. // First check if the app renounced location. // Check every step along the attribution chain for a renouncement. AttributionSource currentAttrib = attributionSource; while (true) { int curUid = currentAttrib.getUid(); String curPackageName = currentAttrib.getPackageName(); // If location has been renounced anywhere in the chain we treat it as a disavowal. if (currentAttrib.getRenouncedPermissions().contains(ACCESS_FINE_LOCATION) && mWifiPermissionsWrapper.getUidPermission(RENOUNCE_PERMISSIONS, curUid) == PackageManager.PERMISSION_GRANTED) { if (mVerboseLoggingEnabled) { Log.v(TAG, "package=" + curPackageName + " UID=" + curUid + " has renounced location permission - bypassing location check."); } return; } AttributionSource nextAttrib = currentAttrib.getNext(); if (nextAttrib == null) { break; } currentAttrib = nextAttrib; } // If the app did not renounce location, check if "neverForLocation" is set. PackageManager pm = mContext.getPackageManager(); long ident = Binder.clearCallingIdentity(); try { PackageInfo pkgInfo = pm.getPackageInfo(packageName, GET_PERMISSIONS | MATCH_UNINSTALLED_PACKAGES); int requestedPermissionsLength = pkgInfo.requestedPermissions == null || pkgInfo.requestedPermissionsFlags == null ? 0 : pkgInfo.requestedPermissions.length; if (requestedPermissionsLength == 0) { Log.e(TAG, "package=" + packageName + " unexpectedly has null " + "requestedPermissions or requestPermissionFlags."); } for (int i = 0; i < requestedPermissionsLength; i++) { if (pkgInfo.requestedPermissions[i].equals(NEARBY_WIFI_DEVICES) && (pkgInfo.requestedPermissionsFlags[i] & PackageInfo.REQUESTED_PERMISSION_NEVER_FOR_LOCATION) != 0) { if (mVerboseLoggingEnabled) { Log.v(TAG, "package=" + packageName + " UID=" + uid + " has declared neverForLocation - bypassing location check."); } return; } } } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Could not find package for disavowal check: " + packageName); } finally { Binder.restoreCallingIdentity(ident); } // App did not disavow location. Check for location permission and location mode. ident = Binder.clearCallingIdentity(); try { if (!isLocationModeEnabled()) { if (mVerboseLoggingEnabled) { Log.v(TAG, "enforceNearbyDevicesPermission(pkg=" + packageName + ", uid=" + uid + "): " + "location is disabled"); } throw new SecurityException("Location mode is disabled for the device"); } } finally { Binder.restoreCallingIdentity(ident); } if (mPermissionManager.checkPermissionForDataDelivery( ACCESS_FINE_LOCATION, attributionSource, message) == PermissionManager.PERMISSION_GRANTED) { if (mVerboseLoggingEnabled) { Log.v(TAG, "package=" + packageName + " UID=" + uid + " has location permission."); } return; } throw new SecurityException("package=" + packageName + ", UID=" + uid + " does not have Fine Location permission"); } /** * Checks whether than the target SDK of the package is less than the specified version code. */ public boolean isTargetSdkLessThan(String packageName, int versionCode, int callingUid) { long ident = Binder.clearCallingIdentity(); try { final int targetSdkVersion; if (SdkLevel.isAtLeastS()) { // >= S, use the lightweight API to just get the target SDK version. Context userContext = createPackageContextAsUser(callingUid); if (userContext == null) return false; targetSdkVersion = userContext.getPackageManager().getTargetSdkVersion(packageName); } else { // < S, use the heavyweight API to get all package info. targetSdkVersion = mContext.getPackageManager().getApplicationInfoAsUser( packageName, 0, UserHandle.getUserHandleForUid(callingUid)).targetSdkVersion; } return targetSdkVersion < versionCode; } catch (PackageManager.NameNotFoundException e) { // In case of exception, assume unknown app (more strict checking) // Note: This case will never happen since checkPackage is // called to verify validity before checking App's version. return false; } finally { Binder.restoreCallingIdentity(ident); } } /** * Returns the global demo mode of the device. Note that there is a * UserManager.isDeviceInDemoMode(Context) which does the same thing - but is not a * public/system API (whereas the Settings.Global.DEVICE_DEMO_MODE is a System API). */ public boolean isDeviceInDemoMode(Context context) { return Settings.Global.getInt(context.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 0) > 0; } /** * Check and enforce Location permission in the manifest. * * @param uid uid of the app. * @param isCoarseOnly whether permission type is COARSE or FINE since FINE permission * implies having COARSE permission. */ public void enforceLocationPermissionInManifest(int uid, boolean isCoarseOnly) { if (!checkCallersLocationPermissionInManifest(uid, isCoarseOnly)) { throw new SecurityException("UID " + uid + " does not have Location permission (" + "isCoarseOnly = " + isCoarseOnly + " )"); } } /** * Checks if the app has the location permission in the manifest. * * @param uid uid of the app. * @param isCoarseOnly whether permission type is COARSE or FINE since FINE permission * implies having COARSE permission. * @return true if the app does have the permission, false otherwise. */ public boolean checkCallersLocationPermissionInManifest(int uid, boolean isCoarseOnly) { // Having FINE permission implies having COARSE permission (but not the reverse) String permissionType = isCoarseOnly ? ACCESS_COARSE_LOCATION : ACCESS_FINE_LOCATION; return mWifiPermissionsWrapper.getUidPermission(permissionType, uid) == PackageManager.PERMISSION_GRANTED; } /** * Checks that calling process has android.Manifest.permission.ACCESS_FINE_LOCATION or * android.Manifest.permission.ACCESS_FINE_LOCATION (depending on config/targetSDK leve) * and a corresponding app op is allowed for this package and uid. * * @param pkgName PackageName of the application requesting access * @param featureId The feature in the package * @param uid The uid of the package * @param coarseForTargetSdkLessThanQ If true and the targetSDK < Q then will check for COARSE * else (false or targetSDK >= Q) then will check for FINE * @param message A message describing why the permission was checked. Only needed if this is * not inside of a two-way binder call from the data receiver */ public boolean checkCallersLocationPermission(String pkgName, @Nullable String featureId, int uid, boolean coarseForTargetSdkLessThanQ, @Nullable String message) { boolean isTargetSdkLessThanQ = isTargetSdkLessThan(pkgName, Build.VERSION_CODES.Q, uid); String permissionType = ACCESS_FINE_LOCATION; if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) { // Having FINE permission implies having COARSE permission (but not the reverse) permissionType = ACCESS_COARSE_LOCATION; } if (mWifiPermissionsWrapper.getUidPermission(permissionType, uid) == PackageManager.PERMISSION_DENIED) { if (mVerboseLoggingEnabled) { Log.v(TAG, "checkCallersLocationPermission(" + pkgName + "): uid " + uid + " doesn't have permission " + permissionType); } return false; } // Always checking FINE - even if will not enforce. This will record the request for FINE // so that a location request by the app is surfaced to the user. boolean isFineLocationAllowed = noteAppOpAllowed( AppOpsManager.OPSTR_FINE_LOCATION, pkgName, featureId, uid, message); if (isFineLocationAllowed) { if (mVerboseLoggingEnabled) { Log.v(TAG, "checkCallersLocationPermission(" + pkgName + "): ok because uid " + uid + " has app-op " + AppOpsManager.OPSTR_FINE_LOCATION); } return true; } if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) { boolean allowed = noteAppOpAllowed(AppOpsManager.OPSTR_COARSE_LOCATION, pkgName, featureId, uid, message); if (mVerboseLoggingEnabled) { Log.v(TAG, "checkCallersLocationPermission(" + pkgName + "): returning " + allowed + " because uid " + uid + (allowed ? "has" : "doesn't have") + " app-op " + AppOpsManager.OPSTR_COARSE_LOCATION); } return allowed; } if (mVerboseLoggingEnabled) { Log.v(TAG, "checkCallersLocationPermission(" + pkgName + "): returning false for " + uid + ": coarseForTargetSdkLessThanQ=" + coarseForTargetSdkLessThanQ + ", isTargetSdkLessThanQ=" + isTargetSdkLessThanQ); } return false; } /** * Check and enforce Fine Location permission. * * @param pkgName PackageName of the application requesting access * @param featureId The feature in the package * @param uid The uid of the package */ public void enforceFineLocationPermission(String pkgName, @Nullable String featureId, int uid) { if (!checkCallersFineLocationPermission(pkgName, featureId, uid, false, false)) { throw new SecurityException("UID " + uid + " does not have Fine Location permission"); } } /** * Checks that calling process has android.Manifest.permission.ACCESS_FINE_LOCATION * and a corresponding app op is allowed for this package and uid. * * @param pkgName PackageName of the application requesting access * @param featureId The feature in the package * @param uid The uid of the package * @param hideFromAppOps True to invoke {@link AppOpsManager#checkOp(int, int, String)}, false * to invoke {@link AppOpsManager#noteOp(String, int, String, String, * String)}. * @param ignoreLocationSettings Whether this request can bypass location settings. */ private boolean checkCallersFineLocationPermission(String pkgName, @Nullable String featureId, int uid, boolean hideFromAppOps, boolean ignoreLocationSettings) { // Having FINE permission implies having COARSE permission (but not the reverse) if (mWifiPermissionsWrapper.getUidPermission( ACCESS_FINE_LOCATION, uid) == PackageManager.PERMISSION_DENIED) { return false; } boolean isAllowed; if (hideFromAppOps) { // Don't note the operation, just check if the app is allowed to perform the operation. isAllowed = checkAppOpAllowed(AppOpsManager.OPSTR_FINE_LOCATION, pkgName, uid); } else { isAllowed = noteAppOpAllowed(AppOpsManager.OPSTR_FINE_LOCATION, pkgName, featureId, uid, null); } // If the ignoreLocationSettings is true, we always return true. This is for the emergency // location service use case. But still notify the operation manager. return isAllowed || ignoreLocationSettings; } /** * Check and enforce Coarse Location permission. * * @param pkgName PackageName of the application requesting access. * @param featureId The feature in the package. * @param uid The uid of the package. */ public void enforceCoarseLocationPermission(String pkgName, @Nullable String featureId, int uid) { if (!checkCallersCoarseLocationPermission(pkgName, featureId, uid, null)) { throw new SecurityException( "UID " + uid + " does not have Coarse Location permission"); } } /** * Checks that calling process has android.Manifest.permission.ACCESS_COARSE_LOCATION * and a corresponding app op is allowed for this package and uid. * * @param pkgName PackageName of the application requesting access. * @param featureId The feature in the package. * @param uid The uid of the package. * @param message A message describing why the permission was checked. Only needed if this is * not inside of a two-way binder call from the data receiver. */ public boolean checkCallersCoarseLocationPermission(String pkgName, @Nullable String featureId, int uid, @Nullable String message) { if (mWifiPermissionsWrapper.getUidPermission(ACCESS_COARSE_LOCATION, uid) == PackageManager.PERMISSION_DENIED) { if (mVerboseLoggingEnabled) { Log.v(TAG, "checkCallersCoarseLocationPermission(" + pkgName + "): uid " + uid + " doesn't have ACCESS_COARSE_LOCATION permission "); } return false; } boolean allowed = noteAppOpAllowed(AppOpsManager.OPSTR_COARSE_LOCATION, pkgName, featureId, uid, message); if (mVerboseLoggingEnabled) { Log.v(TAG, "checkCallersCoarseLocationPermission(" + pkgName + "): returning " + allowed + " because uid " + uid + (allowed ? "has" : "doesn't have") + " app-op " + AppOpsManager.OPSTR_COARSE_LOCATION); } return allowed; } /** * Checks that calling process has android.Manifest.permission.LOCATION_HARDWARE. * * @param uid The uid of the package */ public boolean checkCallersHardwareLocationPermission(int uid) { return mWifiPermissionsWrapper.getUidPermission(Manifest.permission.LOCATION_HARDWARE, uid) == PackageManager.PERMISSION_GRANTED; } /** * API to determine if the caller has permissions to get scan results. Throws SecurityException * if the caller has no permission. * @param pkgName package name of the application requesting access * @param featureId The feature in the package * @param uid The uid of the package * @param message A message describing why the permission was checked. Only needed if this is * not inside of a two-way binder call from the data receiver */ public void enforceCanAccessScanResults(String pkgName, @Nullable String featureId, int uid, @Nullable String message) throws SecurityException { checkPackage(uid, pkgName); // Apps with NETWORK_SETTINGS, NETWORK_SETUP_WIZARD, NETWORK_MANAGED_PROVISIONING, // NETWORK_STACK & MAINLINE_NETWORK_STACK, RADIO_SCAN_WITHOUT_LOCATION are granted a bypass. if (checkNetworkSettingsPermission(uid) || checkNetworkSetupWizardPermission(uid) || checkNetworkManagedProvisioningPermission(uid) || checkNetworkStackPermission(uid) || checkMainlineNetworkStackPermission(uid) || checkScanWithoutLocationPermission(uid)) { return; } // Location mode must be enabled if (!isLocationModeEnabled()) { if (mVerboseLoggingEnabled) { Log.v(TAG, "enforceCanAccessScanResults(pkg=" + pkgName + ", uid=" + uid + "): " + "location is disabled"); } // Location mode is disabled, scan results cannot be returned throw new SecurityException("Location mode is disabled for the device"); } // Check if the calling Uid has CAN_READ_PEER_MAC_ADDRESS permission. boolean canCallingUidAccessLocation = checkCallerHasPeersMacAddressPermission(uid); // LocationAccess by App: caller must have Coarse/Fine Location permission to have access to // location information. boolean canAppPackageUseLocation = checkCallersLocationPermission(pkgName, featureId, uid, /* coarseForTargetSdkLessThanQ */ true, message); // If neither caller or app has location access, there is no need to check // any other permissions. Deny access to scan results. if (!canCallingUidAccessLocation && !canAppPackageUseLocation) { if (mVerboseLoggingEnabled) { Log.v(TAG, "enforceCanAccessScanResults(pkg=" + pkgName + ", uid=" + uid + "): " + "canCallingUidAccessLocation=" + canCallingUidAccessLocation + ", canAppPackageUseLocation=" + canAppPackageUseLocation); } throw new SecurityException("UID " + uid + " has no location permission"); } // Check if Wifi Scan request is an operation allowed for this App. if (!isScanAllowedbyApps(pkgName, featureId, uid)) { if (mVerboseLoggingEnabled) { Log.v(TAG, "enforceCanAccessScanResults(pkg=" + pkgName + ", uid=" + uid + "): " + "doesn't have app-op " + AppOpsManager.OPSTR_WIFI_SCAN); } throw new SecurityException("UID " + uid + " has no wifi scan permission"); } // If the User or profile is current, permission is granted // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. boolean isCurrentProfile = doesUidBelongToUser( uid, mWifiPermissionsWrapper.getCurrentUser()); if (!isCurrentProfile && !checkInteractAcrossUsersFull(uid)) { if (mVerboseLoggingEnabled) { Log.v(TAG, "enforceCanAccessScanResults(pkg=" + pkgName + ", uid=" + uid + "): " + "isCurrentProfile=" + isCurrentProfile + ", checkInteractAcrossUsersFull=" + checkInteractAcrossUsersFull(uid)); } throw new SecurityException("UID " + uid + " profile not permitted"); } } /** * API to determine if the caller has permissions to get scan results. Throws SecurityException * if the caller has no permission. * @param pkgName package name of the application requesting access * @param featureId The feature in the package * @param uid The uid of the package * @param ignoreLocationSettings Whether this request can bypass location settings. * @param hideFromAppOps Whether to note the request in app-ops logging or not. * * Note: This is to be used for checking permissions in the internal WifiScanner API surface * for requests coming from system apps. */ public void enforceCanAccessScanResultsForWifiScanner(String pkgName, @Nullable String featureId, int uid, boolean ignoreLocationSettings, boolean hideFromAppOps) throws SecurityException { checkPackage(uid, pkgName); // Apps with NETWORK_SETTINGS or NETWORK_SETUP_WIZARD get a bypass if (checkNetworkSettingsPermission(uid) || checkNetworkSetupWizardPermission(uid)) { return; } // Location mode must be enabled if (!isLocationModeEnabled()) { if (ignoreLocationSettings) { mLog.w("Request from " + pkgName + " violated location settings"); } else { // Location mode is disabled, scan results cannot be returned throw new SecurityException("Location mode is disabled for the device"); } } // LocationAccess by App: caller must have fine & hardware Location permission to have // access to location information. if (!checkCallersFineLocationPermission(pkgName, featureId, uid, hideFromAppOps, ignoreLocationSettings) || !checkCallersHardwareLocationPermission(uid)) { throw new SecurityException("UID " + uid + " has no location permission"); } // Check if Wifi Scan request is an operation allowed for this App. if (!isScanAllowedbyApps(pkgName, featureId, uid)) { throw new SecurityException("UID " + uid + " has no wifi scan permission"); } } /** * * Checks that calling process has android.Manifest.permission.ACCESS_FINE_LOCATION * and a corresponding app op is allowed for this package and uid * * @param pkgName package name of the application requesting access * @param featureId The feature in the package * @param uid The uid of the package * @param needLocationModeEnabled indicates location mode must be enabled. * * @return true if caller has permission, false otherwise */ public boolean checkCanAccessWifiDirect(String pkgName, @Nullable String featureId, int uid, boolean needLocationModeEnabled) { try { checkPackage(uid, pkgName); } catch (SecurityException se) { Log.e(TAG, "Package check exception - " + se); return false; } // Apps with NETWORK_SETTINGS are granted a bypass. if (checkNetworkSettingsPermission(uid)) { return true; } // Location mode must be enabled if needed. if (needLocationModeEnabled && !isLocationModeEnabled()) { Log.e(TAG, "Location mode is disabled for the device"); return false; } // LocationAccess by App: caller must have Fine Location permission to have access to // location information. if (!checkCallersLocationPermission(pkgName, featureId, uid, /* coarseForTargetSdkLessThanQ */ false, null)) { Log.e(TAG, "UID " + uid + " has no location permission"); return false; } return true; } /** * API to validate if a package name belongs to a UID. Throws SecurityException * if pkgName does not belongs to a UID * * @param pkgName package name of the application requesting access * @param uid The uid of the package * */ public void checkPackage(int uid, String pkgName) throws SecurityException { if (pkgName == null) { throw new SecurityException("Checking UID " + uid + " but Package Name is Null"); } mAppOps.checkPackage(uid, pkgName); } /** * Returns true if the caller holds PEERS_MAC_ADDRESS permission. */ private boolean checkCallerHasPeersMacAddressPermission(int uid) { return mWifiPermissionsWrapper.getUidPermission( android.Manifest.permission.PEERS_MAC_ADDRESS, uid) == PackageManager.PERMISSION_GRANTED; } /** * Returns true if Wifi scan operation is allowed for this caller * and package. */ private boolean isScanAllowedbyApps(String pkgName, @Nullable String featureId, int uid) { return noteAppOpAllowed(AppOpsManager.OPSTR_WIFI_SCAN, pkgName, featureId, uid, null); } /** * Returns true if the caller holds INTERACT_ACROSS_USERS_FULL. */ private boolean checkInteractAcrossUsersFull(int uid) { return mWifiPermissionsWrapper.getUidPermission( android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, uid) == PackageManager.PERMISSION_GRANTED; } private boolean noteAppOpAllowed(String op, String pkgName, @Nullable String featureId, int uid, @Nullable String message) { return mAppOps.noteOpNoThrow(op, uid, pkgName, featureId, message) == AppOpsManager.MODE_ALLOWED; } private boolean checkAppOpAllowed(String op, String pkgName, int uid) { return mAppOps.unsafeCheckOp(op, uid, pkgName) == AppOpsManager.MODE_ALLOWED; } private boolean retrieveLocationManagerIfNecessary() { // This is going to be accessed by multiple threads. synchronized (mLock) { if (mLocationManager == null) { mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); } } return mLocationManager != null; } /** * Retrieves a handle to LocationManager (if not already done) and check if location is enabled. */ public boolean isLocationModeEnabled() { if (!retrieveLocationManagerIfNecessary()) return false; try { return mLocationManager.isLocationEnabledForUser(UserHandle.of( mWifiPermissionsWrapper.getCurrentUser())); } catch (Exception e) { Log.e(TAG, "Failure to get location mode via API, falling back to settings", e); return mFrameworkFacade.getIntegerSetting( mContext, Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF) == Settings.Secure.LOCATION_MODE_ON; } } /** * Returns true if the |uid| holds REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION permission. */ public boolean checkRequestCompanionProfileAutomotiveProjectionPermission(int uid) { return mWifiPermissionsWrapper.getUidPermission( REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION, uid) == PackageManager.PERMISSION_GRANTED; } /** * Returns true if the |uid| holds ENTER_CAR_MODE_PRIORITIZED permission. */ public boolean checkEnterCarModePrioritized(int uid) { return mWifiPermissionsWrapper.getUidPermission(ENTER_CAR_MODE_PRIORITIZED, uid) == PackageManager.PERMISSION_GRANTED; } /** * Returns true if the |uid| holds MANAGE_WIFI_INTERFACES permission. */ public boolean checkManageWifiInterfacesPermission(int uid) { return mWifiPermissionsWrapper.getUidPermission( android.Manifest.permission.MANAGE_WIFI_INTERFACES, uid) == PackageManager.PERMISSION_GRANTED; } /** * Returns true if the |uid| holds MANAGE_WIFI_NETWORK_SELECTION permission. */ public boolean checkManageWifiNetworkSelectionPermission(int uid) { return mWifiPermissionsWrapper.getUidPermission( android.Manifest.permission.MANAGE_WIFI_NETWORK_SELECTION, uid) == PackageManager.PERMISSION_GRANTED; } /** * Returns true if the |uid| holds NETWORK_SETTINGS permission. */ public boolean checkNetworkSettingsPermission(int uid) { return mWifiPermissionsWrapper.getUidPermission( android.Manifest.permission.NETWORK_SETTINGS, uid) == PackageManager.PERMISSION_GRANTED; } /** * Returns true if the |uid| holds RADIO_SCAN_WITHOUT_LOCATION permission. */ public boolean checkScanWithoutLocationPermission(int uid) { return mWifiPermissionsWrapper.getUidPermission( android.Manifest.permission.RADIO_SCAN_WITHOUT_LOCATION, uid) == PackageManager.PERMISSION_GRANTED; } /** * Returns true if the |uid| holds LOCAL_MAC_ADDRESS permission. */ public boolean checkLocalMacAddressPermission(int uid) { return mWifiPermissionsWrapper.getUidPermission( android.Manifest.permission.LOCAL_MAC_ADDRESS, uid) == PackageManager.PERMISSION_GRANTED; } /** * Returns true if the |uid| holds NETWORK_SETUP_WIZARD permission. */ public boolean checkNetworkSetupWizardPermission(int uid) { return mWifiPermissionsWrapper.getUidPermission( android.Manifest.permission.NETWORK_SETUP_WIZARD, uid) == PackageManager.PERMISSION_GRANTED; } /** * Returns true if the |uid| holds NETWORK_STACK permission. */ public boolean checkNetworkStackPermission(int uid) { return mWifiPermissionsWrapper.getUidPermission( android.Manifest.permission.NETWORK_STACK, uid) == PackageManager.PERMISSION_GRANTED; } /** * Returns true if the |uid| holds MAINLINE_NETWORK_STACK permission. */ public boolean checkMainlineNetworkStackPermission(int uid) { return mWifiPermissionsWrapper.getUidPermission( NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, uid) == PackageManager.PERMISSION_GRANTED; } /** * Returns true if the |uid| holds NETWORK_MANAGED_PROVISIONING permission. */ public boolean checkNetworkManagedProvisioningPermission(int uid) { return mWifiPermissionsWrapper.getUidPermission( android.Manifest.permission.NETWORK_MANAGED_PROVISIONING, uid) == PackageManager.PERMISSION_GRANTED; } /** * Returns true if the |uid| holds NETWORK_CARRIER_PROVISIONING permission. */ public boolean checkNetworkCarrierProvisioningPermission(int uid) { return mWifiPermissionsWrapper.getUidPermission( android.Manifest.permission.NETWORK_CARRIER_PROVISIONING, uid) == PackageManager.PERMISSION_GRANTED; } /** * Returns true if the |uid| holds READ_WIFI_CREDENTIAL permission. */ public boolean checkReadWifiCredentialPermission(int uid) { return mWifiPermissionsWrapper.getUidPermission( android.Manifest.permission.READ_WIFI_CREDENTIAL, uid) == PackageManager.PERMISSION_GRANTED; } /** * Returns true if the |uid| holds CAMERA permission. */ public boolean checkCameraPermission(int uid) { return mWifiPermissionsWrapper.getUidPermission( android.Manifest.permission.CAMERA, uid) == PackageManager.PERMISSION_GRANTED; } /** * Returns the DevicePolicyManager from context */ public static DevicePolicyManager retrieveDevicePolicyManagerFromContext(Context context) { DevicePolicyManager devicePolicyManager = context.getSystemService(DevicePolicyManager.class); if (devicePolicyManager == null && context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_DEVICE_ADMIN)) { Log.w(TAG, "Error retrieving DPM service"); } return devicePolicyManager; } @Nullable private Context createPackageContextAsUser(int uid) { Context userContext = null; try { userContext = mContext.createPackageContextAsUser(mContext.getPackageName(), 0, UserHandle.getUserHandleForUid(uid)); } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Unknown package name"); return null; } if (userContext == null) { Log.e(TAG, "Unable to retrieve user context for " + uid); return null; } return userContext; } private DevicePolicyManager retrieveDevicePolicyManagerFromUserContext(int uid) { long ident = Binder.clearCallingIdentity(); try { Context userContext = createPackageContextAsUser(uid); if (userContext == null) return null; return retrieveDevicePolicyManagerFromContext(userContext); } finally { Binder.restoreCallingIdentity(ident); } } @Nullable private Pair getDeviceOwner() { DevicePolicyManager devicePolicyManager = retrieveDevicePolicyManagerFromContext(mContext); if (devicePolicyManager == null) return null; long ident = Binder.clearCallingIdentity(); UserHandle deviceOwnerUser = null; ComponentName deviceOwnerComponent = null; try { deviceOwnerUser = devicePolicyManager.getDeviceOwnerUser(); deviceOwnerComponent = devicePolicyManager.getDeviceOwnerComponentOnAnyUser(); } finally { Binder.restoreCallingIdentity(ident); } if (deviceOwnerUser == null || deviceOwnerComponent == null) return null; if (deviceOwnerComponent.getPackageName() == null) { // shouldn't happen Log.wtf(TAG, "no package name on device owner component: " + deviceOwnerComponent); return null; } return new Pair<>(deviceOwnerUser, deviceOwnerComponent); } /** * Returns {@code true} if the calling {@code uid} and {@code packageName} is the device owner. */ public boolean isDeviceOwner(int uid, @Nullable String packageName) { // Cannot determine if the app is DO/PO if packageName is null. So, will return false to be // safe. if (packageName == null) { Log.e(TAG, "isDeviceOwner: packageName is null, returning false"); return false; } Pair deviceOwner = getDeviceOwner(); if (mVerboseLoggingEnabled) Log.v(TAG, "deviceOwner:" + deviceOwner); // no device owner if (deviceOwner == null) return false; return deviceOwner.first.equals(UserHandle.getUserHandleForUid(uid)) && deviceOwner.second.getPackageName().equals(packageName); } /** * Returns {@code true} if the calling {@code uid} is the device owner. */ public boolean isDeviceOwner(int uid) { Pair deviceOwner = getDeviceOwner(); // no device owner if (deviceOwner == null) return false; // device owner belowngs to wrong user if (!deviceOwner.first.equals(UserHandle.getUserHandleForUid(uid))) return false; // finally, check uid String deviceOwnerPackageName = deviceOwner.second.getPackageName(); String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); if (mVerboseLoggingEnabled) { Log.v(TAG, "Packages for uid " + uid + ":" + Arrays.toString(packageNames)); } if (packageNames == null) { Log.w(TAG, "isDeviceOwner(): could not find packages for packageName=" + deviceOwnerPackageName + " uid=" + uid); return false; } for (String packageName : packageNames) { if (deviceOwnerPackageName.equals(packageName)) return true; } return false; } /** * Returns {@code true} if the calling {@code uid} is the OEM privileged admin. * * The admin must be allowlisted in the wifi overlay and signed with system cert. */ @RequiresApi(Build.VERSION_CODES.TIRAMISU) public boolean isOemPrivilegedAdmin(int uid) { synchronized (mOemPrivilegedAdminUidCache) { int cacheIdx = mOemPrivilegedAdminUidCache.indexOfKey(uid); if (cacheIdx >= 0) { return mOemPrivilegedAdminUidCache.valueAt(cacheIdx); } } boolean result = isOemPrivilegedAdminNoCache(uid); synchronized (mOemPrivilegedAdminUidCache) { mOemPrivilegedAdminUidCache.put(uid, result); } return result; } /** * Returns {@code true} if the calling {@code uid} is the OEM privileged admin. * * This method doesn't memoize results, use {@code isOemPrivilegedAdmin} instead. */ @RequiresApi(Build.VERSION_CODES.TIRAMISU) private boolean isOemPrivilegedAdminNoCache(int uid) { Set oemPrivilegedAdmins = new ArraySet<>(mContext.getResources() .getStringArray(R.array.config_oemPrivilegedWifiAdminPackages)); PackageManager pm = mContext.getPackageManager(); String[] packages = pm.getPackagesForUid(uid); if (packages == null || Arrays.stream(packages).noneMatch(oemPrivilegedAdmins::contains)) { return false; } return pm.checkSignatures(uid, Process.SYSTEM_UID) == PackageManager.SIGNATURE_MATCH; } /** * Returns true if the |callingUid|/|callingPackage| is the profile owner. */ public boolean isProfileOwner(int uid, @Nullable String packageName) { // Cannot determine if the app is DO/PO if packageName is null. So, will return false to be // safe. if (packageName == null) { Log.e(TAG, "isProfileOwner: packageName is null, returning false"); return false; } DevicePolicyManager devicePolicyManager = retrieveDevicePolicyManagerFromUserContext(uid); if (devicePolicyManager == null) return false; return devicePolicyManager.isProfileOwnerApp(packageName); } /** * Returns {@code true} if the calling {@code uid} is the profile owner of * an organization owned device. */ public boolean isProfileOwnerOfOrganizationOwnedDevice(int uid) { DevicePolicyManager devicePolicyManager = retrieveDevicePolicyManagerFromUserContext(uid); if (devicePolicyManager == null) return false; // this relies on having only one PO on COPE device. if (!devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()) { return false; } String[] packages = mContext.getPackageManager().getPackagesForUid(uid); if (packages == null) { Log.w(TAG, "isProfileOwnerOfOrganizationOwnedDevice(): could not find packages for uid=" + uid); return false; } for (String packageName : packages) { if (devicePolicyManager.isProfileOwnerApp(packageName)) return true; } return false; } /** * Returns {@code true} if the calling {@code uid} and {@code packageName} is the device owner * or the profile owner of an organization owned device. */ public boolean isOrganizationOwnedDeviceAdmin(int uid, @Nullable String packageName) { boolean isDeviceOwner = packageName == null ? isDeviceOwner(uid) : isDeviceOwner(uid, packageName); return isDeviceOwner || isProfileOwnerOfOrganizationOwnedDevice(uid); } /** Helper method to check if the entity initiating the binder call is a system app. */ public boolean isSystem(String packageName, int uid) { long ident = Binder.clearCallingIdentity(); try { ApplicationInfo info = mContext.getPackageManager().getApplicationInfoAsUser( packageName, 0, UserHandle.getUserHandleForUid(uid)); return (info.flags & APP_INFO_FLAGS_SYSTEM_APP) != 0; } catch (PackageManager.NameNotFoundException e) { // In case of exception, assume unknown app (more strict checking) // Note: This case will never happen since checkPackage is // called to verify validity before checking App's version. } finally { Binder.restoreCallingIdentity(ident); } return false; } /** * Checks if the given UID belongs to the current foreground or device owner user. This is * used to prevent apps running in background users from modifying network * configurations. *

* UIDs belonging to system internals (such as SystemUI) are always allowed, * since they always run as {@link UserHandle#USER_SYSTEM}. * * @param uid uid of the app. * @return true if the given UID belongs to the current foreground user, * otherwise false. */ public boolean doesUidBelongToCurrentUserOrDeviceOwner(int uid) { boolean isCurrentProfile = doesUidBelongToUser( uid, mWifiPermissionsWrapper.getCurrentUser()); if (!isCurrentProfile) { // Fix for b/174749461 EventLog.writeEvent(0x534e4554, "174749461", -1, "Non foreground user trying to modify wifi configuration"); } return isCurrentProfile || isDeviceOwner(uid); } /** * Check if the current user is a guest user * @return true if the current user is a guest user, false otherwise. */ public boolean isGuestUser() { UserManager userManager = mContext.createContextAsUser( UserHandle.of(mWifiPermissionsWrapper.getCurrentUser()), 0) .getSystemService(UserManager.class); if (userManager == null) { return true; } return userManager.isGuestUser(); } /** * Checks if the given UID belongs to the given user ID. This is * used to prevent apps running in other users from modifying network configurations belonging * to the given user. *

* UIDs belonging to system internals (such as SystemUI) are always allowed, * since they always run as {@link UserHandle#USER_SYSTEM}. * * @param uid uid to check * @param userId user to check against * @return true if the given UID belongs to the given user. */ public boolean doesUidBelongToUser(int uid, int userId) { if (UserHandle.getAppId(uid) == android.os.Process.SYSTEM_UID // UIDs with the NETWORK_SETTINGS permission are always allowed since they are // acting on behalf of the user. || checkNetworkSettingsPermission(uid)) { return true; } UserHandle uidHandle = UserHandle.getUserHandleForUid(uid); UserHandle userHandle = UserHandle.of(userId); return uidHandle.equals(userHandle) || mUserManager.isSameProfileGroup(uidHandle, userHandle); } /** * Sets the verbose logging level. */ public void enableVerboseLogging(boolean enabled) { mVerboseLoggingEnabled = enabled; } /** * Return the corresponding WifiCallerType enum used for WifiStatsLog metrics logging. */ @RequiresApi(Build.VERSION_CODES.S) public int getWifiCallerType(@NonNull AttributionSource attributionSource) { if (!SdkLevel.isAtLeastS() || attributionSource == null) { return 0; } return getWifiCallerType(attributionSource.getUid(), attributionSource.getPackageName()); } /** * Return the corresponding WifiCallerType enum used for WifiStatsLog metrics logging. */ public int getWifiCallerType(int uid, String packageName) { // TODO: Need to hardcode enum values for now since no atom is actually using this enum. // Once the first atom start using it, replace the hardcoded values with constants generated // in WifiStatsLog if (checkNetworkSettingsPermission(uid) || checkNetworkSetupWizardPermission(uid)) { return 1; // SETTINGS } else if (isAdmin(uid, packageName)) { return 2; // ADMIN } else if (checkEnterCarModePrioritized(uid)) { return 3; // AUTOMOTIVE } else if (mContext.getPackageManager().checkSignatures(uid, Process.SYSTEM_UID) == PackageManager.SIGNATURE_MATCH) { return 4; // SIGNATURE } else if (isSystem(packageName, uid)) { return 5; // SYSTEM } return 6; // OTHERS } /** * Returns true if the |callingUid|/|callingPackage| is an admin. */ public boolean isAdmin(int uid, @Nullable String packageName) { // Cannot determine if the app is an admin if packageName is null. // So, will return false to be safe. if (packageName == null) { Log.e(TAG, "isAdmin: packageName is null, returning false"); return false; } boolean isOemPrivilegedAdmin = (SdkLevel.isAtLeastT()) ? isOemPrivilegedAdmin(uid) : false; return isDeviceOwner(uid, packageName) || isProfileOwner(uid, packageName) || isOemPrivilegedAdmin; } /** * Returns true if a package is a device admin. * Note that device admin is a deprecated concept so this should only be used in very specific * cases which require such checks. */ public boolean isLegacyDeviceAdmin(int uid, String packageName) { if (packageName == null) { Log.e(TAG, "isLegacyDeviceAdmin: packageName is null, returning false"); return false; } DevicePolicyManager devicePolicyManager = retrieveDevicePolicyManagerFromUserContext(uid); if (devicePolicyManager == null) return false; return devicePolicyManager.packageHasActiveAdmins(packageName); } /** * Returns true if the device may not connect to the configuration due to admin restriction */ public boolean isAdminRestrictedNetwork(@Nullable WifiConfiguration config) { if (config == null || !SdkLevel.isAtLeastT()) { return false; } DevicePolicyManager devicePolicyManager = WifiPermissionsUtil.retrieveDevicePolicyManagerFromContext(mContext); if (devicePolicyManager == null) return false; int adminMinimumSecurityLevel = 0; WifiSsidPolicy policy; long ident = Binder.clearCallingIdentity(); try { adminMinimumSecurityLevel = devicePolicyManager.getMinimumRequiredWifiSecurityLevel(); policy = devicePolicyManager.getWifiSsidPolicy(); } finally { Binder.restoreCallingIdentity(ident); } //check minimum security level restriction if (adminMinimumSecurityLevel != 0) { boolean securityRestrictionPassed = false; for (SecurityParams params : config.getSecurityParamsList()) { int securityLevel = WifiInfo.convertSecurityTypeToDpmWifiSecurity( WifiInfo.convertWifiConfigurationSecurityType(params.getSecurityType())); // Skip unknown security type since security level cannot be determined. if (securityLevel == WifiInfo.DPM_SECURITY_TYPE_UNKNOWN) continue; if (adminMinimumSecurityLevel <= securityLevel) { securityRestrictionPassed = true; break; } } if (!securityRestrictionPassed) { return true; } } //check SSID restriction if (policy != null) { //skip SSID restriction check for Osu and Passpoint networks if (config.osu || config.isPasspoint()) return false; int policyType = policy.getPolicyType(); Set ssids = policy.getSsids(); WifiSsid ssid = WifiSsid.fromString(config.SSID); if (policyType == WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST && !ssids.contains(ssid)) { return true; } if (policyType == WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST && ssids.contains(ssid)) { return true; } } return false; } /** * Returns the foreground userId */ public int getCurrentUser() { //set the default to undefined user id (UserHandle.USER_NULL) int user = -10000; long ident = Binder.clearCallingIdentity(); try { user = mWifiPermissionsWrapper.getCurrentUser(); } finally { Binder.restoreCallingIdentity(ident); } return user; } /** Whether the uid is signed with the same key as the platform. */ public boolean isSignedWithPlatformKey(int uid) { return mContext.getPackageManager().checkSignatures(uid, Process.SYSTEM_UID) == PackageManager.SIGNATURE_MATCH; } }