Home | History | Annotate | Download | only in incident
      1 /*
      2  * Copyright (C) 2019 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.AppOpsManager;
     20 import android.app.BroadcastOptions;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.pm.PackageManager;
     25 import android.content.pm.ResolveInfo;
     26 import android.net.Uri;
     27 import android.os.Handler;
     28 import android.os.IIncidentAuthListener;
     29 import android.os.IncidentManager;
     30 import android.os.RemoteException;
     31 import android.os.SystemClock;
     32 import android.os.UserHandle;
     33 import android.util.Log;
     34 
     35 import java.io.FileDescriptor;
     36 import java.io.PrintWriter;
     37 import java.text.SimpleDateFormat;
     38 import java.util.ArrayList;
     39 import java.util.Date;
     40 import java.util.Iterator;
     41 import java.util.List;
     42 
     43 // TODO: User changes should deny everything that's pending.
     44 
     45 /**
     46  * Tracker for reports pending approval.
     47  */
     48 class PendingReports {
     49     static final String TAG = IncidentCompanionService.TAG;
     50 
     51     private final Handler mHandler = new Handler();
     52     private final RequestQueue mRequestQueue = new RequestQueue(mHandler);
     53     private final Context mContext;
     54     private final PackageManager mPackageManager;
     55     private final AppOpsManager mAppOpsManager;
     56 
     57     //
     58     // All fields below must be protected by mLock
     59     //
     60     private final Object mLock = new Object();
     61     private final ArrayList<PendingReportRec> mPending = new ArrayList();
     62 
     63     /**
     64      * The next ID we'll use when we make a PendingReportRec.
     65      */
     66     private int mNextPendingId = 1;
     67 
     68     /**
     69      * One for each authorization that's pending.
     70      */
     71     private final class PendingReportRec {
     72         public int id;
     73         public String callingPackage;
     74         public int flags;
     75         public IIncidentAuthListener listener;
     76         public long addedRealtime;
     77         public long addedWalltime;
     78         public String receiverClass;
     79         public String reportId;
     80 
     81         /**
     82          * Construct a PendingReportRec, with an auto-incremented id.
     83          */
     84         PendingReportRec(String callingPackage, String receiverClass, String reportId, int flags,
     85                 IIncidentAuthListener listener) {
     86             this.id = mNextPendingId++;
     87             this.callingPackage = callingPackage;
     88             this.flags = flags;
     89             this.listener = listener;
     90             this.addedRealtime = SystemClock.elapsedRealtime();
     91             this.addedWalltime = System.currentTimeMillis();
     92             this.receiverClass = receiverClass;
     93             this.reportId = reportId;
     94         }
     95 
     96         /**
     97          * Get the Uri that contains the flattened data.
     98          */
     99         Uri getUri() {
    100             final Uri.Builder builder = (new Uri.Builder())
    101                     .scheme(IncidentManager.URI_SCHEME)
    102                     .authority(IncidentManager.URI_AUTHORITY)
    103                     .path(IncidentManager.URI_PATH)
    104                     .appendQueryParameter(IncidentManager.URI_PARAM_ID, Integer.toString(id))
    105                     .appendQueryParameter(IncidentManager.URI_PARAM_CALLING_PACKAGE, callingPackage)
    106                     .appendQueryParameter(IncidentManager.URI_PARAM_FLAGS, Integer.toString(flags))
    107                     .appendQueryParameter(IncidentManager.URI_PARAM_TIMESTAMP,
    108                             Long.toString(addedWalltime));
    109             if (receiverClass != null && receiverClass.length() > 0) {
    110                 builder.appendQueryParameter(IncidentManager.URI_PARAM_RECEIVER_CLASS,
    111                         receiverClass);
    112             }
    113             if (reportId != null && reportId.length() > 0) {
    114                 builder.appendQueryParameter(IncidentManager.URI_PARAM_REPORT_ID, reportId);
    115             }
    116             return builder.build();
    117         }
    118     }
    119 
    120     /**
    121      * Construct new PendingReports with the context.
    122      */
    123     PendingReports(Context context) {
    124         mContext = context;
    125         mPackageManager = context.getPackageManager();
    126         mAppOpsManager = context.getSystemService(AppOpsManager.class);
    127     }
    128 
    129     /**
    130      * ONEWAY binder call to initiate authorizing the report.  The actual logic is posted
    131      * to mRequestQueue, and may happen later.
    132      * <p>
    133      * The security checks are handled by IncidentCompanionService.
    134      */
    135     public void authorizeReport(int callingUid, final String callingPackage,
    136             final String receiverClass, final String reportId, final int flags,
    137             final IIncidentAuthListener listener) {
    138         // Starting the system server is complicated, and rather than try to
    139         // have a complicated lifecycle that we share with dumpstated and incidentd,
    140         // we will accept the request, and then display it whenever it becomes possible to.
    141         mRequestQueue.enqueue(listener.asBinder(), true, () -> {
    142             authorizeReportImpl(callingUid, callingPackage, receiverClass, reportId,
    143                     flags, listener);
    144         });
    145     }
    146 
    147     /**
    148      * ONEWAY binder call to cancel the inbound authorization request.
    149      * <p>
    150      * This is a oneway call, and so is authorizeReport, so the
    151      * caller's ordering is preserved.  The other calls on this object are synchronous, so
    152      * their ordering is not guaranteed with respect to these calls.  So the implementation
    153      * sends out extra broadcasts to allow for eventual consistency.
    154      * <p>
    155      * The security checks are handled by IncidentCompanionService.
    156      */
    157     public void cancelAuthorization(final IIncidentAuthListener listener) {
    158         mRequestQueue.enqueue(listener.asBinder(), false, () -> {
    159             cancelReportImpl(listener);
    160         });
    161     }
    162 
    163     /**
    164      * SYNCHRONOUS binder call to get the list of reports that are pending confirmation
    165      * by the user.
    166      * <p>
    167      * The security checks are handled by IncidentCompanionService.
    168      */
    169     public List<String> getPendingReports() {
    170         synchronized (mLock) {
    171             final int size = mPending.size();
    172             final ArrayList<String> result = new ArrayList(size);
    173             for (int i = 0; i < size; i++) {
    174                 result.add(mPending.get(i).getUri().toString());
    175             }
    176             return result;
    177         }
    178     }
    179 
    180     /**
    181      * SYNCHRONOUS binder call to mark a report as approved.
    182      * <p>
    183      * The security checks are handled by IncidentCompanionService.
    184      */
    185     public void approveReport(String uri) {
    186         final PendingReportRec rec;
    187         synchronized (mLock) {
    188             rec = findAndRemovePendingReportRecLocked(uri);
    189             if (rec == null) {
    190                 Log.e(TAG, "confirmApproved: Couldn't find record for uri: " + uri);
    191                 return;
    192             }
    193         }
    194 
    195         // Re-do the broadcast, so whoever is listening knows the list changed,
    196         // in case another one was added in the meantime.
    197         sendBroadcast();
    198 
    199         Log.i(TAG, "Approved report: " + uri);
    200         try {
    201             rec.listener.onReportApproved();
    202         } catch (RemoteException ex) {
    203             Log.w(TAG, "Failed calling back for approval for: " + uri, ex);
    204         }
    205     }
    206 
    207     /**
    208      * SYNCHRONOUS binder call to mark a report as NOT approved.
    209      */
    210     public void denyReport(String uri) {
    211         final PendingReportRec rec;
    212         synchronized (mLock) {
    213             rec = findAndRemovePendingReportRecLocked(uri);
    214             if (rec == null) {
    215                 Log.e(TAG, "confirmDenied: Couldn't find record for uri: " + uri);
    216                 return;
    217             }
    218         }
    219 
    220         // Re-do the broadcast, so whoever is listening knows the list changed,
    221         // in case another one was added in the meantime.
    222         sendBroadcast();
    223 
    224         Log.i(TAG, "Denied report: " + uri);
    225         try {
    226             rec.listener.onReportDenied();
    227         } catch (RemoteException ex) {
    228             Log.w(TAG, "Failed calling back for denial for: " + uri, ex);
    229         }
    230     }
    231 
    232     /**
    233      * Implementation of adb shell dumpsys debugreportcompanion.
    234      */
    235     protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
    236         if (args.length == 0) {
    237             // Standard text dumpsys
    238             final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    239             synchronized (mLock) {
    240                 final int size = mPending.size();
    241                 writer.println("mPending: (" + size + ")");
    242                 for (int i = 0; i < size; i++) {
    243                     final PendingReportRec entry = mPending.get(i);
    244                     writer.println(String.format("  %11d %s: %s", entry.addedRealtime,
    245                                 df.format(new Date(entry.addedWalltime)),
    246                                 entry.getUri().toString()));
    247                 }
    248             }
    249         }
    250     }
    251 
    252     /**
    253      * Handle the boot process... Starts everything running once the system is
    254      * up enough for us to do UI.
    255      */
    256     public void onBootCompleted() {
    257         // Release the enqueued work.
    258         mRequestQueue.start();
    259     }
    260 
    261     /**
    262      * Start the confirmation process.
    263      */
    264     private void authorizeReportImpl(int callingUid, final String callingPackage,
    265             final String receiverClass, final String reportId,
    266             int flags, final IIncidentAuthListener listener) {
    267         // Enforce that the calling package pertains to the callingUid.
    268         if (callingUid != 0 && !isPackageInUid(callingUid, callingPackage)) {
    269             Log.w(TAG, "Calling uid " + callingUid + " doesn't match package "
    270                     + callingPackage);
    271             denyReportBeforeAddingRec(listener, callingPackage);
    272             return;
    273         }
    274 
    275         // Find the primary user of this device.
    276         final int primaryUser = getAndValidateUser();
    277         if (primaryUser == UserHandle.USER_NULL) {
    278             denyReportBeforeAddingRec(listener, callingPackage);
    279             return;
    280         }
    281 
    282         // Find the approver app (hint: it's PermissionController).
    283         final ComponentName receiver = getApproverComponent(primaryUser);
    284         if (receiver == null) {
    285             // We couldn't find an approver... so deny the request here and now, before we
    286             // do anything else.
    287             denyReportBeforeAddingRec(listener, callingPackage);
    288             return;
    289         }
    290 
    291         // Save the record for when the PermissionController comes back to authorize it.
    292         PendingReportRec rec = null;
    293         synchronized (mLock) {
    294             rec = new PendingReportRec(callingPackage, receiverClass, reportId, flags, listener);
    295             mPending.add(rec);
    296         }
    297 
    298         try {
    299             listener.asBinder().linkToDeath(() -> {
    300                 Log.i(TAG, "Got death notification listener=" + listener);
    301                 cancelReportImpl(listener, receiver, primaryUser);
    302             }, 0);
    303         } catch (RemoteException ex) {
    304             Log.e(TAG, "Remote died while trying to register death listener: " + rec.getUri());
    305             // First, remove from our list.
    306             cancelReportImpl(listener, receiver, primaryUser);
    307         }
    308 
    309         // Go tell Permission controller to start asking the user.
    310         sendBroadcast(receiver, primaryUser);
    311     }
    312 
    313     /**
    314      * Cancel a pending report request (because of an explicit call to cancel)
    315      */
    316     private void cancelReportImpl(IIncidentAuthListener listener) {
    317         final int primaryUser = getAndValidateUser();
    318         final ComponentName receiver = getApproverComponent(primaryUser);
    319         if (primaryUser != UserHandle.USER_NULL && receiver != null) {
    320             cancelReportImpl(listener, receiver, primaryUser);
    321         }
    322     }
    323 
    324     /**
    325      * Cancel a pending report request (either because of an explicit call to cancel
    326      * by the calling app, or because of a binder death).
    327      */
    328     private void cancelReportImpl(IIncidentAuthListener listener, ComponentName receiver,
    329             int primaryUser) {
    330         // First, remove from our list.
    331         synchronized (mLock) {
    332             removePendingReportRecLocked(listener);
    333         }
    334         // Second, call back to PermissionController to say it's canceled.
    335         sendBroadcast(receiver, primaryUser);
    336     }
    337 
    338     /**
    339      * Send an extra copy of the broadcast, to tell them that the list has changed
    340      * because of an addition or removal.  This function is less aggressive than
    341      * authorizeReportImpl in logging about failures, because this is for use in
    342      * cleanup cases to keep the apps' list in sync with ours.
    343      */
    344     private void sendBroadcast() {
    345         final int primaryUser = getAndValidateUser();
    346         if (primaryUser == UserHandle.USER_NULL) {
    347             return;
    348         }
    349         final ComponentName receiver = getApproverComponent(primaryUser);
    350         if (receiver == null) {
    351             return;
    352         }
    353         sendBroadcast(receiver, primaryUser);
    354     }
    355 
    356     /**
    357      * Send the confirmation broadcast.
    358      */
    359     private void sendBroadcast(ComponentName receiver, int primaryUser) {
    360         final Intent intent = new Intent(Intent.ACTION_PENDING_INCIDENT_REPORTS_CHANGED);
    361         intent.setComponent(receiver);
    362         final BroadcastOptions options = BroadcastOptions.makeBasic();
    363         options.setBackgroundActivityStartsAllowed(true);
    364 
    365         // Send it to the primary user.
    366         mContext.sendBroadcastAsUser(intent, UserHandle.getUserHandleForUid(primaryUser),
    367                 android.Manifest.permission.APPROVE_INCIDENT_REPORTS, options.toBundle());
    368     }
    369 
    370     /**
    371      * Remove a PendingReportRec keyed by uri, and return it.
    372      */
    373     private PendingReportRec findAndRemovePendingReportRecLocked(String uriString) {
    374         final Uri uri = Uri.parse(uriString);
    375         final int id;
    376         try {
    377             final String idStr = uri.getQueryParameter(IncidentManager.URI_PARAM_ID);
    378             id = Integer.parseInt(idStr);
    379         } catch (NumberFormatException ex) {
    380             Log.w(TAG, "Can't parse id from: " + uriString);
    381             return null;
    382         }
    383 
    384         for (Iterator<PendingReportRec> i = mPending.iterator(); i.hasNext();) {
    385             final PendingReportRec rec = i.next();
    386             if (rec.id == id) {
    387                 i.remove();
    388                 return rec;
    389             }
    390         }
    391         return null;
    392     }
    393 
    394     /**
    395      * Remove a PendingReportRec keyed by listener.
    396      */
    397     private void removePendingReportRecLocked(IIncidentAuthListener listener) {
    398 
    399         for (Iterator<PendingReportRec> i = mPending.iterator(); i.hasNext();) {
    400             final PendingReportRec rec = i.next();
    401             if (rec.listener.asBinder() == listener.asBinder()) {
    402                 Log.i(TAG, "  ...Removed PendingReportRec index=" + i + ": " + rec.getUri());
    403                 i.remove();
    404             }
    405         }
    406     }
    407 
    408     /**
    409      * Just call listener.deny() (wrapping the RemoteException), without try to
    410      * add it to the list.
    411      */
    412     private void denyReportBeforeAddingRec(IIncidentAuthListener listener, String pkg) {
    413         try {
    414             listener.onReportDenied();
    415         } catch (RemoteException ex) {
    416             Log.w(TAG, "Failed calling back for denial for " + pkg, ex);
    417         }
    418     }
    419 
    420     /**
    421      * Check whether the current user is the primary user, and return the user id if they are.
    422      * Returns UserHandle.USER_NULL if not valid.
    423      */
    424     private int getAndValidateUser() {
    425         return IncidentCompanionService.getAndValidateUser(mContext);
    426     }
    427 
    428     /**
    429      * Return the ComponentName of the BroadcastReceiver that will approve reports.
    430      * The system must have zero or one of these installed.  We only look on the
    431      * system partition.  When the broadcast happens, the component will also need
    432      * have the APPROVE_INCIDENT_REPORTS permission.
    433      */
    434     private ComponentName getApproverComponent(int userId) {
    435         // Find the one true BroadcastReceiver
    436         final Intent intent = new Intent(Intent.ACTION_PENDING_INCIDENT_REPORTS_CHANGED);
    437         final List<ResolveInfo> matches = mPackageManager.queryBroadcastReceiversAsUser(intent,
    438                 PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DIRECT_BOOT_AWARE
    439                 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
    440         if (matches.size() == 1) {
    441             return matches.get(0).getComponentInfo().getComponentName();
    442         } else {
    443             Log.w(TAG, "Didn't find exactly one BroadcastReceiver to handle "
    444                     + Intent.ACTION_PENDING_INCIDENT_REPORTS_CHANGED
    445                     + ". The report will be denied. size="
    446                     + matches.size() + ": matches=" + matches);
    447             return null;
    448         }
    449     }
    450 
    451     /**
    452      * Return whether the package is one of the packages installed for the uid.
    453      */
    454     private boolean isPackageInUid(int uid, String packageName) {
    455         try {
    456             mAppOpsManager.checkPackage(uid, packageName);
    457             return true;
    458         } catch (SecurityException ex) {
    459             return false;
    460         }
    461     }
    462 }
    463 
    464