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