1 /* 2 * Copyright 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.bluetooth; 18 19 import static android.Manifest.permission.BLUETOOTH_CONNECT; 20 import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; 21 import static android.content.pm.PackageManager.SIGNATURE_MATCH; 22 import static android.os.Process.SYSTEM_UID; 23 import static android.permission.PermissionManager.PERMISSION_GRANTED; 24 import static android.permission.PermissionManager.PERMISSION_HARD_DENIED; 25 26 import static com.android.server.bluetooth.ChangeIds.RESTRICT_ENABLE_DISABLE; 27 28 import android.annotation.RequiresPermission; 29 import android.annotation.SuppressLint; 30 import android.app.ActivityManager; 31 import android.app.AppOpsManager; 32 import android.app.admin.DevicePolicyManager; 33 import android.app.compat.CompatChanges; 34 import android.content.AttributionSource; 35 import android.content.ComponentName; 36 import android.content.Context; 37 import android.content.pm.ApplicationInfo; 38 import android.content.pm.PackageManager; 39 import android.os.Binder; 40 import android.os.Process; 41 import android.os.UserHandle; 42 import android.os.UserManager; 43 import android.permission.PermissionManager; 44 45 import java.util.Objects; 46 47 class BtPermissionUtils { 48 private static final String TAG = BtPermissionUtils.class.getSimpleName(); 49 50 private static final int FLAGS_SYSTEM_APP = 51 ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; 52 53 private final int mSystemUiUid; 54 BtPermissionUtils(Context ctx)55 BtPermissionUtils(Context ctx) { 56 // Check if device is configured with no home screen, which implies no SystemUI. 57 int systemUiUid = -1; 58 try { 59 systemUiUid = 60 ctx.createContextAsUser(UserHandle.SYSTEM, 0) 61 .getPackageManager() 62 .getPackageUid( 63 "com.android.systemui", 64 PackageManager.PackageInfoFlags.of( 65 PackageManager.MATCH_SYSTEM_ONLY)); 66 Log.d(TAG, "Detected SystemUiUid: " + systemUiUid); 67 } catch (PackageManager.NameNotFoundException e) { 68 Log.w(TAG, "Unable to resolve SystemUI's UID."); 69 } 70 mSystemUiUid = systemUiUid; 71 } 72 73 /** 74 * Returns true if the BLUETOOTH_CONNECT permission is granted for the calling app. Returns 75 * false if the result is a soft denial. Throws SecurityException if the result is a hard 76 * denial. 77 * 78 * <p>Should be used in situations where the app op should not be noted. 79 */ 80 @SuppressLint("AndroidFrameworkRequiresPermission") 81 @RequiresPermission(BLUETOOTH_CONNECT) checkConnectPermissionForDataDelivery( Context ctx, PermissionManager permissionManager, AttributionSource source, String message)82 static boolean checkConnectPermissionForDataDelivery( 83 Context ctx, 84 PermissionManager permissionManager, 85 AttributionSource source, 86 String message) { 87 final String permission = BLUETOOTH_CONNECT; 88 AttributionSource currentSource = 89 new AttributionSource.Builder(ctx.getAttributionSource()) 90 .setNext(Objects.requireNonNull(source)) 91 .build(); 92 final int result = 93 permissionManager.checkPermissionForDataDeliveryFromDataSource( 94 permission, currentSource, message); 95 if (result == PERMISSION_GRANTED) { 96 return true; 97 } 98 99 final String msg = "Need " + permission + " permission for " + source + ": " + message; 100 if (result == PERMISSION_HARD_DENIED) { 101 throw new SecurityException(msg); 102 } 103 Log.w(TAG, msg); 104 return false; 105 } 106 107 /** 108 * Return an empty string if the current call is allowed to toggle bluetooth state 109 * 110 * <p>Return the error description if this caller is not allowed to toggle Bluetooth 111 */ callerCanToggle( Context ctx, AttributionSource source, UserManager userManager, AppOpsManager appOpsManager, PermissionManager permissionManager, String message, boolean requireForeground)112 String callerCanToggle( 113 Context ctx, 114 AttributionSource source, 115 UserManager userManager, 116 AppOpsManager appOpsManager, 117 PermissionManager permissionManager, 118 String message, 119 boolean requireForeground) { 120 if (isBluetoothDisallowed(userManager)) { 121 return "Bluetooth is not allowed"; 122 } 123 124 if (!checkBluetoothPermissions( 125 ctx, 126 source, 127 userManager, 128 appOpsManager, 129 permissionManager, 130 message, 131 requireForeground)) { 132 return "Missing Bluetooth permission"; 133 } 134 135 if (requireForeground && !checkCompatChangeRestriction(source, ctx)) { 136 return "Caller does not match restriction criteria"; 137 } 138 139 return ""; 140 } 141 enforcePrivileged(Context ctx)142 static void enforcePrivileged(Context ctx) { 143 ctx.enforceCallingOrSelfPermission( 144 BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission"); 145 } 146 getCallingAppId()147 static int getCallingAppId() { 148 return UserHandle.getAppId(Binder.getCallingUid()); 149 } 150 isCallerSystem(int callingAppId)151 static boolean isCallerSystem(int callingAppId) { 152 return callingAppId == Process.SYSTEM_UID; 153 } 154 isCallerNfc(int callingAppId)155 static boolean isCallerNfc(int callingAppId) { 156 return callingAppId == Process.NFC_UID; 157 } 158 isCallerShell(int callingAppId)159 private static boolean isCallerShell(int callingAppId) { 160 return callingAppId == Process.SHELL_UID; 161 } 162 isCallerRoot(int callingAppId)163 private static boolean isCallerRoot(int callingAppId) { 164 return callingAppId == Process.ROOT_UID; 165 } 166 isCallerSystemUi(int callingAppId)167 private boolean isCallerSystemUi(int callingAppId) { 168 return callingAppId == mSystemUiUid; 169 } 170 isPrivileged(Context ctx, int pid, int uid)171 private static boolean isPrivileged(Context ctx, int pid, int uid) { 172 return (ctx.checkPermission(BLUETOOTH_PRIVILEGED, pid, uid) == PERMISSION_GRANTED) 173 || (ctx.getPackageManager().checkSignatures(uid, SYSTEM_UID) == SIGNATURE_MATCH); 174 } 175 isProfileOwner(Context ctx, int uid, String packageName)176 private static boolean isProfileOwner(Context ctx, int uid, String packageName) { 177 Context userContext; 178 try { 179 userContext = 180 ctx.createPackageContextAsUser( 181 ctx.getPackageName(), 0, UserHandle.getUserHandleForUid(uid)); 182 } catch (PackageManager.NameNotFoundException e) { 183 Log.e(TAG, "Unknown package name"); 184 return false; 185 } 186 if (userContext == null) { 187 Log.e(TAG, "Unable to retrieve user context for " + uid); 188 return false; 189 } 190 DevicePolicyManager devicePolicyManager = 191 userContext.getSystemService(DevicePolicyManager.class); 192 if (devicePolicyManager == null) { 193 Log.w(TAG, "isProfileOwner: Error retrieving DevicePolicyManager service"); 194 return false; 195 } 196 return devicePolicyManager.isProfileOwnerApp(packageName); 197 } 198 isDeviceOwner(Context ctx, int uid, String packageName)199 private static boolean isDeviceOwner(Context ctx, int uid, String packageName) { 200 if (packageName == null) { 201 Log.e(TAG, "isDeviceOwner: packageName is null, returning false"); 202 return false; 203 } 204 205 DevicePolicyManager devicePolicyManager = ctx.getSystemService(DevicePolicyManager.class); 206 if (devicePolicyManager == null) { 207 Log.w(TAG, "isDeviceOwner: Error retrieving DevicePolicyManager service"); 208 return false; 209 } 210 UserHandle deviceOwnerUser = null; 211 ComponentName deviceOwnerComponent = null; 212 long ident = Binder.clearCallingIdentity(); 213 try { 214 deviceOwnerUser = devicePolicyManager.getDeviceOwnerUser(); 215 deviceOwnerComponent = devicePolicyManager.getDeviceOwnerComponentOnAnyUser(); 216 } finally { 217 Binder.restoreCallingIdentity(ident); 218 } 219 if (deviceOwnerUser == null 220 || deviceOwnerComponent == null 221 || deviceOwnerComponent.getPackageName() == null) { 222 return false; 223 } 224 225 return deviceOwnerUser.equals(UserHandle.getUserHandleForUid(uid)) 226 && deviceOwnerComponent.getPackageName().equals(packageName); 227 } 228 isSystem(Context ctx, String packageName, int uid)229 private static boolean isSystem(Context ctx, String packageName, int uid) { 230 long ident = Binder.clearCallingIdentity(); 231 try { 232 ApplicationInfo info = 233 ctx.getPackageManager() 234 .getApplicationInfoAsUser( 235 packageName, 0, UserHandle.getUserHandleForUid(uid)); 236 return (info.flags & FLAGS_SYSTEM_APP) != 0; 237 } catch (PackageManager.NameNotFoundException e) { 238 return false; 239 } finally { 240 Binder.restoreCallingIdentity(ident); 241 } 242 } 243 isBluetoothDisallowed(UserManager userManager)244 private static boolean isBluetoothDisallowed(UserManager userManager) { 245 final long callingIdentity = Binder.clearCallingIdentity(); 246 try { 247 return userManager.hasUserRestrictionForUser( 248 UserManager.DISALLOW_BLUETOOTH, UserHandle.SYSTEM); 249 } finally { 250 Binder.restoreCallingIdentity(callingIdentity); 251 } 252 } 253 254 /** 255 * Check ifthe packageName belongs to calling uid 256 * 257 * <p>A null package belongs to any uid 258 */ checkPackage(AppOpsManager appOpsManager, String packageName)259 private static void checkPackage(AppOpsManager appOpsManager, String packageName) { 260 final int callingUid = Binder.getCallingUid(); 261 if (packageName == null) { 262 Log.w(TAG, "checkPackage(): called with null packageName from " + callingUid); 263 return; 264 } 265 266 try { 267 appOpsManager.checkPackage(callingUid, packageName); 268 } catch (SecurityException e) { 269 Log.w(TAG, "checkPackage(): " + packageName + " does not belong to uid " + callingUid); 270 throw new SecurityException(e.getMessage()); 271 } 272 } 273 checkIfCallerIsForegroundUser(UserManager userManager)274 boolean checkIfCallerIsForegroundUser(UserManager userManager) { 275 final int callingUid = Binder.getCallingUid(); 276 final UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid); 277 final UserHandle foregroundUser; 278 final UserHandle parentUser; 279 final long callingIdentity = Binder.clearCallingIdentity(); 280 try { 281 // `getCurrentUser` need to be call by system server because it require one of 282 // INTERACT_ACROSS_USERS | INTERACT_ACROSS_USERS_FULL 283 foregroundUser = UserHandle.of(ActivityManager.getCurrentUser()); 284 // `getProfileParent` need to be call by system server because it require one of 285 // MANAGE_USERS | INTERACT_ACROSS_USER and 286 parentUser = userManager.getProfileParent(callingUser); 287 } finally { 288 Binder.restoreCallingIdentity(callingIdentity); 289 } 290 final int callingAppId = UserHandle.getAppId(callingUid); 291 292 // TODO(b/280890575): Remove isCallerX() to only check isForegroundUser 293 294 final boolean valid = 295 Objects.equals(callingUser, foregroundUser) 296 || Objects.equals(parentUser, foregroundUser) 297 || isCallerNfc(callingAppId) 298 || isCallerSystemUi(callingAppId) 299 || isCallerShell(callingAppId); 300 301 if (!valid) { 302 Log.d( 303 TAG, 304 "checkIfCallerIsForegroundUser: REJECTED:" 305 + " callingUser=" 306 + callingUser 307 + " parentUser=" 308 + parentUser 309 + " foregroundUser=" 310 + foregroundUser 311 + " callingAppId=" 312 + callingAppId); 313 } 314 return valid; 315 } 316 317 @RequiresPermission(BLUETOOTH_CONNECT) checkBluetoothPermissions( Context ctx, AttributionSource source, UserManager userManager, AppOpsManager appOpsManager, PermissionManager permissionManager, String message, boolean requireForeground)318 private boolean checkBluetoothPermissions( 319 Context ctx, 320 AttributionSource source, 321 UserManager userManager, 322 AppOpsManager appOpsManager, 323 PermissionManager permissionManager, 324 String message, 325 boolean requireForeground) { 326 final int callingAppId = getCallingAppId(); 327 if (isCallerSystem(callingAppId)) return true; 328 if (isCallerShell(callingAppId)) return true; 329 if (isCallerRoot(callingAppId)) return true; 330 checkPackage(appOpsManager, source.getPackageName()); 331 332 if (requireForeground && !checkIfCallerIsForegroundUser(userManager)) { 333 Log.w(TAG, "Not allowed for non-active and non system user"); 334 return false; 335 } 336 337 if (!checkConnectPermissionForDataDelivery(ctx, permissionManager, source, message)) { 338 return false; 339 } 340 return true; 341 } 342 343 /** Starting from T, enable/disable APIs are limited to system apps or device owners. */ checkCompatChangeRestriction(AttributionSource source, Context ctx)344 private static boolean checkCompatChangeRestriction(AttributionSource source, Context ctx) { 345 final String packageName = source.getPackageName(); 346 347 final int callingUid = Binder.getCallingUid(); 348 final int callingPid = Binder.getCallingPid(); 349 if (CompatChanges.isChangeEnabled(RESTRICT_ENABLE_DISABLE, callingUid) 350 && !isPrivileged(ctx, callingPid, callingUid) 351 && !isSystem(ctx, packageName, callingUid) 352 && !isDeviceOwner(ctx, callingUid, packageName) 353 && !isProfileOwner(ctx, callingUid, packageName)) { 354 Log.e(TAG, "Caller is not one of: privileged | system | deviceOwner | profileOwner"); 355 return false; 356 } 357 return true; 358 } 359 } 360