1 /*
2  * Copyright (C) 2020 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.devicepolicy;
18 
19 import static android.app.admin.DevicePolicyManager.ACTION_BUGREPORT_SHARING_ACCEPTED;
20 import static android.app.admin.DevicePolicyManager.ACTION_BUGREPORT_SHARING_DECLINED;
21 import static android.app.admin.DevicePolicyManager.ACTION_REMOTE_BUGREPORT_DISPATCH;
22 import static android.app.admin.DevicePolicyManager.EXTRA_BUGREPORT_NOTIFICATION_TYPE;
23 import static android.app.admin.DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_HASH;
24 import static android.app.admin.DevicePolicyManager.NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED;
25 import static android.app.admin.DevicePolicyManager.NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED;
26 import static android.app.admin.DevicePolicyManager.NOTIFICATION_BUGREPORT_STARTED;
27 
28 import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG;
29 
30 import android.annotation.IntDef;
31 import android.app.Notification;
32 import android.app.PendingIntent;
33 import android.app.admin.DeviceAdminReceiver;
34 import android.app.admin.DevicePolicyManager;
35 import android.content.BroadcastReceiver;
36 import android.content.Context;
37 import android.content.Intent;
38 import android.content.IntentFilter;
39 import android.content.pm.ActivityInfo;
40 import android.content.pm.PackageManager;
41 import android.net.Uri;
42 import android.os.Bundle;
43 import android.os.Handler;
44 import android.os.RemoteException;
45 import android.os.UserHandle;
46 import android.provider.Settings;
47 import android.text.format.DateUtils;
48 import android.util.Pair;
49 
50 import com.android.internal.R;
51 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
52 import com.android.internal.notification.SystemNotificationChannels;
53 import com.android.server.utils.Slogf;
54 
55 import java.io.FileNotFoundException;
56 import java.lang.annotation.Retention;
57 import java.lang.annotation.RetentionPolicy;
58 import java.security.SecureRandom;
59 import java.util.concurrent.atomic.AtomicBoolean;
60 import java.util.concurrent.atomic.AtomicLong;
61 
62 /**
63  * Class managing bugreport collection upon device owner's request.
64  */
65 public class RemoteBugreportManager {
66 
67     static final String BUGREPORT_MIMETYPE = "application/vnd.android.bugreport";
68 
69     private static final long REMOTE_BUGREPORT_TIMEOUT_MILLIS = 10 * DateUtils.MINUTE_IN_MILLIS;
70     private static final String CTL_STOP = "ctl.stop";
71     private static final String REMOTE_BUGREPORT_SERVICE = "bugreportd";
72     private static final int NOTIFICATION_ID = SystemMessage.NOTE_REMOTE_BUGREPORT;
73 
74     @Retention(RetentionPolicy.SOURCE)
75     @IntDef({
76             NOTIFICATION_BUGREPORT_STARTED,
77             NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED,
78             NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED
79     })
80     @interface RemoteBugreportNotificationType {}
81     private final DevicePolicyManagerService mService;
82     private final DevicePolicyManagerService.Injector mInjector;
83 
84     private final SecureRandom mRng = new SecureRandom();
85 
86     private final AtomicLong mRemoteBugreportNonce = new AtomicLong();
87     private final AtomicBoolean mRemoteBugreportServiceIsActive = new AtomicBoolean();
88     private final AtomicBoolean mRemoteBugreportSharingAccepted = new AtomicBoolean();
89     private final Context mContext;
90 
91     private final Handler mHandler;
92 
93     private final Runnable mRemoteBugreportTimeoutRunnable = () -> {
94         if (mRemoteBugreportServiceIsActive.get()) {
95             onBugreportFailed();
96         }
97     };
98 
99     private final BroadcastReceiver mRemoteBugreportFinishedReceiver = new BroadcastReceiver() {
100         @Override
101         public void onReceive(Context context, Intent intent) {
102             if (ACTION_REMOTE_BUGREPORT_DISPATCH.equals(intent.getAction())
103                     && mRemoteBugreportServiceIsActive.get()) {
104                 onBugreportFinished(intent);
105             }
106         }
107     };
108 
109     private final BroadcastReceiver mRemoteBugreportConsentReceiver = new BroadcastReceiver() {
110 
111         @Override
112         public void onReceive(Context context, Intent intent) {
113             final String action = intent.getAction();
114             cancelNotification();
115             if (ACTION_BUGREPORT_SHARING_ACCEPTED.equals(action)) {
116                 onBugreportSharingAccepted();
117             } else if (ACTION_BUGREPORT_SHARING_DECLINED.equals(action)) {
118                 onBugreportSharingDeclined();
119             }
120             mContext.unregisterReceiver(mRemoteBugreportConsentReceiver);
121         }
122     };
123 
RemoteBugreportManager( DevicePolicyManagerService service, DevicePolicyManagerService.Injector injector)124     public RemoteBugreportManager(
125             DevicePolicyManagerService service, DevicePolicyManagerService.Injector injector) {
126         mService = service;
127         mInjector = injector;
128         mContext = service.mContext;
129         mHandler = service.mHandler;
130     }
131 
buildNotification(@emoteBugreportNotificationType int type)132     private Notification buildNotification(@RemoteBugreportNotificationType int type) {
133         final Intent dialogIntent = new Intent(Settings.ACTION_SHOW_REMOTE_BUGREPORT_DIALOG);
134         dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
135         dialogIntent.putExtra(EXTRA_BUGREPORT_NOTIFICATION_TYPE, type);
136 
137         // Fill the component explicitly to prevent the PendingIntent from being intercepted
138         // and fired with crafted target. b/155183624
139         final ActivityInfo targetInfo = dialogIntent.resolveActivityInfo(
140                 mContext.getPackageManager(), PackageManager.MATCH_SYSTEM_ONLY);
141         if (targetInfo != null) {
142             dialogIntent.setComponent(targetInfo.getComponentName());
143         } else {
144             Slogf.wtf(LOG_TAG, "Failed to resolve intent for remote bugreport dialog");
145         }
146 
147         // Simple notification clicks are immutable
148         final PendingIntent pendingDialogIntent = PendingIntent.getActivityAsUser(mContext, type,
149                 dialogIntent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
150 
151         final Notification.Builder builder =
152                 new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN)
153                         .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
154                         .setOngoing(true)
155                         .setLocalOnly(true)
156                         .setContentIntent(pendingDialogIntent)
157                         .setColor(mContext.getColor(
158                                 com.android.internal.R.color.system_notification_accent_color))
159                         .extend(new Notification.TvExtender());
160 
161         if (type == NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED) {
162             builder.setContentTitle(mContext.getString(
163                         R.string.sharing_remote_bugreport_notification_title))
164                     .setProgress(0, 0, true);
165         } else if (type == NOTIFICATION_BUGREPORT_STARTED) {
166             builder.setContentTitle(mContext.getString(
167                         R.string.taking_remote_bugreport_notification_title))
168                     .setProgress(0, 0, true);
169         } else if (type == NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED) {
170             // Simple notification action button clicks are immutable
171             final PendingIntent pendingIntentAccept = PendingIntent.getBroadcast(mContext,
172                     NOTIFICATION_ID, new Intent(ACTION_BUGREPORT_SHARING_ACCEPTED),
173                     PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
174             // Simple notification action button clicks are immutable
175             final PendingIntent pendingIntentDecline = PendingIntent.getBroadcast(mContext,
176                     NOTIFICATION_ID, new Intent(ACTION_BUGREPORT_SHARING_DECLINED),
177                     PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
178             builder.addAction(new Notification.Action.Builder(null /* icon */, mContext.getString(
179                         R.string.decline_remote_bugreport_action), pendingIntentDecline).build())
180                     .addAction(new Notification.Action.Builder(null /* icon */, mContext.getString(
181                         R.string.share_remote_bugreport_action), pendingIntentAccept).build())
182                     .setContentTitle(mContext.getString(
183                         R.string.share_remote_bugreport_notification_title))
184                     .setContentText(mContext.getString(
185                         R.string.share_remote_bugreport_notification_message_finished))
186                     .setStyle(new Notification.BigTextStyle().bigText(mContext.getString(
187                         R.string.share_remote_bugreport_notification_message_finished)));
188         }
189 
190         return builder.build();
191     }
192 
193     /**
194      * Initiates bugreport collection.
195      * @return whether collection was initiated successfully.
196      */
requestBugreport()197     public boolean requestBugreport() {
198         if (mRemoteBugreportServiceIsActive.get()
199                 || (mService.getDeviceOwnerRemoteBugreportUriAndHash() != null)) {
200             Slogf.d(LOG_TAG, "Remote bugreport wasn't started because there's already one running");
201             return false;
202         }
203 
204         final long callingIdentity = mInjector.binderClearCallingIdentity();
205         try {
206             long nonce;
207             do {
208                 nonce = mRng.nextLong();
209             } while (nonce == 0);
210             mInjector.getIActivityManager().requestRemoteBugReport(nonce);
211 
212             mRemoteBugreportNonce.set(nonce);
213             mRemoteBugreportServiceIsActive.set(true);
214             mRemoteBugreportSharingAccepted.set(false);
215             registerRemoteBugreportReceivers();
216             notify(NOTIFICATION_BUGREPORT_STARTED);
217             mHandler.postDelayed(mRemoteBugreportTimeoutRunnable, REMOTE_BUGREPORT_TIMEOUT_MILLIS);
218             return true;
219         } catch (RemoteException re) {
220             // should never happen
221             Slogf.e(LOG_TAG, "Failed to make remote calls to start bugreportremote service", re);
222             return false;
223         } finally {
224             mInjector.binderRestoreCallingIdentity(callingIdentity);
225         }
226     }
227 
registerRemoteBugreportReceivers()228     private void registerRemoteBugreportReceivers() {
229         try {
230             final IntentFilter filterFinished =
231                     new IntentFilter(ACTION_REMOTE_BUGREPORT_DISPATCH, BUGREPORT_MIMETYPE);
232             mContext.registerReceiver(mRemoteBugreportFinishedReceiver, filterFinished,
233                     Context.RECEIVER_EXPORTED);
234         } catch (IntentFilter.MalformedMimeTypeException e) {
235             // should never happen, as setting a constant
236             Slogf.w(LOG_TAG, e, "Failed to set type %s", BUGREPORT_MIMETYPE);
237         }
238         final IntentFilter filterConsent = new IntentFilter();
239         filterConsent.addAction(ACTION_BUGREPORT_SHARING_DECLINED);
240         filterConsent.addAction(ACTION_BUGREPORT_SHARING_ACCEPTED);
241         mContext.registerReceiver(mRemoteBugreportConsentReceiver, filterConsent);
242     }
243 
onBugreportFinished(Intent intent)244     private void onBugreportFinished(Intent intent) {
245         long nonce = intent.getLongExtra(DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_NONCE, 0);
246         if (nonce == 0 || mRemoteBugreportNonce.get() != nonce) {
247             Slogf.w(LOG_TAG, "Invalid nonce provided, ignoring " + nonce);
248             return;
249         }
250         mHandler.removeCallbacks(mRemoteBugreportTimeoutRunnable);
251         mRemoteBugreportServiceIsActive.set(false);
252         final Uri bugreportUri = intent.getData();
253         String bugreportUriString = null;
254         if (bugreportUri != null) {
255             bugreportUriString = bugreportUri.toString();
256         }
257         final String bugreportHash = intent.getStringExtra(EXTRA_REMOTE_BUGREPORT_HASH);
258         if (mRemoteBugreportSharingAccepted.get()) {
259             shareBugreportWithDeviceOwnerIfExists(bugreportUriString, bugreportHash);
260             cancelNotification();
261         } else {
262             mService.setDeviceOwnerRemoteBugreportUriAndHash(bugreportUriString, bugreportHash);
263             notify(NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED);
264         }
265         mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver);
266     }
267 
onBugreportFailed()268     private void onBugreportFailed() {
269         mRemoteBugreportServiceIsActive.set(false);
270         mInjector.systemPropertiesSet(CTL_STOP, REMOTE_BUGREPORT_SERVICE);
271         mRemoteBugreportSharingAccepted.set(false);
272         mService.setDeviceOwnerRemoteBugreportUriAndHash(null, null);
273         cancelNotification();
274         final Bundle extras = new Bundle();
275         extras.putInt(DeviceAdminReceiver.EXTRA_BUGREPORT_FAILURE_REASON,
276                 DeviceAdminReceiver.BUGREPORT_FAILURE_FAILED_COMPLETING);
277         mService.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_BUGREPORT_FAILED, extras);
278         mContext.unregisterReceiver(mRemoteBugreportConsentReceiver);
279         mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver);
280     }
281 
onBugreportSharingAccepted()282     private void onBugreportSharingAccepted() {
283         mRemoteBugreportSharingAccepted.set(true);
284         final Pair<String, String> uriAndHash = mService.getDeviceOwnerRemoteBugreportUriAndHash();
285         if (uriAndHash != null) {
286             shareBugreportWithDeviceOwnerIfExists(uriAndHash.first, uriAndHash.second);
287         } else if (mRemoteBugreportServiceIsActive.get()) {
288             notify(NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED);
289         }
290     }
291 
onBugreportSharingDeclined()292     private void onBugreportSharingDeclined() {
293         if (mRemoteBugreportServiceIsActive.get()) {
294             mInjector.systemPropertiesSet(CTL_STOP,
295                     REMOTE_BUGREPORT_SERVICE);
296             mRemoteBugreportServiceIsActive.set(false);
297             mHandler.removeCallbacks(mRemoteBugreportTimeoutRunnable);
298             mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver);
299         }
300         mRemoteBugreportSharingAccepted.set(false);
301         mService.setDeviceOwnerRemoteBugreportUriAndHash(null, null);
302         mService.sendDeviceOwnerCommand(
303                 DeviceAdminReceiver.ACTION_BUGREPORT_SHARING_DECLINED, null);
304     }
305 
shareBugreportWithDeviceOwnerIfExists( String bugreportUriString, String bugreportHash)306     private void shareBugreportWithDeviceOwnerIfExists(
307             String bugreportUriString, String bugreportHash) {
308         try {
309             if (bugreportUriString == null) {
310                 throw new FileNotFoundException();
311             }
312             final Uri bugreportUri = Uri.parse(bugreportUriString);
313             mService.sendBugreportToDeviceOwner(bugreportUri, bugreportHash);
314         } catch (FileNotFoundException e) {
315             final Bundle extras = new Bundle();
316             extras.putInt(DeviceAdminReceiver.EXTRA_BUGREPORT_FAILURE_REASON,
317                     DeviceAdminReceiver.BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE);
318             mService.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_BUGREPORT_FAILED, extras);
319         } finally {
320             mRemoteBugreportSharingAccepted.set(false);
321             mService.setDeviceOwnerRemoteBugreportUriAndHash(null, null);
322         }
323     }
324 
325     /**
326      * Check if a bugreport was collected but not shared before reboot because the user didn't act
327      * upon sharing notification.
328      */
checkForPendingBugreportAfterBoot()329     public void checkForPendingBugreportAfterBoot() {
330         if (mService.getDeviceOwnerRemoteBugreportUriAndHash() == null) {
331             return;
332         }
333         final IntentFilter filterConsent = new IntentFilter();
334         filterConsent.addAction(ACTION_BUGREPORT_SHARING_DECLINED);
335         filterConsent.addAction(ACTION_BUGREPORT_SHARING_ACCEPTED);
336         mContext.registerReceiver(mRemoteBugreportConsentReceiver, filterConsent);
337         notify(NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED);
338     }
339 
notify(@emoteBugreportNotificationType int type)340     private void notify(@RemoteBugreportNotificationType int type) {
341         mInjector.getNotificationManager()
342                 .notifyAsUser(LOG_TAG, NOTIFICATION_ID, buildNotification(type), UserHandle.ALL);
343     }
344 
cancelNotification()345     private void cancelNotification() {
346         mInjector.getNotificationManager()
347                 .cancelAsUser(LOG_TAG, NOTIFICATION_ID, UserHandle.ALL);
348     }
349 }
350