• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.companion;
18 
19 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
20 import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
21 import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED;
22 import static android.app.AppOpsManager.MODE_ALLOWED;
23 import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
24 import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
25 import static android.companion.AssociationRequest.DEVICE_PROFILE_COMPUTER;
26 import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
27 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
28 import static android.os.Binder.getCallingPid;
29 import static android.os.Binder.getCallingUid;
30 import static android.os.Process.SYSTEM_UID;
31 import static android.os.UserHandle.getCallingUserId;
32 
33 import static java.util.Collections.unmodifiableMap;
34 
35 import android.Manifest;
36 import android.annotation.NonNull;
37 import android.annotation.Nullable;
38 import android.annotation.UserIdInt;
39 import android.companion.AssociationInfo;
40 import android.companion.AssociationRequest;
41 import android.companion.CompanionDeviceManager;
42 import android.content.Context;
43 import android.os.RemoteException;
44 import android.os.ServiceManager;
45 import android.util.ArrayMap;
46 
47 import com.android.internal.app.IAppOpsService;
48 
49 import java.util.Map;
50 
51 /**
52  * Utility methods for checking permissions required for accessing {@link CompanionDeviceManager}
53  * APIs (such as {@link Manifest.permission#REQUEST_COMPANION_PROFILE_WATCH},
54  * {@link Manifest.permission#REQUEST_COMPANION_PROFILE_APP_STREAMING},
55  * {@link Manifest.permission#REQUEST_COMPANION_SELF_MANAGED} etc.)
56  */
57 final class PermissionsUtils {
58 
59     private static final Map<String, String> DEVICE_PROFILE_TO_PERMISSION;
60     static {
61         final Map<String, String> map = new ArrayMap<>();
map.put(DEVICE_PROFILE_WATCH, Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH)62         map.put(DEVICE_PROFILE_WATCH, Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH);
map.put(DEVICE_PROFILE_APP_STREAMING, Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING)63         map.put(DEVICE_PROFILE_APP_STREAMING,
64                 Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING);
map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION)65         map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION,
66                 Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION);
map.put(DEVICE_PROFILE_COMPUTER, Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER)67         map.put(DEVICE_PROFILE_COMPUTER, Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER);
68 
69         DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map);
70     }
71 
enforcePermissionsForAssociation(@onNull Context context, @NonNull AssociationRequest request, int packageUid)72     static void enforcePermissionsForAssociation(@NonNull Context context,
73             @NonNull AssociationRequest request, int packageUid) {
74         enforceRequestDeviceProfilePermissions(context, request.getDeviceProfile(), packageUid);
75 
76         if (request.isSelfManaged()) {
77             enforceRequestSelfManagedPermission(context, packageUid);
78         }
79     }
80 
enforceRequestDeviceProfilePermissions( @onNull Context context, @Nullable String deviceProfile, int packageUid)81     static void enforceRequestDeviceProfilePermissions(
82             @NonNull Context context, @Nullable String deviceProfile, int packageUid) {
83         // Device profile can be null.
84         if (deviceProfile == null) return;
85 
86         if (!DEVICE_PROFILE_TO_PERMISSION.containsKey(deviceProfile)) {
87             throw new IllegalArgumentException("Unsupported device profile: " + deviceProfile);
88         }
89 
90         final String permission = DEVICE_PROFILE_TO_PERMISSION.get(deviceProfile);
91         if (context.checkPermission(permission, getCallingPid(), packageUid)
92                 != PERMISSION_GRANTED) {
93             throw new SecurityException("Application must hold " + permission + " to associate "
94                     + "with a device with " + deviceProfile + " profile.");
95         }
96     }
97 
enforceRequestSelfManagedPermission(@onNull Context context, int packageUid)98     static void enforceRequestSelfManagedPermission(@NonNull Context context, int packageUid) {
99         if (context.checkPermission(REQUEST_COMPANION_SELF_MANAGED, getCallingPid(), packageUid)
100                 != PERMISSION_GRANTED) {
101             throw new SecurityException("Application does not hold "
102                     + REQUEST_COMPANION_SELF_MANAGED);
103         }
104     }
105 
checkCallerCanInteractWithUserId(@onNull Context context, int userId)106     static boolean checkCallerCanInteractWithUserId(@NonNull Context context, int userId) {
107         if (getCallingUserId() == userId) return true;
108 
109         return context.checkCallingPermission(INTERACT_ACROSS_USERS) == PERMISSION_GRANTED;
110     }
111 
enforceCallerCanInteractWithUserId(@onNull Context context, int userId)112     static void enforceCallerCanInteractWithUserId(@NonNull Context context, int userId) {
113         if (getCallingUserId() == userId) return;
114 
115         context.enforceCallingPermission(INTERACT_ACROSS_USERS, null);
116     }
117 
enforceCallerIsSystemOrCanInteractWithUserId(@onNull Context context, int userId)118     static void enforceCallerIsSystemOrCanInteractWithUserId(@NonNull Context context, int userId) {
119         if (getCallingUid() == SYSTEM_UID) return;
120 
121         enforceCallerCanInteractWithUserId(context, userId);
122     }
123 
checkCallerIsSystemOr(@serIdInt int userId, @NonNull String packageName)124     static boolean checkCallerIsSystemOr(@UserIdInt int userId, @NonNull String packageName) {
125         final int callingUid = getCallingUid();
126         if (callingUid == SYSTEM_UID) return true;
127 
128         if (getCallingUserId() != userId) return false;
129 
130         if (!checkPackage(callingUid, packageName)) return false;
131 
132         return true;
133     }
134 
enforceCallerIsSystemOr(@serIdInt int userId, @NonNull String packageName)135     static void enforceCallerIsSystemOr(@UserIdInt int userId, @NonNull String packageName) {
136         final int callingUid = getCallingUid();
137         if (callingUid == SYSTEM_UID) return;
138 
139         final int callingUserId = getCallingUserId();
140         if (getCallingUserId() != userId) {
141             throw new SecurityException("Calling UserId (" + callingUserId + ") does not match "
142                     + "the expected UserId (" + userId + ")");
143         }
144 
145         if (!checkPackage(callingUid, packageName)) {
146             throw new SecurityException(packageName + " doesn't belong to calling uid ("
147                     + callingUid + ")");
148         }
149     }
150 
checkCallerCanManageCompanionDevice(@onNull Context context)151     static boolean checkCallerCanManageCompanionDevice(@NonNull Context context) {
152         if (getCallingUid() == SYSTEM_UID) return true;
153 
154         return context.checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED;
155     }
156 
enforceCallerCanManageCompanionDevice(@onNull Context context, @Nullable String message)157     static void enforceCallerCanManageCompanionDevice(@NonNull Context context,
158             @Nullable String message) {
159         if (getCallingUid() == SYSTEM_UID) return;
160 
161         context.enforceCallingPermission(MANAGE_COMPANION_DEVICES, message);
162     }
163 
enforceCallerCanManageAssociationsForPackage(@onNull Context context, @UserIdInt int userId, @NonNull String packageName, @Nullable String actionDescription)164     static void enforceCallerCanManageAssociationsForPackage(@NonNull Context context,
165             @UserIdInt int userId, @NonNull String packageName,
166             @Nullable String actionDescription) {
167         if (checkCallerCanManageAssociationsForPackage(context, userId, packageName)) return;
168 
169         throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
170                 + "permissions to "
171                 + (actionDescription != null ? actionDescription : "manage associations")
172                 + " for u" + userId + "/" + packageName);
173     }
174 
175     /**
176      * Check if the caller is either:
177      * <ul>
178      * <li> the package itself
179      * <li> the System ({@link android.os.Process#SYSTEM_UID})
180      * <li> holds {@link Manifest.permission#MANAGE_COMPANION_DEVICES} and, if belongs to a
181      * different user, also holds {@link Manifest.permission#INTERACT_ACROSS_USERS}.
182      * </ul>
183      * @return whether the caller is one of the above.
184      */
checkCallerCanManageAssociationsForPackage(@onNull Context context, @UserIdInt int userId, @NonNull String packageName)185     static boolean checkCallerCanManageAssociationsForPackage(@NonNull Context context,
186             @UserIdInt int userId, @NonNull String packageName) {
187         if (checkCallerIsSystemOr(userId, packageName)) return true;
188 
189         if (!checkCallerCanInteractWithUserId(context, userId)) return false;
190 
191         return checkCallerCanManageCompanionDevice(context);
192     }
193 
sanitizeWithCallerChecks(@onNull Context context, @Nullable AssociationInfo association)194     static @Nullable AssociationInfo sanitizeWithCallerChecks(@NonNull Context context,
195             @Nullable AssociationInfo association) {
196         if (association == null) return null;
197 
198         final int userId = association.getUserId();
199         final String packageName = association.getPackageName();
200         if (!checkCallerCanManageAssociationsForPackage(context, userId, packageName)) {
201             return null;
202         }
203 
204         return association;
205     }
206 
checkPackage(@serIdInt int uid, @NonNull String packageName)207     private static boolean checkPackage(@UserIdInt int uid, @NonNull String packageName) {
208         try {
209             return getAppOpsService().checkPackage(uid, packageName) == MODE_ALLOWED;
210         } catch (RemoteException e) {
211             // Can't happen: AppOpsManager is running in the same process.
212             return true;
213         }
214     }
215 
getAppOpsService()216     private static IAppOpsService getAppOpsService() {
217         if (sAppOpsService == null) {
218             synchronized (PermissionsUtils.class) {
219                 if (sAppOpsService == null) {
220                     sAppOpsService = IAppOpsService.Stub.asInterface(
221                             ServiceManager.getService(Context.APP_OPS_SERVICE));
222                 }
223             }
224         }
225         return sAppOpsService;
226     }
227 
228     // DO NOT USE DIRECTLY! Access via getAppOpsService().
229     private static IAppOpsService sAppOpsService = null;
230 
PermissionsUtils()231     private PermissionsUtils() {}
232 }
233