• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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