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