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