1 /* 2 * Copyright (C) 2022 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.logcat; 18 19 import static android.os.Process.getParentPid; 20 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.app.ActivityManager; 25 import android.app.ActivityManagerInternal; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.pm.PackageManager; 30 import android.os.Build; 31 import android.os.DeadObjectException; 32 import android.os.Handler; 33 import android.os.ILogd; 34 import android.os.Looper; 35 import android.os.Message; 36 import android.os.RemoteException; 37 import android.os.ServiceManager; 38 import android.os.SystemClock; 39 import android.os.UserHandle; 40 import android.os.logcat.ILogcatManagerService; 41 import android.util.ArrayMap; 42 import android.util.Slog; 43 44 import com.android.internal.annotations.VisibleForTesting; 45 import com.android.internal.app.ILogAccessDialogCallback; 46 import com.android.internal.util.ArrayUtils; 47 import com.android.server.LocalServices; 48 import com.android.server.SystemService; 49 50 import java.lang.annotation.Retention; 51 import java.lang.annotation.RetentionPolicy; 52 import java.util.ArrayList; 53 import java.util.Arrays; 54 import java.util.List; 55 import java.util.Map; 56 import java.util.Objects; 57 import java.util.function.Supplier; 58 59 60 /** 61 * Service responsible for managing the access to Logcat. 62 */ 63 public final class LogcatManagerService extends SystemService { 64 private static final String TAG = "LogcatManagerService"; 65 private static final boolean DEBUG = false; 66 private static final String TARGET_PACKAGE_NAME = "com.android.systemui"; 67 private static final String TARGET_ACTIVITY_NAME = 68 "com.android.systemui.logcat.LogAccessDialogActivity"; 69 public static final String EXTRA_CALLBACK = "EXTRA_CALLBACK"; 70 71 /** How long to wait for the user to approve/decline before declining automatically */ 72 @VisibleForTesting 73 static final int PENDING_CONFIRMATION_TIMEOUT_MILLIS = Build.IS_DEBUGGABLE ? 70000 : 400000; 74 75 /** 76 * How long an approved / declined status is valid for. 77 * 78 * After a client has been approved/declined log access, if they try to access logs again within 79 * this timeout, the new request will be automatically approved/declined. 80 * Only after this timeout expires will a new request generate another prompt to the user. 81 **/ 82 @VisibleForTesting 83 static final int STATUS_EXPIRATION_TIMEOUT_MILLIS = 60 * 1000; 84 85 private static final int MSG_LOG_ACCESS_REQUESTED = 0; 86 private static final int MSG_APPROVE_LOG_ACCESS = 1; 87 private static final int MSG_DECLINE_LOG_ACCESS = 2; 88 private static final int MSG_LOG_ACCESS_FINISHED = 3; 89 private static final int MSG_PENDING_TIMEOUT = 4; 90 private static final int MSG_LOG_ACCESS_STATUS_EXPIRED = 5; 91 92 private static final int STATUS_NEW_REQUEST = 0; 93 private static final int STATUS_PENDING = 1; 94 private static final int STATUS_APPROVED = 2; 95 private static final int STATUS_DECLINED = 3; 96 97 @IntDef(prefix = {"STATUS_"}, value = { 98 STATUS_NEW_REQUEST, 99 STATUS_PENDING, 100 STATUS_APPROVED, 101 STATUS_DECLINED, 102 }) 103 @Retention(RetentionPolicy.SOURCE) 104 public @interface LogAccessRequestStatus { 105 } 106 107 private final Context mContext; 108 private final Injector mInjector; 109 private final Supplier<Long> mClock; 110 private final BinderService mBinderService; 111 private final LogAccessDialogCallback mDialogCallback; 112 private final Handler mHandler; 113 private ActivityManagerInternal mActivityManagerInternal; 114 private ILogd mLogdService; 115 116 private static final class LogAccessClient { 117 final int mUid; 118 @NonNull 119 final String mPackageName; 120 LogAccessClient(int uid, @NonNull String packageName)121 LogAccessClient(int uid, @NonNull String packageName) { 122 mUid = uid; 123 mPackageName = packageName; 124 } 125 126 @Override equals(Object o)127 public boolean equals(Object o) { 128 if (this == o) return true; 129 if (!(o instanceof LogAccessClient)) return false; 130 LogAccessClient that = (LogAccessClient) o; 131 return mUid == that.mUid && Objects.equals(mPackageName, that.mPackageName); 132 } 133 134 @Override hashCode()135 public int hashCode() { 136 return Objects.hash(mUid, mPackageName); 137 } 138 139 @Override toString()140 public String toString() { 141 return "LogAccessClient{" 142 + "mUid=" + mUid 143 + ", mPackageName=" + mPackageName 144 + '}'; 145 } 146 } 147 148 private static final class LogAccessRequest { 149 final int mUid; 150 final int mGid; 151 final int mPid; 152 final int mFd; 153 LogAccessRequest(int uid, int gid, int pid, int fd)154 private LogAccessRequest(int uid, int gid, int pid, int fd) { 155 mUid = uid; 156 mGid = gid; 157 mPid = pid; 158 mFd = fd; 159 } 160 161 @Override equals(Object o)162 public boolean equals(Object o) { 163 if (this == o) return true; 164 if (!(o instanceof LogAccessRequest)) return false; 165 LogAccessRequest that = (LogAccessRequest) o; 166 return mUid == that.mUid && mGid == that.mGid && mPid == that.mPid && mFd == that.mFd; 167 } 168 169 @Override hashCode()170 public int hashCode() { 171 return Objects.hash(mUid, mGid, mPid, mFd); 172 } 173 174 @Override toString()175 public String toString() { 176 return "LogAccessRequest{" 177 + "mUid=" + mUid 178 + ", mGid=" + mGid 179 + ", mPid=" + mPid 180 + ", mFd=" + mFd 181 + '}'; 182 } 183 } 184 185 private static final class LogAccessStatus { 186 @LogAccessRequestStatus 187 int mStatus = STATUS_NEW_REQUEST; 188 final List<LogAccessRequest> mPendingRequests = new ArrayList<>(); 189 } 190 191 private final Map<LogAccessClient, LogAccessStatus> mLogAccessStatus = new ArrayMap<>(); 192 private final Map<LogAccessClient, Integer> mActiveLogAccessCount = new ArrayMap<>(); 193 194 private final class BinderService extends ILogcatManagerService.Stub { 195 @Override startThread(int uid, int gid, int pid, int fd)196 public void startThread(int uid, int gid, int pid, int fd) { 197 final LogAccessRequest logAccessRequest = new LogAccessRequest(uid, gid, pid, fd); 198 if (DEBUG) { 199 Slog.d(TAG, "New log access request: " + logAccessRequest); 200 } 201 final Message msg = mHandler.obtainMessage(MSG_LOG_ACCESS_REQUESTED, logAccessRequest); 202 mHandler.sendMessageAtTime(msg, mClock.get()); 203 } 204 205 @Override finishThread(int uid, int gid, int pid, int fd)206 public void finishThread(int uid, int gid, int pid, int fd) { 207 final LogAccessRequest logAccessRequest = new LogAccessRequest(uid, gid, pid, fd); 208 if (DEBUG) { 209 Slog.d(TAG, "Log access finished: " + logAccessRequest); 210 } 211 final Message msg = mHandler.obtainMessage(MSG_LOG_ACCESS_FINISHED, logAccessRequest); 212 mHandler.sendMessageAtTime(msg, mClock.get()); 213 } 214 } 215 216 final class LogAccessDialogCallback extends ILogAccessDialogCallback.Stub { 217 @Override approveAccessForClient(int uid, @NonNull String packageName)218 public void approveAccessForClient(int uid, @NonNull String packageName) { 219 final LogAccessClient client = new LogAccessClient(uid, packageName); 220 if (DEBUG) { 221 Slog.d(TAG, "Approving log access for client: " + client); 222 } 223 final Message msg = mHandler.obtainMessage(MSG_APPROVE_LOG_ACCESS, client); 224 mHandler.sendMessageAtTime(msg, mClock.get()); 225 } 226 227 @Override declineAccessForClient(int uid, @NonNull String packageName)228 public void declineAccessForClient(int uid, @NonNull String packageName) { 229 final LogAccessClient client = new LogAccessClient(uid, packageName); 230 if (DEBUG) { 231 Slog.d(TAG, "Declining log access for client: " + client); 232 } 233 final Message msg = mHandler.obtainMessage(MSG_DECLINE_LOG_ACCESS, client); 234 mHandler.sendMessageAtTime(msg, mClock.get()); 235 } 236 } 237 getLogdService()238 private ILogd getLogdService() { 239 if (mLogdService == null) { 240 mLogdService = mInjector.getLogdService(); 241 } 242 return mLogdService; 243 } 244 245 private static class LogAccessRequestHandler extends Handler { 246 private final LogcatManagerService mService; 247 LogAccessRequestHandler(Looper looper, LogcatManagerService service)248 LogAccessRequestHandler(Looper looper, LogcatManagerService service) { 249 super(looper); 250 mService = service; 251 } 252 253 @Override handleMessage(Message msg)254 public void handleMessage(Message msg) { 255 switch (msg.what) { 256 case MSG_LOG_ACCESS_REQUESTED: { 257 LogAccessRequest request = (LogAccessRequest) msg.obj; 258 mService.onLogAccessRequested(request); 259 break; 260 } 261 case MSG_APPROVE_LOG_ACCESS: { 262 LogAccessClient client = (LogAccessClient) msg.obj; 263 mService.onAccessApprovedForClient(client); 264 break; 265 } 266 case MSG_DECLINE_LOG_ACCESS: { 267 LogAccessClient client = (LogAccessClient) msg.obj; 268 mService.onAccessDeclinedForClient(client); 269 break; 270 } 271 case MSG_LOG_ACCESS_FINISHED: { 272 LogAccessRequest request = (LogAccessRequest) msg.obj; 273 mService.onLogAccessFinished(request); 274 break; 275 } 276 case MSG_PENDING_TIMEOUT: { 277 LogAccessClient client = (LogAccessClient) msg.obj; 278 mService.onPendingTimeoutExpired(client); 279 break; 280 } 281 case MSG_LOG_ACCESS_STATUS_EXPIRED: { 282 LogAccessClient client = (LogAccessClient) msg.obj; 283 mService.onAccessStatusExpired(client); 284 break; 285 } 286 } 287 } 288 } 289 290 static class Injector { createClock()291 protected Supplier<Long> createClock() { 292 return SystemClock::uptimeMillis; 293 } 294 getLooper()295 protected Looper getLooper() { 296 return Looper.getMainLooper(); 297 } 298 getLogdService()299 protected ILogd getLogdService() { 300 return ILogd.Stub.asInterface(ServiceManager.getService("logd")); 301 } 302 } 303 LogcatManagerService(Context context)304 public LogcatManagerService(Context context) { 305 this(context, new Injector()); 306 } 307 LogcatManagerService(Context context, Injector injector)308 public LogcatManagerService(Context context, Injector injector) { 309 super(context); 310 mContext = context; 311 mInjector = injector; 312 mClock = injector.createClock(); 313 mBinderService = new BinderService(); 314 mDialogCallback = new LogAccessDialogCallback(); 315 mHandler = new LogAccessRequestHandler(injector.getLooper(), this); 316 } 317 318 @Override onStart()319 public void onStart() { 320 try { 321 mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); 322 publishBinderService("logcat", mBinderService); 323 } catch (Throwable t) { 324 Slog.e(TAG, "Could not start the LogcatManagerService.", t); 325 } 326 } 327 328 @VisibleForTesting getDialogCallback()329 LogAccessDialogCallback getDialogCallback() { 330 return mDialogCallback; 331 } 332 333 @VisibleForTesting getBinderService()334 ILogcatManagerService getBinderService() { 335 return mBinderService; 336 } 337 338 @Nullable getClientForRequest(LogAccessRequest request)339 private LogAccessClient getClientForRequest(LogAccessRequest request) { 340 final String packageName = getPackageName(request); 341 if (packageName == null) { 342 return null; 343 } 344 345 return new LogAccessClient(request.mUid, packageName); 346 } 347 348 /** 349 * Returns the package name. 350 * If we cannot retrieve the package name, it returns null and we decline the full device log 351 * access 352 */ getPackageName(LogAccessRequest request)353 private String getPackageName(LogAccessRequest request) { 354 PackageManager pm = mContext.getPackageManager(); 355 if (pm == null) { 356 // Decline the logd access if PackageManager is null 357 Slog.e(TAG, "PackageManager is null, declining the logd access"); 358 return null; 359 } 360 361 String[] packageNames = pm.getPackagesForUid(request.mUid); 362 if (ArrayUtils.isEmpty(packageNames)) { 363 // Decline the logd access if the app name is unknown 364 Slog.e(TAG, "Unknown calling package name, declining the logd access"); 365 return null; 366 } 367 368 if (mActivityManagerInternal != null) { 369 int pid = request.mPid; 370 String packageName = mActivityManagerInternal.getPackageNameByPid(pid); 371 while ((packageName == null || !ArrayUtils.contains(packageNames, packageName)) 372 && pid != -1) { 373 pid = getParentPid(pid); 374 packageName = mActivityManagerInternal.getPackageNameByPid(pid); 375 } 376 377 if (packageName != null && ArrayUtils.contains(packageNames, packageName)) { 378 return packageName; 379 } 380 } 381 382 Arrays.sort(packageNames); 383 String firstPackageName = packageNames[0]; 384 if (firstPackageName == null || firstPackageName.isEmpty()) { 385 // Decline the logd access if the package name from uid is unknown 386 Slog.e(TAG, "Unknown calling package name, declining the logd access"); 387 return null; 388 } 389 390 return firstPackageName; 391 } 392 onLogAccessRequested(LogAccessRequest request)393 void onLogAccessRequested(LogAccessRequest request) { 394 final LogAccessClient client = getClientForRequest(request); 395 if (client == null) { 396 declineRequest(request); 397 return; 398 } 399 400 LogAccessStatus logAccessStatus = mLogAccessStatus.get(client); 401 if (logAccessStatus == null) { 402 logAccessStatus = new LogAccessStatus(); 403 mLogAccessStatus.put(client, logAccessStatus); 404 } 405 406 switch (logAccessStatus.mStatus) { 407 case STATUS_NEW_REQUEST: 408 logAccessStatus.mPendingRequests.add(request); 409 processNewLogAccessRequest(client); 410 break; 411 case STATUS_PENDING: 412 logAccessStatus.mPendingRequests.add(request); 413 return; 414 case STATUS_APPROVED: 415 approveRequest(client, request); 416 break; 417 case STATUS_DECLINED: 418 declineRequest(request); 419 break; 420 } 421 } 422 shouldShowConfirmationDialog(LogAccessClient client)423 private boolean shouldShowConfirmationDialog(LogAccessClient client) { 424 // If the process is foreground, show a dialog for user consent 425 final int procState = mActivityManagerInternal.getUidProcessState(client.mUid); 426 return procState == ActivityManager.PROCESS_STATE_TOP; 427 } 428 processNewLogAccessRequest(LogAccessClient client)429 private void processNewLogAccessRequest(LogAccessClient client) { 430 boolean isInstrumented = mActivityManagerInternal.getInstrumentationSourceUid(client.mUid) 431 != android.os.Process.INVALID_UID; 432 // The instrumented apks only run for testing, so we don't check user permission. 433 if (isInstrumented) { 434 onAccessApprovedForClient(client); 435 return; 436 } 437 438 if (!shouldShowConfirmationDialog(client)) { 439 onAccessDeclinedForClient(client); 440 return; 441 } 442 443 final LogAccessStatus logAccessStatus = mLogAccessStatus.get(client); 444 logAccessStatus.mStatus = STATUS_PENDING; 445 446 mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_PENDING_TIMEOUT, client), 447 mClock.get() + PENDING_CONFIRMATION_TIMEOUT_MILLIS); 448 final Intent mIntent = createIntent(client); 449 mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 450 mIntent.setComponent(new ComponentName(TARGET_PACKAGE_NAME, TARGET_ACTIVITY_NAME)); 451 mContext.startActivityAsUser(mIntent, UserHandle.SYSTEM); 452 } 453 onAccessApprovedForClient(LogAccessClient client)454 void onAccessApprovedForClient(LogAccessClient client) { 455 scheduleStatusExpiry(client); 456 457 LogAccessStatus logAccessStatus = mLogAccessStatus.get(client); 458 if (logAccessStatus != null) { 459 for (LogAccessRequest request : logAccessStatus.mPendingRequests) { 460 approveRequest(client, request); 461 } 462 logAccessStatus.mStatus = STATUS_APPROVED; 463 logAccessStatus.mPendingRequests.clear(); 464 } 465 } 466 onAccessDeclinedForClient(LogAccessClient client)467 void onAccessDeclinedForClient(LogAccessClient client) { 468 scheduleStatusExpiry(client); 469 470 LogAccessStatus logAccessStatus = mLogAccessStatus.get(client); 471 if (logAccessStatus != null) { 472 for (LogAccessRequest request : logAccessStatus.mPendingRequests) { 473 declineRequest(request); 474 } 475 logAccessStatus.mStatus = STATUS_DECLINED; 476 logAccessStatus.mPendingRequests.clear(); 477 } 478 } 479 scheduleStatusExpiry(LogAccessClient client)480 private void scheduleStatusExpiry(LogAccessClient client) { 481 mHandler.removeMessages(MSG_PENDING_TIMEOUT, client); 482 mHandler.removeMessages(MSG_LOG_ACCESS_STATUS_EXPIRED, client); 483 mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_LOG_ACCESS_STATUS_EXPIRED, client), 484 mClock.get() + STATUS_EXPIRATION_TIMEOUT_MILLIS); 485 } 486 onPendingTimeoutExpired(LogAccessClient client)487 void onPendingTimeoutExpired(LogAccessClient client) { 488 final LogAccessStatus logAccessStatus = mLogAccessStatus.get(client); 489 if (logAccessStatus != null && logAccessStatus.mStatus == STATUS_PENDING) { 490 onAccessDeclinedForClient(client); 491 } 492 } 493 onAccessStatusExpired(LogAccessClient client)494 void onAccessStatusExpired(LogAccessClient client) { 495 if (DEBUG) { 496 Slog.d(TAG, "Log access status expired for " + client); 497 } 498 mLogAccessStatus.remove(client); 499 } 500 onLogAccessFinished(LogAccessRequest request)501 void onLogAccessFinished(LogAccessRequest request) { 502 final LogAccessClient client = getClientForRequest(request); 503 final int activeCount = mActiveLogAccessCount.getOrDefault(client, 1) - 1; 504 505 if (activeCount == 0) { 506 mActiveLogAccessCount.remove(client); 507 if (DEBUG) { 508 Slog.d(TAG, "Client is no longer accessing logs: " + client); 509 } 510 // TODO This will be used to notify the AppOpsManager that the logd data access 511 // is finished. 512 } else { 513 mActiveLogAccessCount.put(client, activeCount); 514 } 515 } 516 approveRequest(LogAccessClient client, LogAccessRequest request)517 private void approveRequest(LogAccessClient client, LogAccessRequest request) { 518 if (DEBUG) { 519 Slog.d(TAG, "Approving log access: " + request); 520 } 521 try { 522 try { 523 getLogdService().approve(request.mUid, request.mGid, request.mPid, request.mFd); 524 } catch (DeadObjectException e) { 525 // This can happen if logd restarts, so force getting a new connection 526 // to logd and try once more. 527 Slog.w(TAG, "Logd connection no longer valid while approving, trying once more."); 528 mLogdService = null; 529 getLogdService().approve(request.mUid, request.mGid, request.mPid, request.mFd); 530 } 531 Integer activeCount = mActiveLogAccessCount.getOrDefault(client, 0); 532 mActiveLogAccessCount.put(client, activeCount + 1); 533 } catch (RemoteException e) { 534 Slog.e(TAG, "Fails to call remote functions", e); 535 } 536 } 537 declineRequest(LogAccessRequest request)538 private void declineRequest(LogAccessRequest request) { 539 if (DEBUG) { 540 Slog.d(TAG, "Declining log access: " + request); 541 } 542 try { 543 try { 544 getLogdService().decline(request.mUid, request.mGid, request.mPid, request.mFd); 545 } catch (DeadObjectException e) { 546 // This can happen if logd restarts, so force getting a new connection 547 // to logd and try once more. 548 Slog.w(TAG, "Logd connection no longer valid while declining, trying once more."); 549 mLogdService = null; 550 getLogdService().decline(request.mUid, request.mGid, request.mPid, request.mFd); 551 } 552 } catch (RemoteException e) { 553 Slog.e(TAG, "Fails to call remote functions", e); 554 } 555 } 556 557 /** 558 * Create the Intent for LogAccessDialogActivity. 559 */ createIntent(LogAccessClient client)560 public Intent createIntent(LogAccessClient client) { 561 final Intent intent = new Intent(); 562 563 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 564 565 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, client.mPackageName); 566 intent.putExtra(Intent.EXTRA_UID, client.mUid); 567 intent.putExtra(EXTRA_CALLBACK, mDialogCallback.asBinder()); 568 569 return intent; 570 } 571 } 572