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 mInjector.getNotificationManager().cancel(LOG_TAG, NOTIFICATION_ID); 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 mInjector.getNotificationManager().notifyAsUser(LOG_TAG, NOTIFICATION_ID, 217 buildNotification(NOTIFICATION_BUGREPORT_STARTED), UserHandle.ALL); 218 mHandler.postDelayed(mRemoteBugreportTimeoutRunnable, REMOTE_BUGREPORT_TIMEOUT_MILLIS); 219 return true; 220 } catch (RemoteException re) { 221 // should never happen 222 Slogf.e(LOG_TAG, "Failed to make remote calls to start bugreportremote service", re); 223 return false; 224 } finally { 225 mInjector.binderRestoreCallingIdentity(callingIdentity); 226 } 227 } 228 registerRemoteBugreportReceivers()229 private void registerRemoteBugreportReceivers() { 230 try { 231 final IntentFilter filterFinished = 232 new IntentFilter(ACTION_REMOTE_BUGREPORT_DISPATCH, BUGREPORT_MIMETYPE); 233 mContext.registerReceiver(mRemoteBugreportFinishedReceiver, filterFinished, 234 Context.RECEIVER_EXPORTED); 235 } catch (IntentFilter.MalformedMimeTypeException e) { 236 // should never happen, as setting a constant 237 Slogf.w(LOG_TAG, e, "Failed to set type %s", BUGREPORT_MIMETYPE); 238 } 239 final IntentFilter filterConsent = new IntentFilter(); 240 filterConsent.addAction(ACTION_BUGREPORT_SHARING_DECLINED); 241 filterConsent.addAction(ACTION_BUGREPORT_SHARING_ACCEPTED); 242 mContext.registerReceiver(mRemoteBugreportConsentReceiver, filterConsent); 243 } 244 onBugreportFinished(Intent intent)245 private void onBugreportFinished(Intent intent) { 246 long nonce = intent.getLongExtra(DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_NONCE, 0); 247 if (nonce == 0 || mRemoteBugreportNonce.get() != nonce) { 248 Slogf.w(LOG_TAG, "Invalid nonce provided, ignoring " + nonce); 249 return; 250 } 251 mHandler.removeCallbacks(mRemoteBugreportTimeoutRunnable); 252 mRemoteBugreportServiceIsActive.set(false); 253 final Uri bugreportUri = intent.getData(); 254 String bugreportUriString = null; 255 if (bugreportUri != null) { 256 bugreportUriString = bugreportUri.toString(); 257 } 258 final String bugreportHash = intent.getStringExtra(EXTRA_REMOTE_BUGREPORT_HASH); 259 if (mRemoteBugreportSharingAccepted.get()) { 260 shareBugreportWithDeviceOwnerIfExists(bugreportUriString, bugreportHash); 261 mInjector.getNotificationManager().cancel(LOG_TAG, 262 NOTIFICATION_ID); 263 } else { 264 mService.setDeviceOwnerRemoteBugreportUriAndHash(bugreportUriString, bugreportHash); 265 mInjector.getNotificationManager().notifyAsUser(LOG_TAG, NOTIFICATION_ID, 266 buildNotification(NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED), 267 UserHandle.ALL); 268 } 269 mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver); 270 } 271 onBugreportFailed()272 private void onBugreportFailed() { 273 mRemoteBugreportServiceIsActive.set(false); 274 mInjector.systemPropertiesSet(CTL_STOP, REMOTE_BUGREPORT_SERVICE); 275 mRemoteBugreportSharingAccepted.set(false); 276 mService.setDeviceOwnerRemoteBugreportUriAndHash(null, null); 277 mInjector.getNotificationManager().cancel(LOG_TAG, NOTIFICATION_ID); 278 final Bundle extras = new Bundle(); 279 extras.putInt(DeviceAdminReceiver.EXTRA_BUGREPORT_FAILURE_REASON, 280 DeviceAdminReceiver.BUGREPORT_FAILURE_FAILED_COMPLETING); 281 mService.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_BUGREPORT_FAILED, extras); 282 mContext.unregisterReceiver(mRemoteBugreportConsentReceiver); 283 mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver); 284 } 285 onBugreportSharingAccepted()286 private void onBugreportSharingAccepted() { 287 mRemoteBugreportSharingAccepted.set(true); 288 final Pair<String, String> uriAndHash = mService.getDeviceOwnerRemoteBugreportUriAndHash(); 289 if (uriAndHash != null) { 290 shareBugreportWithDeviceOwnerIfExists(uriAndHash.first, uriAndHash.second); 291 } else if (mRemoteBugreportServiceIsActive.get()) { 292 mInjector.getNotificationManager().notifyAsUser(LOG_TAG, NOTIFICATION_ID, 293 buildNotification(NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED), 294 UserHandle.ALL); 295 } 296 } 297 onBugreportSharingDeclined()298 private void onBugreportSharingDeclined() { 299 if (mRemoteBugreportServiceIsActive.get()) { 300 mInjector.systemPropertiesSet(CTL_STOP, 301 REMOTE_BUGREPORT_SERVICE); 302 mRemoteBugreportServiceIsActive.set(false); 303 mHandler.removeCallbacks(mRemoteBugreportTimeoutRunnable); 304 mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver); 305 } 306 mRemoteBugreportSharingAccepted.set(false); 307 mService.setDeviceOwnerRemoteBugreportUriAndHash(null, null); 308 mService.sendDeviceOwnerCommand( 309 DeviceAdminReceiver.ACTION_BUGREPORT_SHARING_DECLINED, null); 310 } 311 shareBugreportWithDeviceOwnerIfExists( String bugreportUriString, String bugreportHash)312 private void shareBugreportWithDeviceOwnerIfExists( 313 String bugreportUriString, String bugreportHash) { 314 try { 315 if (bugreportUriString == null) { 316 throw new FileNotFoundException(); 317 } 318 final Uri bugreportUri = Uri.parse(bugreportUriString); 319 mService.sendBugreportToDeviceOwner(bugreportUri, bugreportHash); 320 } catch (FileNotFoundException e) { 321 final Bundle extras = new Bundle(); 322 extras.putInt(DeviceAdminReceiver.EXTRA_BUGREPORT_FAILURE_REASON, 323 DeviceAdminReceiver.BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE); 324 mService.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_BUGREPORT_FAILED, extras); 325 } finally { 326 mRemoteBugreportSharingAccepted.set(false); 327 mService.setDeviceOwnerRemoteBugreportUriAndHash(null, null); 328 } 329 } 330 331 /** 332 * Check if a bugreport was collected but not shared before reboot because the user didn't act 333 * upon sharing notification. 334 */ checkForPendingBugreportAfterBoot()335 public void checkForPendingBugreportAfterBoot() { 336 if (mService.getDeviceOwnerRemoteBugreportUriAndHash() == null) { 337 return; 338 } 339 final IntentFilter filterConsent = new IntentFilter(); 340 filterConsent.addAction(ACTION_BUGREPORT_SHARING_DECLINED); 341 filterConsent.addAction(ACTION_BUGREPORT_SHARING_ACCEPTED); 342 mContext.registerReceiver(mRemoteBugreportConsentReceiver, filterConsent); 343 mInjector.getNotificationManager().notifyAsUser(LOG_TAG, NOTIFICATION_ID, 344 buildNotification(NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED), UserHandle.ALL); 345 } 346 } 347