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