1 /* 2 * Copyright (C) 2018 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.incident; 18 19 import android.app.ActivityManager; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.pm.ApplicationInfo; 24 import android.content.pm.PackageManager; 25 import android.content.pm.UserInfo; 26 import android.content.res.Resources; 27 import android.os.Binder; 28 import android.os.Build; 29 import android.os.IBinder; 30 import android.os.IIncidentAuthListener; 31 import android.os.IIncidentCompanion; 32 import android.os.IIncidentManager; 33 import android.os.IncidentManager; 34 import android.os.RemoteException; 35 import android.os.ServiceManager; 36 import android.os.UserHandle; 37 import android.util.Log; 38 39 import com.android.internal.util.DumpUtils; 40 import com.android.server.SystemService; 41 42 import java.io.FileDescriptor; 43 import java.io.PrintWriter; 44 import java.util.List; 45 46 /** 47 * Helper service for incidentd and dumpstated to provide user feedback 48 * and authorization for bug and inicdent reports to be taken. 49 */ 50 public class IncidentCompanionService extends SystemService { 51 static final String TAG = "IncidentCompanionService"; 52 53 /** 54 * Dump argument for proxying restricted image dumps to the services 55 * listed in the config. 56 */ 57 private static String[] RESTRICTED_IMAGE_DUMP_ARGS = new String[] { 58 "--hal", "--restricted_image" }; 59 60 /** 61 * The two permissions, for sendBroadcastAsUserMultiplePermissions. 62 */ 63 private static final String[] DUMP_AND_USAGE_STATS_PERMISSIONS = new String[] { 64 android.Manifest.permission.DUMP, 65 android.Manifest.permission.PACKAGE_USAGE_STATS 66 }; 67 68 /** 69 * Tracker for reports pending approval. 70 */ 71 private PendingReports mPendingReports; 72 73 /** 74 * Implementation of the IIncidentCompanion binder interface. 75 */ 76 private final class BinderService extends IIncidentCompanion.Stub { 77 /** 78 * ONEWAY binder call to initiate authorizing the report. If you don't need 79 * IncidentCompanionService to check whether the calling UID matches then 80 * pass 0 for callingUid. Either way, the caller must have DUMP and USAGE_STATS 81 * permissions to retrieve the data, so it ends up being about the same. 82 */ 83 @Override authorizeReport(int callingUid, final String callingPackage, final String receiverClass, final String reportId, final int flags, final IIncidentAuthListener listener)84 public void authorizeReport(int callingUid, final String callingPackage, 85 final String receiverClass, final String reportId, 86 final int flags, final IIncidentAuthListener listener) { 87 enforceRequestAuthorizationPermission(); 88 89 final long ident = Binder.clearCallingIdentity(); 90 try { 91 mPendingReports.authorizeReport(callingUid, callingPackage, 92 receiverClass, reportId, flags, listener); 93 } finally { 94 Binder.restoreCallingIdentity(ident); 95 } 96 } 97 98 /** 99 * ONEWAY binder call to cancel the inbound authorization request. 100 * <p> 101 * This is a oneway call, and so is authorizeReport, so the 102 * caller's ordering is preserved. The other calls on this object are synchronous, so 103 * their ordering is not guaranteed with respect to these calls. So the implementation 104 * sends out extra broadcasts to allow for eventual consistency. 105 */ cancelAuthorization(final IIncidentAuthListener listener)106 public void cancelAuthorization(final IIncidentAuthListener listener) { 107 enforceRequestAuthorizationPermission(); 108 109 // Caller can cancel if they don't want it anymore, and mRequestQueue elides 110 // authorize/cancel pairs. 111 final long ident = Binder.clearCallingIdentity(); 112 try { 113 mPendingReports.cancelAuthorization(listener); 114 } finally { 115 Binder.restoreCallingIdentity(ident); 116 } 117 } 118 119 /** 120 * ONEWAY implementation to send broadcast from incidentd, which is native. 121 */ 122 @Override sendReportReadyBroadcast(String pkg, String cls)123 public void sendReportReadyBroadcast(String pkg, String cls) { 124 enforceRequestAuthorizationPermission(); 125 126 final long ident = Binder.clearCallingIdentity(); 127 try { 128 final Context context = getContext(); 129 130 // Get the current admin user. Only they can do incident reports. 131 final int currentAdminUser = getCurrentUserIfAdmin(); 132 if (currentAdminUser == UserHandle.USER_NULL) { 133 return; 134 } 135 136 final Intent intent = new Intent(Intent.ACTION_INCIDENT_REPORT_READY); 137 intent.setComponent(new ComponentName(pkg, cls)); 138 139 Log.d(TAG, "sendReportReadyBroadcast sending currentUser=" + currentAdminUser 140 + " userHandle=" + UserHandle.of(currentAdminUser) 141 + " intent=" + intent); 142 143 context.sendBroadcastAsUserMultiplePermissions(intent, 144 UserHandle.of(currentAdminUser), 145 DUMP_AND_USAGE_STATS_PERMISSIONS); 146 } finally { 147 Binder.restoreCallingIdentity(ident); 148 } 149 } 150 151 /** 152 * SYNCHRONOUS binder call to get the list of reports that are pending confirmation 153 * by the user. 154 */ 155 @Override getPendingReports()156 public List<String> getPendingReports() { 157 enforceAuthorizePermission(); 158 return mPendingReports.getPendingReports(); 159 } 160 161 /** 162 * SYNCHRONOUS binder call to mark a report as approved. 163 */ 164 @Override approveReport(String uri)165 public void approveReport(String uri) { 166 enforceAuthorizePermission(); 167 168 final long ident = Binder.clearCallingIdentity(); 169 try { 170 mPendingReports.approveReport(uri); 171 } finally { 172 Binder.restoreCallingIdentity(ident); 173 } 174 } 175 176 /** 177 * SYNCHRONOUS binder call to mark a report as NOT approved. 178 */ 179 @Override denyReport(String uri)180 public void denyReport(String uri) { 181 enforceAuthorizePermission(); 182 183 final long ident = Binder.clearCallingIdentity(); 184 try { 185 mPendingReports.denyReport(uri); 186 } finally { 187 Binder.restoreCallingIdentity(ident); 188 } 189 } 190 191 /** 192 * SYNCHRONOUS binder call to get the list of incident reports waiting for a receiver. 193 */ 194 @Override getIncidentReportList(String pkg, String cls)195 public List<String> getIncidentReportList(String pkg, String cls) throws RemoteException { 196 enforceAccessReportsPermissions(null); 197 198 final long ident = Binder.clearCallingIdentity(); 199 try { 200 return getIIncidentManager().getIncidentReportList(pkg, cls); 201 } finally { 202 Binder.restoreCallingIdentity(ident); 203 } 204 } 205 206 /** 207 * SYNCHRONOUS binder call to commit an incident report 208 */ 209 @Override deleteIncidentReports(String pkg, String cls, String id)210 public void deleteIncidentReports(String pkg, String cls, String id) 211 throws RemoteException { 212 if (pkg == null || cls == null || id == null 213 || pkg.length() == 0 || cls.length() == 0 || id.length() == 0) { 214 throw new RuntimeException("Invalid pkg, cls or id"); 215 } 216 enforceAccessReportsPermissions(pkg); 217 218 final long ident = Binder.clearCallingIdentity(); 219 try { 220 getIIncidentManager().deleteIncidentReports(pkg, cls, id); 221 } finally { 222 Binder.restoreCallingIdentity(ident); 223 } 224 } 225 226 /** 227 * SYNCHRONOUS binder call to delete all incident reports for a package. 228 */ 229 @Override deleteAllIncidentReports(String pkg)230 public void deleteAllIncidentReports(String pkg) throws RemoteException { 231 if (pkg == null || pkg.length() == 0) { 232 throw new RuntimeException("Invalid pkg"); 233 } 234 enforceAccessReportsPermissions(pkg); 235 236 final long ident = Binder.clearCallingIdentity(); 237 try { 238 getIIncidentManager().deleteAllIncidentReports(pkg); 239 } finally { 240 Binder.restoreCallingIdentity(ident); 241 } 242 } 243 244 /** 245 * SYNCHRONOUS binder call to get the IncidentReport object. 246 */ 247 @Override getIncidentReport(String pkg, String cls, String id)248 public IncidentManager.IncidentReport getIncidentReport(String pkg, String cls, String id) 249 throws RemoteException { 250 if (pkg == null || cls == null || id == null 251 || pkg.length() == 0 || cls.length() == 0 || id.length() == 0) { 252 throw new RuntimeException("Invalid pkg, cls or id"); 253 } 254 enforceAccessReportsPermissions(pkg); 255 256 final long ident = Binder.clearCallingIdentity(); 257 try { 258 return getIIncidentManager().getIncidentReport(pkg, cls, id); 259 } finally { 260 Binder.restoreCallingIdentity(ident); 261 } 262 } 263 264 /** 265 * SYNCHRONOUS implementation of adb shell dumpsys debugreportcompanion. 266 */ 267 @Override dump(FileDescriptor fd, final PrintWriter writer, String[] args)268 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) { 269 if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) { 270 return; 271 } 272 273 if (args.length == 1 && "--restricted_image".equals(args[0])) { 274 // Does NOT clearCallingIdentity 275 dumpRestrictedImages(fd); 276 } else { 277 // Regular dump 278 mPendingReports.dump(fd, writer, args); 279 } 280 } 281 282 /** 283 * Proxy for the restricted images section. 284 */ dumpRestrictedImages(FileDescriptor fd)285 private void dumpRestrictedImages(FileDescriptor fd) { 286 // Only supported on eng or userdebug. 287 if (!(Build.IS_ENG || Build.IS_USERDEBUG)) { 288 return; 289 } 290 291 final Resources res = getContext().getResources(); 292 final String[] services = res.getStringArray( 293 com.android.internal.R.array.config_restrictedImagesServices); 294 final int servicesCount = services.length; 295 for (int i = 0; i < servicesCount; i++) { 296 final String name = services[i]; 297 Log.d(TAG, "Looking up service " + name); 298 final IBinder service = ServiceManager.getService(name); 299 if (service != null) { 300 Log.d(TAG, "Calling dump on service: " + name); 301 try { 302 service.dump(fd, RESTRICTED_IMAGE_DUMP_ARGS); 303 } catch (RemoteException ex) { 304 Log.w(TAG, "dump --restricted_image of " + name + " threw", ex); 305 } 306 } 307 } 308 } 309 310 /** 311 * Inside the binder interface class because we want to do all of the authorization 312 * here, before calling out to the helper objects. 313 */ enforceRequestAuthorizationPermission()314 private void enforceRequestAuthorizationPermission() { 315 getContext().enforceCallingOrSelfPermission( 316 android.Manifest.permission.REQUEST_INCIDENT_REPORT_APPROVAL, null); 317 } 318 319 /** 320 * Inside the binder interface class because we want to do all of the authorization 321 * here, before calling out to the helper objects. 322 */ enforceAuthorizePermission()323 private void enforceAuthorizePermission() { 324 getContext().enforceCallingOrSelfPermission( 325 android.Manifest.permission.APPROVE_INCIDENT_REPORTS, null); 326 } 327 328 /** 329 * Enforce that the calling process either has APPROVE_INCIDENT_REPORTS or 330 * (DUMP and PACKAGE_USAGE_STATS). This lets the approver get, because showing 331 * information about the report is a prerequisite for letting the user decide. 332 * 333 * If pkg is null, it is not checked, so make sure that you check it for null first 334 * if you do need the packages to match. 335 * 336 * Inside the binder interface class because we want to do all of the authorization 337 * here, before calling out to the helper objects. 338 */ enforceAccessReportsPermissions(String pkg)339 private void enforceAccessReportsPermissions(String pkg) { 340 if (getContext().checkCallingPermission( 341 android.Manifest.permission.APPROVE_INCIDENT_REPORTS) 342 != PackageManager.PERMISSION_GRANTED) { 343 getContext().enforceCallingOrSelfPermission( 344 android.Manifest.permission.DUMP, null); 345 getContext().enforceCallingOrSelfPermission( 346 android.Manifest.permission.PACKAGE_USAGE_STATS, null); 347 if (pkg != null) { 348 enforceCallerIsSameApp(pkg); 349 } 350 } 351 } 352 353 /** 354 * Throw a SecurityException if the incoming binder call is not from pkg. 355 */ enforceCallerIsSameApp(String pkg)356 private void enforceCallerIsSameApp(String pkg) throws SecurityException { 357 try { 358 final int uid = Binder.getCallingUid(); 359 final int userId = UserHandle.getCallingUserId(); 360 final ApplicationInfo ai = getContext().getPackageManager() 361 .getApplicationInfoAsUser(pkg, 0, userId); 362 if (ai == null) { 363 throw new SecurityException("Unknown package " + pkg); 364 } 365 if (!UserHandle.isSameApp(ai.uid, uid)) { 366 throw new SecurityException("Calling uid " + uid + " gave package " 367 + pkg + " which is owned by uid " + ai.uid); 368 } 369 } catch (PackageManager.NameNotFoundException re) { 370 throw new SecurityException("Unknown package " + pkg + "\n" + re); 371 } 372 } 373 } 374 375 /** 376 * Construct new IncidentCompanionService with the context. 377 */ IncidentCompanionService(Context context)378 public IncidentCompanionService(Context context) { 379 super(context); 380 mPendingReports = new PendingReports(context); 381 } 382 383 /** 384 * Initialize the service. It is still not safe to do UI until 385 * onBootPhase(SystemService.PHASE_BOOT_COMPLETED). 386 */ 387 @Override onStart()388 public void onStart() { 389 publishBinderService(Context.INCIDENT_COMPANION_SERVICE, new BinderService()); 390 } 391 392 /** 393 * Handle the boot process... Starts everything running once the system is 394 * up enough for us to do UI. 395 */ 396 @Override onBootPhase(int phase)397 public void onBootPhase(int phase) { 398 super.onBootPhase(phase); 399 switch (phase) { 400 case SystemService.PHASE_BOOT_COMPLETED: 401 mPendingReports.onBootCompleted(); 402 break; 403 } 404 } 405 406 /** 407 * Looks up incidentd every time, so we don't need a complex handshake between 408 * incidentd and IncidentCompanionService. 409 */ getIIncidentManager()410 private IIncidentManager getIIncidentManager() throws RemoteException { 411 return IIncidentManager.Stub.asInterface( 412 ServiceManager.getService(Context.INCIDENT_SERVICE)); 413 } 414 415 /** 416 * Check whether the current user is an admin user, and return the user id if they are. 417 * Returns UserHandle.USER_NULL if not valid. 418 */ getCurrentUserIfAdmin()419 public static int getCurrentUserIfAdmin() { 420 // Current user 421 UserInfo currentUser; 422 try { 423 currentUser = ActivityManager.getService().getCurrentUser(); 424 } catch (RemoteException ex) { 425 // We're already inside the system process. 426 throw new RuntimeException(ex); 427 } 428 429 // Check that we're using the right user. 430 if (currentUser == null) { 431 Log.w(TAG, "No current user. Nobody to approve the report." 432 + " The report will be denied."); 433 return UserHandle.USER_NULL; 434 } 435 436 if (!currentUser.isAdmin()) { 437 Log.w(TAG, "Only an admin user running in foreground can approve " 438 + "bugreports, but the current foreground user is not an admin user. " 439 + "The report will be denied."); 440 return UserHandle.USER_NULL; 441 } 442 443 return currentUser.id; 444 } 445 } 446 447