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