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") // This method enforces the permission 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 = 100 "Need " + permission + " permission for " + currentSource + ": " + message; 101 if (result == PERMISSION_HARD_DENIED) { 102 throw new SecurityException(msg); 103 } 104 Log.w(TAG, msg); 105 return false; 106 } 107 108 /** 109 * Return an empty string if the current call is allowed to toggle bluetooth state 110 * 111 * <p>Return the error description if this caller is not allowed to toggle Bluetooth 112 */ 113 @RequiresPermission(BLUETOOTH_CONNECT) callerCanToggle( Context ctx, AttributionSource source, UserManager userManager, AppOpsManager appOpsManager, PermissionManager permissionManager, String message, boolean requireForeground)114 String callerCanToggle( 115 Context ctx, 116 AttributionSource source, 117 UserManager userManager, 118 AppOpsManager appOpsManager, 119 PermissionManager permissionManager, 120 String message, 121 boolean requireForeground) { 122 if (isBluetoothDisallowed(userManager)) { 123 return "Bluetooth is not allowed"; 124 } 125 126 if (!checkBluetoothPermissions( 127 ctx, 128 source, 129 userManager, 130 appOpsManager, 131 permissionManager, 132 message, 133 requireForeground)) { 134 return "Missing Bluetooth permission"; 135 } 136 137 if (requireForeground && !checkCompatChangeRestriction(source, ctx)) { 138 return "Caller does not match restriction criteria"; 139 } 140 141 return ""; 142 } 143 144 @RequiresPermission(BLUETOOTH_PRIVILEGED) enforcePrivileged(Context ctx)145 static void enforcePrivileged(Context ctx) { 146 ctx.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); 147 } 148 getCallingAppId()149 static int getCallingAppId() { 150 return UserHandle.getAppId(Binder.getCallingUid()); 151 } 152 isCallerSystem(int callingAppId)153 static boolean isCallerSystem(int callingAppId) { 154 return callingAppId == Process.SYSTEM_UID; 155 } 156 isCallerNfc(int callingAppId)157 static boolean isCallerNfc(int callingAppId) { 158 return callingAppId == Process.NFC_UID; 159 } 160 isCallerShell(int callingAppId)161 private static boolean isCallerShell(int callingAppId) { 162 return callingAppId == Process.SHELL_UID; 163 } 164 isCallerRoot(int callingAppId)165 private static boolean isCallerRoot(int callingAppId) { 166 return callingAppId == Process.ROOT_UID; 167 } 168 isCallerSystemUi(int callingAppId)169 private boolean isCallerSystemUi(int callingAppId) { 170 return callingAppId == mSystemUiUid; 171 } 172 173 @SuppressLint("AndroidFrameworkRequiresPermission") // Permission is not enforced, only checked isPrivileged(Context ctx, int pid, int uid)174 private static boolean isPrivileged(Context ctx, int pid, int uid) { 175 return (ctx.checkPermission(BLUETOOTH_PRIVILEGED, pid, uid) == PERMISSION_GRANTED) 176 || (ctx.getPackageManager().checkSignatures(uid, SYSTEM_UID) == SIGNATURE_MATCH); 177 } 178 isProfileOwner(Context ctx, int uid, String packageName)179 private static boolean isProfileOwner(Context ctx, int uid, String packageName) { 180 Context userContext; 181 try { 182 userContext = 183 ctx.createPackageContextAsUser( 184 ctx.getPackageName(), 0, UserHandle.getUserHandleForUid(uid)); 185 } catch (PackageManager.NameNotFoundException e) { 186 Log.e(TAG, "Unknown package name"); 187 return false; 188 } 189 if (userContext == null) { 190 Log.e(TAG, "Unable to retrieve user context for " + uid); 191 return false; 192 } 193 DevicePolicyManager devicePolicyManager = 194 userContext.getSystemService(DevicePolicyManager.class); 195 if (devicePolicyManager == null) { 196 Log.w(TAG, "isProfileOwner: Error retrieving DevicePolicyManager service"); 197 return false; 198 } 199 return devicePolicyManager.isProfileOwnerApp(packageName); 200 } 201 isDeviceOwner(Context ctx, int uid, String packageName)202 private static boolean isDeviceOwner(Context ctx, int uid, String packageName) { 203 if (packageName == null) { 204 Log.e(TAG, "isDeviceOwner: packageName is null, returning false"); 205 return false; 206 } 207 208 DevicePolicyManager devicePolicyManager = ctx.getSystemService(DevicePolicyManager.class); 209 if (devicePolicyManager == null) { 210 Log.w(TAG, "isDeviceOwner: Error retrieving DevicePolicyManager service"); 211 return false; 212 } 213 UserHandle deviceOwnerUser = null; 214 ComponentName deviceOwnerComponent = null; 215 final long ident = Binder.clearCallingIdentity(); 216 try { 217 deviceOwnerUser = devicePolicyManager.getDeviceOwnerUser(); 218 deviceOwnerComponent = devicePolicyManager.getDeviceOwnerComponentOnAnyUser(); 219 } finally { 220 Binder.restoreCallingIdentity(ident); 221 } 222 if (deviceOwnerUser == null 223 || deviceOwnerComponent == null 224 || deviceOwnerComponent.getPackageName() == null) { 225 return false; 226 } 227 228 return deviceOwnerUser.equals(UserHandle.getUserHandleForUid(uid)) 229 && deviceOwnerComponent.getPackageName().equals(packageName); 230 } 231 isSystem(Context ctx, String packageName, int uid)232 private static boolean isSystem(Context ctx, String packageName, int uid) { 233 final long ident = Binder.clearCallingIdentity(); 234 try { 235 ApplicationInfo info = 236 ctx.getPackageManager() 237 .getApplicationInfoAsUser( 238 packageName, 0, UserHandle.getUserHandleForUid(uid)); 239 return (info.flags & FLAGS_SYSTEM_APP) != 0; 240 } catch (PackageManager.NameNotFoundException e) { 241 return false; 242 } finally { 243 Binder.restoreCallingIdentity(ident); 244 } 245 } 246 isBluetoothDisallowed(UserManager userManager)247 private static boolean isBluetoothDisallowed(UserManager userManager) { 248 final long callingIdentity = Binder.clearCallingIdentity(); 249 try { 250 return userManager.hasUserRestrictionForUser( 251 UserManager.DISALLOW_BLUETOOTH, UserHandle.SYSTEM); 252 } finally { 253 Binder.restoreCallingIdentity(callingIdentity); 254 } 255 } 256 257 /** 258 * Check ifthe packageName belongs to calling uid 259 * 260 * <p>A null package belongs to any uid 261 */ checkPackage(AppOpsManager appOpsManager, String packageName)262 private static void checkPackage(AppOpsManager appOpsManager, String packageName) { 263 final int callingUid = Binder.getCallingUid(); 264 if (packageName == null) { 265 Log.w(TAG, "checkPackage(): called with null packageName from " + callingUid); 266 return; 267 } 268 269 try { 270 appOpsManager.checkPackage(callingUid, packageName); 271 } catch (SecurityException e) { 272 Log.w(TAG, "checkPackage(): " + packageName + " does not belong to uid " + callingUid); 273 throw new SecurityException(e.getMessage()); 274 } 275 } 276 checkIfCallerIsForegroundUser(UserManager userManager)277 boolean checkIfCallerIsForegroundUser(UserManager userManager) { 278 final int callingUid = Binder.getCallingUid(); 279 final UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid); 280 final UserHandle foregroundUser; 281 final UserHandle parentUser; 282 final long callingIdentity = Binder.clearCallingIdentity(); 283 try { 284 // `getCurrentUser` need to be call by system server because it require one of 285 // INTERACT_ACROSS_USERS | INTERACT_ACROSS_USERS_FULL 286 foregroundUser = UserHandle.of(ActivityManager.getCurrentUser()); 287 // `getProfileParent` need to be call by system server because it require one of 288 // MANAGE_USERS | INTERACT_ACROSS_USER and 289 parentUser = userManager.getProfileParent(callingUser); 290 } finally { 291 Binder.restoreCallingIdentity(callingIdentity); 292 } 293 final int callingAppId = UserHandle.getAppId(callingUid); 294 295 // TODO(b/280890575): Remove isCallerX() to only check isForegroundUser 296 297 final boolean valid = 298 Objects.equals(callingUser, foregroundUser) 299 || Objects.equals(parentUser, foregroundUser) 300 || isCallerNfc(callingAppId) 301 || isCallerSystemUi(callingAppId) 302 || isCallerShell(callingAppId); 303 304 if (!valid) { 305 Log.d( 306 TAG, 307 "checkIfCallerIsForegroundUser: REJECTED:" 308 + " callingUser=" 309 + callingUser 310 + " parentUser=" 311 + parentUser 312 + " foregroundUser=" 313 + foregroundUser 314 + " callingAppId=" 315 + callingAppId); 316 } 317 return valid; 318 } 319 320 @RequiresPermission(BLUETOOTH_CONNECT) checkBluetoothPermissions( Context ctx, AttributionSource source, UserManager userManager, AppOpsManager appOpsManager, PermissionManager permissionManager, String message, boolean requireForeground)321 private boolean checkBluetoothPermissions( 322 Context ctx, 323 AttributionSource source, 324 UserManager userManager, 325 AppOpsManager appOpsManager, 326 PermissionManager permissionManager, 327 String message, 328 boolean requireForeground) { 329 final int callingAppId = getCallingAppId(); 330 if (isCallerSystem(callingAppId)) return true; 331 if (isCallerShell(callingAppId)) return true; 332 if (isCallerRoot(callingAppId)) return true; 333 checkPackage(appOpsManager, source.getPackageName()); 334 335 if (requireForeground && !checkIfCallerIsForegroundUser(userManager)) { 336 Log.w(TAG, "Not allowed for non-active and non system user"); 337 return false; 338 } 339 340 if (!checkConnectPermissionForDataDelivery(ctx, permissionManager, source, message)) { 341 return false; 342 } 343 return true; 344 } 345 346 /** Starting from T, enable/disable APIs are limited to system apps or device owners. */ checkCompatChangeRestriction(AttributionSource source, Context ctx)347 private static boolean checkCompatChangeRestriction(AttributionSource source, Context ctx) { 348 final String packageName = source.getPackageName(); 349 350 final int callingUid = Binder.getCallingUid(); 351 final int callingPid = Binder.getCallingPid(); 352 if (CompatChanges.isChangeEnabled(RESTRICT_ENABLE_DISABLE, callingUid) 353 && !isPrivileged(ctx, callingPid, callingUid) 354 && !isSystem(ctx, packageName, callingUid) 355 && !isDeviceOwner(ctx, callingUid, packageName) 356 && !isProfileOwner(ctx, callingUid, packageName)) { 357 Log.e(TAG, "Caller is not one of: privileged | system | deviceOwner | profileOwner"); 358 return false; 359 } 360 return true; 361 } 362 } 363