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