1 /* 2 * Copyright 2019 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 package com.android.server.blob; 17 18 import static android.app.blob.BlobStoreManager.COMMIT_RESULT_ERROR; 19 import static android.app.blob.BlobStoreManager.COMMIT_RESULT_SUCCESS; 20 import static android.app.blob.XmlTags.ATTR_VERSION; 21 import static android.app.blob.XmlTags.TAG_BLOB; 22 import static android.app.blob.XmlTags.TAG_BLOBS; 23 import static android.app.blob.XmlTags.TAG_SESSION; 24 import static android.app.blob.XmlTags.TAG_SESSIONS; 25 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; 26 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; 27 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; 28 import static android.os.UserHandle.USER_CURRENT; 29 import static android.os.UserHandle.USER_NULL; 30 31 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; 32 import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_ID; 33 import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_SIZE; 34 import static com.android.server.blob.BlobStoreConfig.LOGV; 35 import static com.android.server.blob.BlobStoreConfig.TAG; 36 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ALLOW_ACCESS_ACROSS_USERS; 37 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT; 38 import static com.android.server.blob.BlobStoreConfig.getAdjustedCommitTimeMs; 39 import static com.android.server.blob.BlobStoreConfig.getDeletionOnLastLeaseDelayMs; 40 import static com.android.server.blob.BlobStoreConfig.getMaxActiveSessions; 41 import static com.android.server.blob.BlobStoreConfig.getMaxCommittedBlobs; 42 import static com.android.server.blob.BlobStoreConfig.getMaxLeasedBlobs; 43 import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED; 44 import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED; 45 import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_INVALID; 46 import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_VALID; 47 import static com.android.server.blob.BlobStoreSession.stateToString; 48 import static com.android.server.blob.BlobStoreUtils.getDescriptionResourceId; 49 import static com.android.server.blob.BlobStoreUtils.getPackageResources; 50 51 import android.annotation.CurrentTimeSecondsLong; 52 import android.annotation.IdRes; 53 import android.annotation.IntRange; 54 import android.annotation.NonNull; 55 import android.annotation.Nullable; 56 import android.annotation.UserIdInt; 57 import android.app.ActivityManager; 58 import android.app.ActivityManagerInternal; 59 import android.app.StatsManager; 60 import android.app.blob.BlobHandle; 61 import android.app.blob.BlobInfo; 62 import android.app.blob.IBlobStoreManager; 63 import android.app.blob.IBlobStoreSession; 64 import android.app.blob.LeaseInfo; 65 import android.content.BroadcastReceiver; 66 import android.content.Context; 67 import android.content.Intent; 68 import android.content.IntentFilter; 69 import android.content.pm.ApplicationInfo; 70 import android.content.pm.PackageManagerInternal; 71 import android.content.pm.PackageStats; 72 import android.content.res.ResourceId; 73 import android.content.res.Resources; 74 import android.os.Binder; 75 import android.os.Handler; 76 import android.os.HandlerThread; 77 import android.os.LimitExceededException; 78 import android.os.ParcelFileDescriptor; 79 import android.os.ParcelableException; 80 import android.os.Process; 81 import android.os.RemoteCallback; 82 import android.os.SystemClock; 83 import android.os.UserHandle; 84 import android.os.UserManager; 85 import android.util.ArrayMap; 86 import android.util.ArraySet; 87 import android.util.AtomicFile; 88 import android.util.ExceptionUtils; 89 import android.util.IndentingPrintWriter; 90 import android.util.LongSparseArray; 91 import android.util.Slog; 92 import android.util.SparseArray; 93 import android.util.StatsEvent; 94 import android.util.Xml; 95 96 import com.android.internal.annotations.GuardedBy; 97 import com.android.internal.annotations.VisibleForTesting; 98 import com.android.internal.os.BackgroundThread; 99 import com.android.internal.util.CollectionUtils; 100 import com.android.internal.util.DumpUtils; 101 import com.android.internal.util.FastXmlSerializer; 102 import com.android.internal.util.FrameworkStatsLog; 103 import com.android.internal.util.Preconditions; 104 import com.android.internal.util.XmlUtils; 105 import com.android.internal.util.function.pooled.PooledLambda; 106 import com.android.server.LocalManagerRegistry; 107 import com.android.server.LocalServices; 108 import com.android.server.ServiceThread; 109 import com.android.server.SystemService; 110 import com.android.server.Watchdog; 111 import com.android.server.blob.BlobMetadata.Committer; 112 import com.android.server.pm.UserManagerInternal; 113 import com.android.server.usage.StorageStatsManagerLocal; 114 import com.android.server.usage.StorageStatsManagerLocal.StorageStatsAugmenter; 115 116 import org.xmlpull.v1.XmlPullParser; 117 import org.xmlpull.v1.XmlSerializer; 118 119 import java.io.File; 120 import java.io.FileDescriptor; 121 import java.io.FileInputStream; 122 import java.io.FileOutputStream; 123 import java.io.IOException; 124 import java.io.PrintWriter; 125 import java.lang.ref.WeakReference; 126 import java.nio.charset.StandardCharsets; 127 import java.security.SecureRandom; 128 import java.util.ArrayList; 129 import java.util.Arrays; 130 import java.util.List; 131 import java.util.Objects; 132 import java.util.Random; 133 import java.util.Set; 134 import java.util.concurrent.atomic.AtomicInteger; 135 import java.util.concurrent.atomic.AtomicLong; 136 import java.util.function.BiConsumer; 137 import java.util.function.Consumer; 138 import java.util.function.Function; 139 140 /** 141 * Service responsible for maintaining and facilitating access to data blobs published by apps. 142 */ 143 public class BlobStoreManagerService extends SystemService { 144 145 private final Object mBlobsLock = new Object(); 146 147 // Contains data of userId -> {sessionId -> {BlobStoreSession}}. 148 @GuardedBy("mBlobsLock") 149 private final SparseArray<LongSparseArray<BlobStoreSession>> mSessions = new SparseArray<>(); 150 151 @GuardedBy("mBlobsLock") 152 private long mCurrentMaxSessionId; 153 154 // Contains data of BlobHandle -> BlobMetadata. 155 @GuardedBy("mBlobsLock") 156 private final ArrayMap<BlobHandle, BlobMetadata> mBlobsMap = new ArrayMap<>(); 157 158 // Contains all ids that are currently in use. 159 @GuardedBy("mBlobsLock") 160 private final ArraySet<Long> mActiveBlobIds = new ArraySet<>(); 161 // Contains all ids that are currently in use and those that were in use but got deleted in the 162 // current boot session. 163 @GuardedBy("mBlobsLock") 164 private final ArraySet<Long> mKnownBlobIds = new ArraySet<>(); 165 166 // Random number generator for new session ids. 167 private final Random mRandom = new SecureRandom(); 168 169 private final Context mContext; 170 private final Handler mHandler; 171 private final Handler mBackgroundHandler; 172 private final Injector mInjector; 173 private final SessionStateChangeListener mSessionStateChangeListener = 174 new SessionStateChangeListener(); 175 176 private PackageManagerInternal mPackageManagerInternal; 177 private StatsManager mStatsManager; 178 private StatsPullAtomCallbackImpl mStatsCallbackImpl = new StatsPullAtomCallbackImpl(); 179 180 private final Runnable mSaveBlobsInfoRunnable = this::writeBlobsInfo; 181 private final Runnable mSaveSessionsRunnable = this::writeBlobSessions; 182 BlobStoreManagerService(Context context)183 public BlobStoreManagerService(Context context) { 184 this(context, new Injector()); 185 } 186 187 @VisibleForTesting BlobStoreManagerService(Context context, Injector injector)188 BlobStoreManagerService(Context context, Injector injector) { 189 super(context); 190 191 mContext = context; 192 mInjector = injector; 193 mHandler = mInjector.initializeMessageHandler(); 194 mBackgroundHandler = mInjector.getBackgroundHandler(); 195 } 196 initializeMessageHandler()197 private static Handler initializeMessageHandler() { 198 final HandlerThread handlerThread = new ServiceThread(TAG, 199 Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */); 200 handlerThread.start(); 201 final Handler handler = new Handler(handlerThread.getLooper()); 202 Watchdog.getInstance().addThread(handler); 203 return handler; 204 } 205 206 @Override onStart()207 public void onStart() { 208 publishBinderService(Context.BLOB_STORE_SERVICE, new Stub()); 209 LocalServices.addService(BlobStoreManagerInternal.class, new LocalService()); 210 211 mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); 212 mStatsManager = getContext().getSystemService(StatsManager.class); 213 registerReceivers(); 214 LocalManagerRegistry.getManager(StorageStatsManagerLocal.class) 215 .registerStorageStatsAugmenter(new BlobStorageStatsAugmenter(), TAG); 216 } 217 218 @Override onBootPhase(int phase)219 public void onBootPhase(int phase) { 220 if (phase == PHASE_ACTIVITY_MANAGER_READY) { 221 BlobStoreConfig.initialize(mContext); 222 } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { 223 synchronized (mBlobsLock) { 224 final SparseArray<SparseArray<String>> allPackages = getAllPackages(); 225 readBlobSessionsLocked(allPackages); 226 readBlobsInfoLocked(allPackages); 227 } 228 registerBlobStorePuller(); 229 } else if (phase == PHASE_BOOT_COMPLETED) { 230 BlobStoreIdleJobService.schedule(mContext); 231 } 232 } 233 234 @GuardedBy("mBlobsLock") generateNextSessionIdLocked()235 private long generateNextSessionIdLocked() { 236 // Logic borrowed from PackageInstallerService. 237 int n = 0; 238 long sessionId; 239 do { 240 final long randomLong = mRandom.nextLong(); 241 sessionId = (randomLong == Long.MIN_VALUE) ? INVALID_BLOB_ID : Math.abs(randomLong); 242 if (mKnownBlobIds.indexOf(sessionId) < 0 && sessionId != INVALID_BLOB_ID) { 243 return sessionId; 244 } 245 } while (n++ < 32); 246 throw new IllegalStateException("Failed to allocate session ID"); 247 } 248 registerReceivers()249 private void registerReceivers() { 250 final IntentFilter packageChangedFilter = new IntentFilter(); 251 packageChangedFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); 252 packageChangedFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED); 253 packageChangedFilter.addDataScheme("package"); 254 mContext.registerReceiverAsUser(new PackageChangedReceiver(), UserHandle.ALL, 255 packageChangedFilter, null, mHandler); 256 257 final IntentFilter userActionFilter = new IntentFilter(); 258 userActionFilter.addAction(Intent.ACTION_USER_REMOVED); 259 mContext.registerReceiverAsUser(new UserActionReceiver(), UserHandle.ALL, 260 userActionFilter, null, mHandler); 261 } 262 263 @GuardedBy("mBlobsLock") getUserSessionsLocked(int userId)264 private LongSparseArray<BlobStoreSession> getUserSessionsLocked(int userId) { 265 LongSparseArray<BlobStoreSession> userSessions = mSessions.get(userId); 266 if (userSessions == null) { 267 userSessions = new LongSparseArray<>(); 268 mSessions.put(userId, userSessions); 269 } 270 return userSessions; 271 } 272 273 @VisibleForTesting addUserSessionsForTest(LongSparseArray<BlobStoreSession> userSessions, int userId)274 void addUserSessionsForTest(LongSparseArray<BlobStoreSession> userSessions, int userId) { 275 synchronized (mBlobsLock) { 276 mSessions.put(userId, userSessions); 277 } 278 } 279 280 @VisibleForTesting getBlobForTest(BlobHandle blobHandle)281 BlobMetadata getBlobForTest(BlobHandle blobHandle) { 282 synchronized (mBlobsLock) { 283 return mBlobsMap.get(blobHandle); 284 } 285 } 286 287 @VisibleForTesting getBlobsCountForTest()288 int getBlobsCountForTest() { 289 synchronized (mBlobsLock) { 290 return mBlobsMap.size(); 291 } 292 } 293 294 @VisibleForTesting addActiveIdsForTest(long... activeIds)295 void addActiveIdsForTest(long... activeIds) { 296 synchronized (mBlobsLock) { 297 for (long id : activeIds) { 298 addActiveBlobIdLocked(id); 299 } 300 } 301 } 302 303 @VisibleForTesting getActiveIdsForTest()304 Set<Long> getActiveIdsForTest() { 305 synchronized (mBlobsLock) { 306 return mActiveBlobIds; 307 } 308 } 309 310 @VisibleForTesting getKnownIdsForTest()311 Set<Long> getKnownIdsForTest() { 312 synchronized (mBlobsLock) { 313 return mKnownBlobIds; 314 } 315 } 316 317 @GuardedBy("mBlobsLock") addSessionForUserLocked(BlobStoreSession session, int userId)318 private void addSessionForUserLocked(BlobStoreSession session, int userId) { 319 getUserSessionsLocked(userId).put(session.getSessionId(), session); 320 addActiveBlobIdLocked(session.getSessionId()); 321 } 322 323 @GuardedBy("mBlobsLock") 324 @VisibleForTesting addBlobLocked(BlobMetadata blobMetadata)325 void addBlobLocked(BlobMetadata blobMetadata) { 326 mBlobsMap.put(blobMetadata.getBlobHandle(), blobMetadata); 327 addActiveBlobIdLocked(blobMetadata.getBlobId()); 328 } 329 330 @GuardedBy("mBlobsLock") addActiveBlobIdLocked(long id)331 private void addActiveBlobIdLocked(long id) { 332 mActiveBlobIds.add(id); 333 mKnownBlobIds.add(id); 334 } 335 336 @GuardedBy("mBlobsLock") getSessionsCountLocked(int uid, String packageName)337 private int getSessionsCountLocked(int uid, String packageName) { 338 // TODO: Maintain a counter instead of traversing all the sessions 339 final AtomicInteger sessionsCount = new AtomicInteger(0); 340 forEachSessionInUser(session -> { 341 if (session.getOwnerUid() == uid && session.getOwnerPackageName().equals(packageName)) { 342 sessionsCount.getAndIncrement(); 343 } 344 }, UserHandle.getUserId(uid)); 345 return sessionsCount.get(); 346 } 347 createSessionInternal(BlobHandle blobHandle, int callingUid, String callingPackage)348 private long createSessionInternal(BlobHandle blobHandle, 349 int callingUid, String callingPackage) { 350 synchronized (mBlobsLock) { 351 final int sessionsCount = getSessionsCountLocked(callingUid, callingPackage); 352 if (sessionsCount >= getMaxActiveSessions()) { 353 throw new LimitExceededException("Too many active sessions for the caller: " 354 + sessionsCount); 355 } 356 // TODO: throw if there is already an active session associated with blobHandle. 357 final long sessionId = generateNextSessionIdLocked(); 358 final BlobStoreSession session = new BlobStoreSession(mContext, 359 sessionId, blobHandle, callingUid, callingPackage, 360 mSessionStateChangeListener); 361 addSessionForUserLocked(session, UserHandle.getUserId(callingUid)); 362 if (LOGV) { 363 Slog.v(TAG, "Created session for " + blobHandle 364 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); 365 } 366 writeBlobSessionsAsync(); 367 return sessionId; 368 } 369 } 370 openSessionInternal(long sessionId, int callingUid, String callingPackage)371 private BlobStoreSession openSessionInternal(long sessionId, 372 int callingUid, String callingPackage) { 373 final BlobStoreSession session; 374 synchronized (mBlobsLock) { 375 session = getUserSessionsLocked( 376 UserHandle.getUserId(callingUid)).get(sessionId); 377 if (session == null || !session.hasAccess(callingUid, callingPackage) 378 || session.isFinalized()) { 379 throw new SecurityException("Session not found: " + sessionId); 380 } 381 } 382 session.open(); 383 return session; 384 } 385 abandonSessionInternal(long sessionId, int callingUid, String callingPackage)386 private void abandonSessionInternal(long sessionId, 387 int callingUid, String callingPackage) { 388 synchronized (mBlobsLock) { 389 final BlobStoreSession session = openSessionInternal(sessionId, 390 callingUid, callingPackage); 391 session.open(); 392 session.abandon(); 393 if (LOGV) { 394 Slog.v(TAG, "Abandoned session with id " + sessionId 395 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); 396 } 397 writeBlobSessionsAsync(); 398 } 399 } 400 openBlobInternal(BlobHandle blobHandle, int callingUid, String callingPackage)401 private ParcelFileDescriptor openBlobInternal(BlobHandle blobHandle, int callingUid, 402 String callingPackage) throws IOException { 403 synchronized (mBlobsLock) { 404 final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle); 405 if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller( 406 callingPackage, callingUid)) { 407 if (blobMetadata == null) { 408 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid, 409 INVALID_BLOB_ID, INVALID_BLOB_SIZE, 410 FrameworkStatsLog.BLOB_OPENED__RESULT__BLOB_DNE); 411 } else { 412 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid, 413 blobMetadata.getBlobId(), blobMetadata.getSize(), 414 FrameworkStatsLog.BLOB_OPENED__RESULT__ACCESS_NOT_ALLOWED); 415 } 416 throw new SecurityException("Caller not allowed to access " + blobHandle 417 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); 418 } 419 420 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid, 421 blobMetadata.getBlobId(), blobMetadata.getSize(), 422 FrameworkStatsLog.BLOB_OPENED__RESULT__SUCCESS); 423 424 return blobMetadata.openForRead(callingPackage, callingUid); 425 } 426 } 427 428 @GuardedBy("mBlobsLock") getCommittedBlobsCountLocked(int uid, String packageName)429 private int getCommittedBlobsCountLocked(int uid, String packageName) { 430 // TODO: Maintain a counter instead of traversing all the blobs 431 final AtomicInteger blobsCount = new AtomicInteger(0); 432 forEachBlobLocked(blobMetadata -> { 433 if (blobMetadata.isACommitter(packageName, uid)) { 434 blobsCount.getAndIncrement(); 435 } 436 }); 437 return blobsCount.get(); 438 } 439 440 @GuardedBy("mBlobsLock") getLeasedBlobsCountLocked(int uid, String packageName)441 private int getLeasedBlobsCountLocked(int uid, String packageName) { 442 // TODO: Maintain a counter instead of traversing all the blobs 443 final AtomicInteger blobsCount = new AtomicInteger(0); 444 forEachBlobLocked(blobMetadata -> { 445 if (blobMetadata.isALeasee(packageName, uid)) { 446 blobsCount.getAndIncrement(); 447 } 448 }); 449 return blobsCount.get(); 450 } 451 acquireLeaseInternal(BlobHandle blobHandle, int descriptionResId, CharSequence description, long leaseExpiryTimeMillis, int callingUid, String callingPackage)452 private void acquireLeaseInternal(BlobHandle blobHandle, int descriptionResId, 453 CharSequence description, long leaseExpiryTimeMillis, 454 int callingUid, String callingPackage) { 455 synchronized (mBlobsLock) { 456 final int leasesCount = getLeasedBlobsCountLocked(callingUid, callingPackage); 457 if (leasesCount >= getMaxLeasedBlobs()) { 458 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, 459 INVALID_BLOB_ID, INVALID_BLOB_SIZE, 460 FrameworkStatsLog.BLOB_LEASED__RESULT__COUNT_LIMIT_EXCEEDED); 461 throw new LimitExceededException("Too many leased blobs for the caller: " 462 + leasesCount); 463 } 464 if (leaseExpiryTimeMillis != 0 && blobHandle.expiryTimeMillis != 0 465 && leaseExpiryTimeMillis > blobHandle.expiryTimeMillis) { 466 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, 467 INVALID_BLOB_ID, INVALID_BLOB_SIZE, 468 FrameworkStatsLog.BLOB_LEASED__RESULT__LEASE_EXPIRY_INVALID); 469 throw new IllegalArgumentException( 470 "Lease expiry cannot be later than blobs expiry time"); 471 } 472 473 final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle); 474 if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller( 475 callingPackage, callingUid)) { 476 if (blobMetadata == null) { 477 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, 478 INVALID_BLOB_ID, INVALID_BLOB_SIZE, 479 FrameworkStatsLog.BLOB_LEASED__RESULT__BLOB_DNE); 480 } else { 481 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, 482 blobMetadata.getBlobId(), blobMetadata.getSize(), 483 FrameworkStatsLog.BLOB_LEASED__RESULT__ACCESS_NOT_ALLOWED); 484 } 485 throw new SecurityException("Caller not allowed to access " + blobHandle 486 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); 487 } 488 489 if (blobMetadata.getSize() 490 > getRemainingLeaseQuotaBytesInternal(callingUid, callingPackage)) { 491 492 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, 493 blobMetadata.getBlobId(), blobMetadata.getSize(), 494 FrameworkStatsLog.BLOB_LEASED__RESULT__DATA_SIZE_LIMIT_EXCEEDED); 495 throw new LimitExceededException("Total amount of data with an active lease" 496 + " is exceeding the max limit"); 497 } 498 499 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, 500 blobMetadata.getBlobId(), blobMetadata.getSize(), 501 FrameworkStatsLog.BLOB_LEASED__RESULT__SUCCESS); 502 503 blobMetadata.addOrReplaceLeasee(callingPackage, callingUid, 504 descriptionResId, description, leaseExpiryTimeMillis); 505 if (LOGV) { 506 Slog.v(TAG, "Acquired lease on " + blobHandle 507 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); 508 } 509 writeBlobsInfoAsync(); 510 } 511 } 512 513 @VisibleForTesting 514 @GuardedBy("mBlobsLock") getTotalUsageBytesLocked(int callingUid, String callingPackage)515 long getTotalUsageBytesLocked(int callingUid, String callingPackage) { 516 final AtomicLong totalBytes = new AtomicLong(0); 517 forEachBlobLocked((blobMetadata) -> { 518 if (blobMetadata.isALeasee(callingPackage, callingUid)) { 519 totalBytes.getAndAdd(blobMetadata.getSize()); 520 } 521 }); 522 return totalBytes.get(); 523 } 524 releaseLeaseInternal(BlobHandle blobHandle, int callingUid, String callingPackage)525 private void releaseLeaseInternal(BlobHandle blobHandle, int callingUid, 526 String callingPackage) { 527 synchronized (mBlobsLock) { 528 final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle); 529 if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller( 530 callingPackage, callingUid)) { 531 throw new SecurityException("Caller not allowed to access " + blobHandle 532 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); 533 } 534 blobMetadata.removeLeasee(callingPackage, callingUid); 535 if (LOGV) { 536 Slog.v(TAG, "Released lease on " + blobHandle 537 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); 538 } 539 if (!blobMetadata.hasValidLeases()) { 540 mHandler.postDelayed(() -> { 541 synchronized (mBlobsLock) { 542 // Check if blobMetadata object is still valid. If it is not, then 543 // it means that it was already deleted and nothing else to do here. 544 if (!Objects.equals(mBlobsMap.get(blobHandle), blobMetadata)) { 545 return; 546 } 547 if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) { 548 deleteBlobLocked(blobMetadata); 549 mBlobsMap.remove(blobHandle); 550 } 551 writeBlobsInfoAsync(); 552 } 553 }, getDeletionOnLastLeaseDelayMs()); 554 } 555 writeBlobsInfoAsync(); 556 } 557 } 558 releaseAllLeasesInternal(int callingUid, String callingPackage)559 private void releaseAllLeasesInternal(int callingUid, String callingPackage) { 560 synchronized (mBlobsLock) { 561 // Remove the package from the leasee list 562 mBlobsMap.forEach((blobHandle, blobMetadata) -> { 563 blobMetadata.removeLeasee(callingPackage, callingUid); 564 }); 565 writeBlobsInfoAsync(); 566 567 if (LOGV) { 568 Slog.v(TAG, "Release all leases associated with pkg=" 569 + callingPackage + ", uid=" + callingUid); 570 } 571 } 572 } 573 getRemainingLeaseQuotaBytesInternal(int callingUid, String callingPackage)574 private long getRemainingLeaseQuotaBytesInternal(int callingUid, String callingPackage) { 575 synchronized (mBlobsLock) { 576 final long remainingQuota = BlobStoreConfig.getAppDataBytesLimit() 577 - getTotalUsageBytesLocked(callingUid, callingPackage); 578 return remainingQuota > 0 ? remainingQuota : 0; 579 } 580 } 581 queryBlobsForUserInternal(int userId)582 private List<BlobInfo> queryBlobsForUserInternal(int userId) { 583 final ArrayList<BlobInfo> blobInfos = new ArrayList<>(); 584 synchronized (mBlobsLock) { 585 final ArrayMap<String, WeakReference<Resources>> resources = new ArrayMap<>(); 586 final Function<String, Resources> resourcesGetter = (packageName) -> { 587 final WeakReference<Resources> resourcesRef = resources.get(packageName); 588 Resources packageResources = resourcesRef == null ? null : resourcesRef.get(); 589 if (packageResources == null) { 590 packageResources = getPackageResources(mContext, packageName, userId); 591 resources.put(packageName, new WeakReference<>(packageResources)); 592 } 593 return packageResources; 594 }; 595 forEachBlobLocked((blobHandle, blobMetadata) -> { 596 if (!blobMetadata.hasACommitterOrLeaseeInUser(userId)) { 597 return; 598 } 599 final ArrayList<LeaseInfo> leaseInfos = new ArrayList<>(); 600 blobMetadata.forEachLeasee(leasee -> { 601 if (!leasee.isStillValid()) { 602 return; 603 } 604 if (userId != UserHandle.getUserId(leasee.uid)) { 605 return; 606 } 607 final int descriptionResId = leasee.descriptionResEntryName == null 608 ? Resources.ID_NULL 609 : getDescriptionResourceId(resourcesGetter.apply(leasee.packageName), 610 leasee.descriptionResEntryName, leasee.packageName); 611 final long expiryTimeMs = leasee.expiryTimeMillis == 0 612 ? blobHandle.getExpiryTimeMillis() : leasee.expiryTimeMillis; 613 leaseInfos.add(new LeaseInfo(leasee.packageName, expiryTimeMs, 614 descriptionResId, leasee.description)); 615 }); 616 blobInfos.add(new BlobInfo(blobMetadata.getBlobId(), 617 blobHandle.getExpiryTimeMillis(), blobHandle.getLabel(), 618 blobMetadata.getSize(), leaseInfos)); 619 }); 620 } 621 return blobInfos; 622 } 623 deleteBlobInternal(long blobId)624 private void deleteBlobInternal(long blobId) { 625 synchronized (mBlobsLock) { 626 mBlobsMap.entrySet().removeIf(entry -> { 627 final BlobMetadata blobMetadata = entry.getValue(); 628 if (blobMetadata.getBlobId() == blobId) { 629 deleteBlobLocked(blobMetadata); 630 return true; 631 } 632 return false; 633 }); 634 writeBlobsInfoAsync(); 635 } 636 } 637 getLeasedBlobsInternal(int callingUid, @NonNull String callingPackage)638 private List<BlobHandle> getLeasedBlobsInternal(int callingUid, 639 @NonNull String callingPackage) { 640 final ArrayList<BlobHandle> leasedBlobs = new ArrayList<>(); 641 synchronized (mBlobsLock) { 642 forEachBlobLocked(blobMetadata -> { 643 if (blobMetadata.isALeasee(callingPackage, callingUid)) { 644 leasedBlobs.add(blobMetadata.getBlobHandle()); 645 } 646 }); 647 } 648 return leasedBlobs; 649 } 650 getLeaseInfoInternal(BlobHandle blobHandle, int callingUid, @NonNull String callingPackage)651 private LeaseInfo getLeaseInfoInternal(BlobHandle blobHandle, 652 int callingUid, @NonNull String callingPackage) { 653 synchronized (mBlobsLock) { 654 final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle); 655 if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller( 656 callingPackage, callingUid)) { 657 throw new SecurityException("Caller not allowed to access " + blobHandle 658 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); 659 } 660 return blobMetadata.getLeaseInfo(callingPackage, callingUid); 661 } 662 } 663 verifyCallingPackage(int callingUid, String callingPackage)664 private void verifyCallingPackage(int callingUid, String callingPackage) { 665 if (mPackageManagerInternal.getPackageUid( 666 callingPackage, 0, UserHandle.getUserId(callingUid)) != callingUid) { 667 throw new SecurityException("Specified calling package [" + callingPackage 668 + "] does not match the calling uid " + callingUid); 669 } 670 } 671 672 class SessionStateChangeListener { onStateChanged(@onNull BlobStoreSession session)673 public void onStateChanged(@NonNull BlobStoreSession session) { 674 mHandler.post(PooledLambda.obtainRunnable( 675 BlobStoreManagerService::onStateChangedInternal, 676 BlobStoreManagerService.this, session).recycleOnUse()); 677 } 678 } 679 onStateChangedInternal(@onNull BlobStoreSession session)680 private void onStateChangedInternal(@NonNull BlobStoreSession session) { 681 switch (session.getState()) { 682 case STATE_ABANDONED: 683 case STATE_VERIFIED_INVALID: 684 synchronized (mBlobsLock) { 685 deleteSessionLocked(session); 686 getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())) 687 .remove(session.getSessionId()); 688 if (LOGV) { 689 Slog.v(TAG, "Session is invalid; deleted " + session); 690 } 691 } 692 break; 693 case STATE_COMMITTED: 694 mBackgroundHandler.post(() -> { 695 session.computeDigest(); 696 mHandler.post(PooledLambda.obtainRunnable( 697 BlobStoreSession::verifyBlobData, session).recycleOnUse()); 698 }); 699 break; 700 case STATE_VERIFIED_VALID: 701 synchronized (mBlobsLock) { 702 final int committedBlobsCount = getCommittedBlobsCountLocked( 703 session.getOwnerUid(), session.getOwnerPackageName()); 704 if (committedBlobsCount >= getMaxCommittedBlobs()) { 705 Slog.d(TAG, "Failed to commit: too many committed blobs. count: " 706 + committedBlobsCount + "; blob: " + session); 707 session.sendCommitCallbackResult(COMMIT_RESULT_ERROR); 708 deleteSessionLocked(session); 709 getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())) 710 .remove(session.getSessionId()); 711 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED, 712 session.getOwnerUid(), session.getSessionId(), session.getSize(), 713 FrameworkStatsLog.BLOB_COMMITTED__RESULT__COUNT_LIMIT_EXCEEDED); 714 break; 715 } 716 final BlobMetadata blob; 717 final int blobIndex = mBlobsMap.indexOfKey(session.getBlobHandle()); 718 if (blobIndex >= 0) { 719 blob = mBlobsMap.valueAt(blobIndex); 720 } else { 721 blob = new BlobMetadata(mContext, session.getSessionId(), 722 session.getBlobHandle()); 723 addBlobLocked(blob); 724 } 725 final Committer existingCommitter = blob.getExistingCommitter( 726 session.getOwnerPackageName(), session.getOwnerUid()); 727 final long existingCommitTimeMs = 728 (existingCommitter == null) ? 0 : existingCommitter.getCommitTimeMs(); 729 final Committer newCommitter = new Committer(session.getOwnerPackageName(), 730 session.getOwnerUid(), session.getBlobAccessMode(), 731 getAdjustedCommitTimeMs(existingCommitTimeMs, 732 System.currentTimeMillis())); 733 blob.addOrReplaceCommitter(newCommitter); 734 try { 735 writeBlobsInfoLocked(); 736 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED, 737 session.getOwnerUid(), blob.getBlobId(), blob.getSize(), 738 FrameworkStatsLog.BLOB_COMMITTED__RESULT__SUCCESS); 739 session.sendCommitCallbackResult(COMMIT_RESULT_SUCCESS); 740 } catch (Exception e) { 741 if (existingCommitter == null) { 742 blob.removeCommitter(newCommitter); 743 } else { 744 blob.addOrReplaceCommitter(existingCommitter); 745 } 746 Slog.d(TAG, "Error committing the blob: " + session, e); 747 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED, 748 session.getOwnerUid(), session.getSessionId(), blob.getSize(), 749 FrameworkStatsLog.BLOB_COMMITTED__RESULT__ERROR_DURING_COMMIT); 750 session.sendCommitCallbackResult(COMMIT_RESULT_ERROR); 751 // If the commit fails and this blob data didn't exist before, delete it. 752 // But if it is a recommit, just leave it as is. 753 if (session.getSessionId() == blob.getBlobId()) { 754 deleteBlobLocked(blob); 755 mBlobsMap.remove(blob.getBlobHandle()); 756 } 757 } 758 // Delete redundant data from recommits. 759 if (session.getSessionId() != blob.getBlobId()) { 760 deleteSessionLocked(session); 761 } 762 getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())) 763 .remove(session.getSessionId()); 764 if (LOGV) { 765 Slog.v(TAG, "Successfully committed session " + session); 766 } 767 } 768 break; 769 default: 770 Slog.wtf(TAG, "Invalid session state: " 771 + stateToString(session.getState())); 772 } 773 synchronized (mBlobsLock) { 774 try { 775 writeBlobSessionsLocked(); 776 } catch (Exception e) { 777 // already logged, ignore. 778 } 779 } 780 } 781 782 @GuardedBy("mBlobsLock") writeBlobSessionsLocked()783 private void writeBlobSessionsLocked() throws Exception { 784 final AtomicFile sessionsIndexFile = prepareSessionsIndexFile(); 785 if (sessionsIndexFile == null) { 786 Slog.wtf(TAG, "Error creating sessions index file"); 787 return; 788 } 789 FileOutputStream fos = null; 790 try { 791 fos = sessionsIndexFile.startWrite(SystemClock.uptimeMillis()); 792 final XmlSerializer out = new FastXmlSerializer(); 793 out.setOutput(fos, StandardCharsets.UTF_8.name()); 794 out.startDocument(null, true); 795 out.startTag(null, TAG_SESSIONS); 796 XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT); 797 798 for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { 799 final LongSparseArray<BlobStoreSession> userSessions = 800 mSessions.valueAt(i); 801 for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) { 802 out.startTag(null, TAG_SESSION); 803 userSessions.valueAt(j).writeToXml(out); 804 out.endTag(null, TAG_SESSION); 805 } 806 } 807 808 out.endTag(null, TAG_SESSIONS); 809 out.endDocument(); 810 sessionsIndexFile.finishWrite(fos); 811 if (LOGV) { 812 Slog.v(TAG, "Finished persisting sessions data"); 813 } 814 } catch (Exception e) { 815 sessionsIndexFile.failWrite(fos); 816 Slog.wtf(TAG, "Error writing sessions data", e); 817 throw e; 818 } 819 } 820 821 @GuardedBy("mBlobsLock") readBlobSessionsLocked(SparseArray<SparseArray<String>> allPackages)822 private void readBlobSessionsLocked(SparseArray<SparseArray<String>> allPackages) { 823 if (!BlobStoreConfig.getBlobStoreRootDir().exists()) { 824 return; 825 } 826 final AtomicFile sessionsIndexFile = prepareSessionsIndexFile(); 827 if (sessionsIndexFile == null) { 828 Slog.wtf(TAG, "Error creating sessions index file"); 829 return; 830 } else if (!sessionsIndexFile.exists()) { 831 Slog.w(TAG, "Sessions index file not available: " + sessionsIndexFile.getBaseFile()); 832 return; 833 } 834 835 mSessions.clear(); 836 try (FileInputStream fis = sessionsIndexFile.openRead()) { 837 final XmlPullParser in = Xml.newPullParser(); 838 in.setInput(fis, StandardCharsets.UTF_8.name()); 839 XmlUtils.beginDocument(in, TAG_SESSIONS); 840 final int version = XmlUtils.readIntAttribute(in, ATTR_VERSION); 841 while (true) { 842 XmlUtils.nextElement(in); 843 if (in.getEventType() == XmlPullParser.END_DOCUMENT) { 844 break; 845 } 846 847 if (TAG_SESSION.equals(in.getName())) { 848 final BlobStoreSession session = BlobStoreSession.createFromXml( 849 in, version, mContext, mSessionStateChangeListener); 850 if (session == null) { 851 continue; 852 } 853 final SparseArray<String> userPackages = allPackages.get( 854 UserHandle.getUserId(session.getOwnerUid())); 855 if (userPackages != null 856 && session.getOwnerPackageName().equals( 857 userPackages.get(session.getOwnerUid()))) { 858 addSessionForUserLocked(session, 859 UserHandle.getUserId(session.getOwnerUid())); 860 } else { 861 // Unknown package or the session data does not belong to this package. 862 session.getSessionFile().delete(); 863 } 864 mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, session.getSessionId()); 865 } 866 } 867 if (LOGV) { 868 Slog.v(TAG, "Finished reading sessions data"); 869 } 870 } catch (Exception e) { 871 Slog.wtf(TAG, "Error reading sessions data", e); 872 } 873 } 874 875 @GuardedBy("mBlobsLock") writeBlobsInfoLocked()876 private void writeBlobsInfoLocked() throws Exception { 877 final AtomicFile blobsIndexFile = prepareBlobsIndexFile(); 878 if (blobsIndexFile == null) { 879 Slog.wtf(TAG, "Error creating blobs index file"); 880 return; 881 } 882 FileOutputStream fos = null; 883 try { 884 fos = blobsIndexFile.startWrite(SystemClock.uptimeMillis()); 885 final XmlSerializer out = new FastXmlSerializer(); 886 out.setOutput(fos, StandardCharsets.UTF_8.name()); 887 out.startDocument(null, true); 888 out.startTag(null, TAG_BLOBS); 889 XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT); 890 891 for (int i = 0, count = mBlobsMap.size(); i < count; ++i) { 892 out.startTag(null, TAG_BLOB); 893 mBlobsMap.valueAt(i).writeToXml(out); 894 out.endTag(null, TAG_BLOB); 895 } 896 897 out.endTag(null, TAG_BLOBS); 898 out.endDocument(); 899 blobsIndexFile.finishWrite(fos); 900 if (LOGV) { 901 Slog.v(TAG, "Finished persisting blobs data"); 902 } 903 } catch (Exception e) { 904 blobsIndexFile.failWrite(fos); 905 Slog.wtf(TAG, "Error writing blobs data", e); 906 throw e; 907 } 908 } 909 910 @GuardedBy("mBlobsLock") readBlobsInfoLocked(SparseArray<SparseArray<String>> allPackages)911 private void readBlobsInfoLocked(SparseArray<SparseArray<String>> allPackages) { 912 if (!BlobStoreConfig.getBlobStoreRootDir().exists()) { 913 return; 914 } 915 final AtomicFile blobsIndexFile = prepareBlobsIndexFile(); 916 if (blobsIndexFile == null) { 917 Slog.wtf(TAG, "Error creating blobs index file"); 918 return; 919 } else if (!blobsIndexFile.exists()) { 920 Slog.w(TAG, "Blobs index file not available: " + blobsIndexFile.getBaseFile()); 921 return; 922 } 923 924 mBlobsMap.clear(); 925 try (FileInputStream fis = blobsIndexFile.openRead()) { 926 final XmlPullParser in = Xml.newPullParser(); 927 in.setInput(fis, StandardCharsets.UTF_8.name()); 928 XmlUtils.beginDocument(in, TAG_BLOBS); 929 final int version = XmlUtils.readIntAttribute(in, ATTR_VERSION); 930 while (true) { 931 XmlUtils.nextElement(in); 932 if (in.getEventType() == XmlPullParser.END_DOCUMENT) { 933 break; 934 } 935 936 if (TAG_BLOB.equals(in.getName())) { 937 final BlobMetadata blobMetadata = BlobMetadata.createFromXml( 938 in, version, mContext); 939 blobMetadata.removeCommittersFromUnknownPkgs(allPackages); 940 blobMetadata.removeLeaseesFromUnknownPkgs(allPackages); 941 mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, blobMetadata.getBlobId()); 942 if (version >= XML_VERSION_ALLOW_ACCESS_ACROSS_USERS) { 943 addBlobLocked(blobMetadata); 944 } else { 945 final BlobMetadata existingBlobMetadata = mBlobsMap.get( 946 blobMetadata.getBlobHandle()); 947 if (existingBlobMetadata == null) { 948 addBlobLocked(blobMetadata); 949 } else { 950 existingBlobMetadata.addCommittersAndLeasees(blobMetadata); 951 blobMetadata.getBlobFile().delete(); 952 } 953 } 954 } 955 } 956 if (LOGV) { 957 Slog.v(TAG, "Finished reading blobs data"); 958 } 959 } catch (Exception e) { 960 Slog.wtf(TAG, "Error reading blobs data", e); 961 } 962 } 963 writeBlobsInfo()964 private void writeBlobsInfo() { 965 synchronized (mBlobsLock) { 966 try { 967 writeBlobsInfoLocked(); 968 } catch (Exception e) { 969 // Already logged, ignore 970 } 971 } 972 } 973 writeBlobsInfoAsync()974 private void writeBlobsInfoAsync() { 975 if (!mHandler.hasCallbacks(mSaveBlobsInfoRunnable)) { 976 mHandler.post(mSaveBlobsInfoRunnable); 977 } 978 } 979 writeBlobSessions()980 private void writeBlobSessions() { 981 synchronized (mBlobsLock) { 982 try { 983 writeBlobSessionsLocked(); 984 } catch (Exception e) { 985 // Already logged, ignore 986 } 987 } 988 } 989 writeBlobSessionsAsync()990 private void writeBlobSessionsAsync() { 991 if (!mHandler.hasCallbacks(mSaveSessionsRunnable)) { 992 mHandler.post(mSaveSessionsRunnable); 993 } 994 } 995 getAllPackages()996 private SparseArray<SparseArray<String>> getAllPackages() { 997 final SparseArray<SparseArray<String>> allPackages = new SparseArray<>(); 998 final int[] allUsers = LocalServices.getService(UserManagerInternal.class).getUserIds(); 999 for (int userId : allUsers) { 1000 final SparseArray<String> userPackages = new SparseArray<>(); 1001 allPackages.put(userId, userPackages); 1002 final List<ApplicationInfo> applicationInfos = mPackageManagerInternal 1003 .getInstalledApplications( 1004 MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE 1005 | MATCH_UNINSTALLED_PACKAGES, 1006 userId, Process.myUid()); 1007 for (int i = 0, count = applicationInfos.size(); i < count; ++i) { 1008 final ApplicationInfo applicationInfo = applicationInfos.get(i); 1009 userPackages.put(applicationInfo.uid, applicationInfo.packageName); 1010 } 1011 } 1012 return allPackages; 1013 } 1014 prepareSessionsIndexFile()1015 private AtomicFile prepareSessionsIndexFile() { 1016 final File file = BlobStoreConfig.prepareSessionIndexFile(); 1017 if (file == null) { 1018 return null; 1019 } 1020 return new AtomicFile(file, "session_index" /* commitLogTag */); 1021 } 1022 prepareBlobsIndexFile()1023 private AtomicFile prepareBlobsIndexFile() { 1024 final File file = BlobStoreConfig.prepareBlobsIndexFile(); 1025 if (file == null) { 1026 return null; 1027 } 1028 return new AtomicFile(file, "blobs_index" /* commitLogTag */); 1029 } 1030 1031 @VisibleForTesting handlePackageRemoved(String packageName, int uid)1032 void handlePackageRemoved(String packageName, int uid) { 1033 synchronized (mBlobsLock) { 1034 // Clean up any pending sessions 1035 final LongSparseArray<BlobStoreSession> userSessions = 1036 getUserSessionsLocked(UserHandle.getUserId(uid)); 1037 userSessions.removeIf((sessionId, blobStoreSession) -> { 1038 if (blobStoreSession.getOwnerUid() == uid 1039 && blobStoreSession.getOwnerPackageName().equals(packageName)) { 1040 deleteSessionLocked(blobStoreSession); 1041 return true; 1042 } 1043 return false; 1044 }); 1045 writeBlobSessionsAsync(); 1046 1047 // Remove the package from the committer and leasee list 1048 mBlobsMap.entrySet().removeIf(entry -> { 1049 final BlobMetadata blobMetadata = entry.getValue(); 1050 final boolean isACommitter = blobMetadata.isACommitter(packageName, uid); 1051 if (isACommitter) { 1052 blobMetadata.removeCommitter(packageName, uid); 1053 } 1054 blobMetadata.removeLeasee(packageName, uid); 1055 // Regardless of when the blob is committed, we need to delete 1056 // it if it was from the deleted package to ensure we delete all traces of it. 1057 if (blobMetadata.shouldBeDeleted(isACommitter /* respectLeaseWaitTime */)) { 1058 deleteBlobLocked(blobMetadata); 1059 return true; 1060 } 1061 return false; 1062 }); 1063 writeBlobsInfoAsync(); 1064 1065 if (LOGV) { 1066 Slog.v(TAG, "Removed blobs data associated with pkg=" 1067 + packageName + ", uid=" + uid); 1068 } 1069 } 1070 } 1071 handleUserRemoved(int userId)1072 private void handleUserRemoved(int userId) { 1073 synchronized (mBlobsLock) { 1074 final LongSparseArray<BlobStoreSession> userSessions = 1075 mSessions.removeReturnOld(userId); 1076 if (userSessions != null) { 1077 for (int i = 0, count = userSessions.size(); i < count; ++i) { 1078 final BlobStoreSession session = userSessions.valueAt(i); 1079 deleteSessionLocked(session); 1080 } 1081 } 1082 1083 mBlobsMap.entrySet().removeIf(entry -> { 1084 final BlobMetadata blobMetadata = entry.getValue(); 1085 blobMetadata.removeDataForUser(userId); 1086 if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) { 1087 deleteBlobLocked(blobMetadata); 1088 return true; 1089 } 1090 return false; 1091 }); 1092 if (LOGV) { 1093 Slog.v(TAG, "Removed blobs data in user " + userId); 1094 } 1095 } 1096 } 1097 1098 @GuardedBy("mBlobsLock") 1099 @VisibleForTesting handleIdleMaintenanceLocked()1100 void handleIdleMaintenanceLocked() { 1101 // Cleanup any left over data on disk that is not part of index. 1102 final ArrayList<Long> deletedBlobIds = new ArrayList<>(); 1103 final ArrayList<File> filesToDelete = new ArrayList<>(); 1104 final File blobsDir = BlobStoreConfig.getBlobsDir(); 1105 if (blobsDir.exists()) { 1106 for (File file : blobsDir.listFiles()) { 1107 try { 1108 final long id = Long.parseLong(file.getName()); 1109 if (mActiveBlobIds.indexOf(id) < 0) { 1110 filesToDelete.add(file); 1111 deletedBlobIds.add(id); 1112 } 1113 } catch (NumberFormatException e) { 1114 Slog.wtf(TAG, "Error parsing the file name: " + file, e); 1115 filesToDelete.add(file); 1116 } 1117 } 1118 for (int i = 0, count = filesToDelete.size(); i < count; ++i) { 1119 filesToDelete.get(i).delete(); 1120 } 1121 } 1122 1123 // Cleanup any stale blobs. 1124 mBlobsMap.entrySet().removeIf(entry -> { 1125 final BlobMetadata blobMetadata = entry.getValue(); 1126 1127 // Remove expired leases 1128 blobMetadata.removeExpiredLeases(); 1129 1130 if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) { 1131 deleteBlobLocked(blobMetadata); 1132 deletedBlobIds.add(blobMetadata.getBlobId()); 1133 return true; 1134 } 1135 return false; 1136 }); 1137 writeBlobsInfoAsync(); 1138 1139 // Cleanup any stale sessions. 1140 for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { 1141 final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i); 1142 userSessions.removeIf((sessionId, blobStoreSession) -> { 1143 boolean shouldRemove = false; 1144 1145 // Cleanup sessions which haven't been modified in a while. 1146 if (blobStoreSession.isExpired()) { 1147 shouldRemove = true; 1148 } 1149 1150 // Cleanup sessions with already expired data. 1151 if (blobStoreSession.getBlobHandle().isExpired()) { 1152 shouldRemove = true; 1153 } 1154 1155 if (shouldRemove) { 1156 deleteSessionLocked(blobStoreSession); 1157 deletedBlobIds.add(blobStoreSession.getSessionId()); 1158 } 1159 return shouldRemove; 1160 }); 1161 } 1162 Slog.d(TAG, "Completed idle maintenance; deleted " 1163 + Arrays.toString(deletedBlobIds.toArray())); 1164 writeBlobSessionsAsync(); 1165 } 1166 1167 @GuardedBy("mBlobsLock") deleteSessionLocked(BlobStoreSession blobStoreSession)1168 private void deleteSessionLocked(BlobStoreSession blobStoreSession) { 1169 blobStoreSession.destroy(); 1170 mActiveBlobIds.remove(blobStoreSession.getSessionId()); 1171 } 1172 1173 @GuardedBy("mBlobsLock") deleteBlobLocked(BlobMetadata blobMetadata)1174 private void deleteBlobLocked(BlobMetadata blobMetadata) { 1175 blobMetadata.destroy(); 1176 mActiveBlobIds.remove(blobMetadata.getBlobId()); 1177 } 1178 runClearAllSessions(@serIdInt int userId)1179 void runClearAllSessions(@UserIdInt int userId) { 1180 synchronized (mBlobsLock) { 1181 for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { 1182 final int sessionUserId = mSessions.keyAt(i); 1183 if (userId != UserHandle.USER_ALL && userId != sessionUserId) { 1184 continue; 1185 } 1186 final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i); 1187 for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) { 1188 mActiveBlobIds.remove(userSessions.valueAt(j).getSessionId()); 1189 } 1190 } 1191 if (userId == UserHandle.USER_ALL) { 1192 mSessions.clear(); 1193 } else { 1194 mSessions.remove(userId); 1195 } 1196 writeBlobSessionsAsync(); 1197 } 1198 } 1199 runClearAllBlobs(@serIdInt int userId)1200 void runClearAllBlobs(@UserIdInt int userId) { 1201 synchronized (mBlobsLock) { 1202 mBlobsMap.entrySet().removeIf(entry -> { 1203 final BlobMetadata blobMetadata = entry.getValue(); 1204 if (userId == UserHandle.USER_ALL) { 1205 mActiveBlobIds.remove(blobMetadata.getBlobId()); 1206 return true; 1207 } 1208 blobMetadata.removeDataForUser(userId); 1209 if (blobMetadata.shouldBeDeleted(false /* respectLeaseWaitTime */)) { 1210 mActiveBlobIds.remove(blobMetadata.getBlobId()); 1211 return true; 1212 } 1213 return false; 1214 }); 1215 writeBlobsInfoAsync(); 1216 } 1217 } 1218 deleteBlob(@onNull BlobHandle blobHandle, @UserIdInt int userId)1219 void deleteBlob(@NonNull BlobHandle blobHandle, @UserIdInt int userId) { 1220 synchronized (mBlobsLock) { 1221 final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle); 1222 if (blobMetadata == null) { 1223 return; 1224 } 1225 blobMetadata.removeDataForUser(userId); 1226 if (blobMetadata.shouldBeDeleted(false /* respectLeaseWaitTime */)) { 1227 deleteBlobLocked(blobMetadata); 1228 mBlobsMap.remove(blobHandle); 1229 } 1230 writeBlobsInfoAsync(); 1231 } 1232 } 1233 runIdleMaintenance()1234 void runIdleMaintenance() { 1235 synchronized (mBlobsLock) { 1236 handleIdleMaintenanceLocked(); 1237 } 1238 } 1239 isBlobAvailable(long blobId, int userId)1240 boolean isBlobAvailable(long blobId, int userId) { 1241 synchronized (mBlobsLock) { 1242 for (int i = 0, blobCount = mBlobsMap.size(); i < blobCount; ++i) { 1243 final BlobMetadata blobMetadata = mBlobsMap.valueAt(i); 1244 if (blobMetadata.getBlobId() != blobId) { 1245 continue; 1246 } 1247 return blobMetadata.hasACommitterInUser(userId); 1248 } 1249 return false; 1250 } 1251 } 1252 1253 @GuardedBy("mBlobsLock") dumpSessionsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs)1254 private void dumpSessionsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) { 1255 for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { 1256 final int userId = mSessions.keyAt(i); 1257 if (!dumpArgs.shouldDumpUser(userId)) { 1258 continue; 1259 } 1260 final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i); 1261 fout.println("List of sessions in user #" 1262 + userId + " (" + userSessions.size() + "):"); 1263 fout.increaseIndent(); 1264 for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) { 1265 final long sessionId = userSessions.keyAt(j); 1266 final BlobStoreSession session = userSessions.valueAt(j); 1267 if (!dumpArgs.shouldDumpSession(session.getOwnerPackageName(), 1268 session.getOwnerUid(), session.getSessionId())) { 1269 continue; 1270 } 1271 fout.println("Session #" + sessionId); 1272 fout.increaseIndent(); 1273 session.dump(fout, dumpArgs); 1274 fout.decreaseIndent(); 1275 } 1276 fout.decreaseIndent(); 1277 } 1278 } 1279 1280 @GuardedBy("mBlobsLock") dumpBlobsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs)1281 private void dumpBlobsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) { 1282 fout.println("List of blobs (" + mBlobsMap.size() + "):"); 1283 fout.increaseIndent(); 1284 for (int i = 0, blobCount = mBlobsMap.size(); i < blobCount; ++i) { 1285 final BlobMetadata blobMetadata = mBlobsMap.valueAt(i); 1286 if (!dumpArgs.shouldDumpBlob(blobMetadata.getBlobId())) { 1287 continue; 1288 } 1289 fout.println("Blob #" + blobMetadata.getBlobId()); 1290 fout.increaseIndent(); 1291 blobMetadata.dump(fout, dumpArgs); 1292 fout.decreaseIndent(); 1293 } 1294 if (mBlobsMap.isEmpty()) { 1295 fout.println("<empty>"); 1296 } 1297 fout.decreaseIndent(); 1298 } 1299 1300 private class BlobStorageStatsAugmenter implements StorageStatsAugmenter { 1301 @Override augmentStatsForPackageForUser( @onNull PackageStats stats, @NonNull String packageName, @NonNull UserHandle userHandle, boolean callerHasStatsPermission)1302 public void augmentStatsForPackageForUser( 1303 @NonNull PackageStats stats, 1304 @NonNull String packageName, 1305 @NonNull UserHandle userHandle, 1306 boolean callerHasStatsPermission) { 1307 final AtomicLong blobsDataSize = new AtomicLong(0); 1308 forEachSessionInUser(session -> { 1309 if (session.getOwnerPackageName().equals(packageName)) { 1310 blobsDataSize.getAndAdd(session.getSize()); 1311 } 1312 }, userHandle.getIdentifier()); 1313 1314 forEachBlob(blobMetadata -> { 1315 if (blobMetadata.shouldAttributeToLeasee(packageName, userHandle.getIdentifier(), 1316 callerHasStatsPermission)) { 1317 blobsDataSize.getAndAdd(blobMetadata.getSize()); 1318 } 1319 }); 1320 1321 stats.dataSize += blobsDataSize.get(); 1322 } 1323 1324 @Override augmentStatsForUid(@onNull PackageStats stats, int uid, boolean callerHasStatsPermission)1325 public void augmentStatsForUid(@NonNull PackageStats stats, int uid, 1326 boolean callerHasStatsPermission) { 1327 final int userId = UserHandle.getUserId(uid); 1328 final AtomicLong blobsDataSize = new AtomicLong(0); 1329 forEachSessionInUser(session -> { 1330 if (session.getOwnerUid() == uid) { 1331 blobsDataSize.getAndAdd(session.getSize()); 1332 } 1333 }, userId); 1334 1335 forEachBlob(blobMetadata -> { 1336 if (blobMetadata.shouldAttributeToLeasee(uid, 1337 callerHasStatsPermission)) { 1338 blobsDataSize.getAndAdd(blobMetadata.getSize()); 1339 } 1340 }); 1341 1342 stats.dataSize += blobsDataSize.get(); 1343 } 1344 1345 @Override augmentStatsForUser( @onNull PackageStats stats, @NonNull UserHandle userHandle)1346 public void augmentStatsForUser( 1347 @NonNull PackageStats stats, @NonNull UserHandle userHandle) { 1348 final AtomicLong blobsDataSize = new AtomicLong(0); 1349 forEachSessionInUser(session -> { 1350 blobsDataSize.getAndAdd(session.getSize()); 1351 }, userHandle.getIdentifier()); 1352 1353 forEachBlob(blobMetadata -> { 1354 if (blobMetadata.shouldAttributeToUser(userHandle.getIdentifier())) { 1355 blobsDataSize.getAndAdd(blobMetadata.getSize()); 1356 } 1357 }); 1358 1359 stats.dataSize += blobsDataSize.get(); 1360 } 1361 } 1362 forEachSessionInUser(Consumer<BlobStoreSession> consumer, int userId)1363 private void forEachSessionInUser(Consumer<BlobStoreSession> consumer, int userId) { 1364 synchronized (mBlobsLock) { 1365 final LongSparseArray<BlobStoreSession> userSessions = getUserSessionsLocked(userId); 1366 for (int i = 0, count = userSessions.size(); i < count; ++i) { 1367 final BlobStoreSession session = userSessions.valueAt(i); 1368 consumer.accept(session); 1369 } 1370 } 1371 } 1372 forEachBlob(Consumer<BlobMetadata> consumer)1373 private void forEachBlob(Consumer<BlobMetadata> consumer) { 1374 synchronized (mBlobsMap) { 1375 forEachBlobLocked(consumer); 1376 } 1377 } 1378 1379 @GuardedBy("mBlobsMap") forEachBlobLocked(Consumer<BlobMetadata> consumer)1380 private void forEachBlobLocked(Consumer<BlobMetadata> consumer) { 1381 for (int blobIdx = 0, count = mBlobsMap.size(); blobIdx < count; ++blobIdx) { 1382 final BlobMetadata blobMetadata = mBlobsMap.valueAt(blobIdx); 1383 consumer.accept(blobMetadata); 1384 } 1385 } 1386 1387 @GuardedBy("mBlobsMap") forEachBlobLocked(BiConsumer<BlobHandle, BlobMetadata> consumer)1388 private void forEachBlobLocked(BiConsumer<BlobHandle, BlobMetadata> consumer) { 1389 for (int blobIdx = 0, count = mBlobsMap.size(); blobIdx < count; ++blobIdx) { 1390 final BlobHandle blobHandle = mBlobsMap.keyAt(blobIdx); 1391 final BlobMetadata blobMetadata = mBlobsMap.valueAt(blobIdx); 1392 consumer.accept(blobHandle, blobMetadata); 1393 } 1394 } 1395 isAllowedBlobStoreAccess(int uid, String packageName)1396 private boolean isAllowedBlobStoreAccess(int uid, String packageName) { 1397 return (!Process.isSdkSandboxUid(uid) && !Process.isIsolated(uid) 1398 && !mPackageManagerInternal.isInstantApp(packageName, UserHandle.getUserId(uid))); 1399 } 1400 1401 private class PackageChangedReceiver extends BroadcastReceiver { 1402 @Override onReceive(Context context, Intent intent)1403 public void onReceive(Context context, Intent intent) { 1404 if (LOGV) { 1405 Slog.v(TAG, "Received " + intent); 1406 } 1407 switch (intent.getAction()) { 1408 case Intent.ACTION_PACKAGE_FULLY_REMOVED: 1409 case Intent.ACTION_PACKAGE_DATA_CLEARED: 1410 final String packageName = intent.getData().getSchemeSpecificPart(); 1411 if (packageName == null) { 1412 Slog.wtf(TAG, "Package name is missing in the intent: " + intent); 1413 return; 1414 } 1415 final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); 1416 if (uid == -1) { 1417 Slog.wtf(TAG, "uid is missing in the intent: " + intent); 1418 return; 1419 } 1420 handlePackageRemoved(packageName, uid); 1421 break; 1422 default: 1423 Slog.wtf(TAG, "Received unknown intent: " + intent); 1424 } 1425 } 1426 } 1427 1428 private class UserActionReceiver extends BroadcastReceiver { 1429 @Override onReceive(Context context, Intent intent)1430 public void onReceive(Context context, Intent intent) { 1431 if (LOGV) { 1432 Slog.v(TAG, "Received: " + intent); 1433 } 1434 switch (intent.getAction()) { 1435 case Intent.ACTION_USER_REMOVED: 1436 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 1437 USER_NULL); 1438 if (userId == USER_NULL) { 1439 Slog.wtf(TAG, "userId is missing in the intent: " + intent); 1440 return; 1441 } 1442 handleUserRemoved(userId); 1443 break; 1444 default: 1445 Slog.wtf(TAG, "Received unknown intent: " + intent); 1446 } 1447 } 1448 } 1449 1450 private class Stub extends IBlobStoreManager.Stub { 1451 @Override 1452 @IntRange(from = 1) createSession(@onNull BlobHandle blobHandle, @NonNull String packageName)1453 public long createSession(@NonNull BlobHandle blobHandle, 1454 @NonNull String packageName) { 1455 Objects.requireNonNull(blobHandle, "blobHandle must not be null"); 1456 blobHandle.assertIsValid(); 1457 Objects.requireNonNull(packageName, "packageName must not be null"); 1458 1459 final int callingUid = Binder.getCallingUid(); 1460 verifyCallingPackage(callingUid, packageName); 1461 1462 if (!isAllowedBlobStoreAccess(callingUid, packageName)) { 1463 throw new SecurityException("Caller not allowed to create session; " 1464 + "callingUid=" + callingUid + ", callingPackage=" + packageName); 1465 } 1466 1467 try { 1468 return createSessionInternal(blobHandle, callingUid, packageName); 1469 } catch (LimitExceededException e) { 1470 throw new ParcelableException(e); 1471 } 1472 } 1473 1474 @Override 1475 @NonNull openSession(@ntRangefrom = 1) long sessionId, @NonNull String packageName)1476 public IBlobStoreSession openSession(@IntRange(from = 1) long sessionId, 1477 @NonNull String packageName) { 1478 Preconditions.checkArgumentPositive(sessionId, 1479 "sessionId must be positive: " + sessionId); 1480 Objects.requireNonNull(packageName, "packageName must not be null"); 1481 1482 final int callingUid = Binder.getCallingUid(); 1483 verifyCallingPackage(callingUid, packageName); 1484 1485 return openSessionInternal(sessionId, callingUid, packageName); 1486 } 1487 1488 @Override abandonSession(@ntRangefrom = 1) long sessionId, @NonNull String packageName)1489 public void abandonSession(@IntRange(from = 1) long sessionId, 1490 @NonNull String packageName) { 1491 Preconditions.checkArgumentPositive(sessionId, 1492 "sessionId must be positive: " + sessionId); 1493 Objects.requireNonNull(packageName, "packageName must not be null"); 1494 1495 final int callingUid = Binder.getCallingUid(); 1496 verifyCallingPackage(callingUid, packageName); 1497 1498 abandonSessionInternal(sessionId, callingUid, packageName); 1499 } 1500 1501 @Override openBlob(@onNull BlobHandle blobHandle, @NonNull String packageName)1502 public ParcelFileDescriptor openBlob(@NonNull BlobHandle blobHandle, 1503 @NonNull String packageName) { 1504 Objects.requireNonNull(blobHandle, "blobHandle must not be null"); 1505 blobHandle.assertIsValid(); 1506 Objects.requireNonNull(packageName, "packageName must not be null"); 1507 1508 final int callingUid = Binder.getCallingUid(); 1509 verifyCallingPackage(callingUid, packageName); 1510 1511 if (!isAllowedBlobStoreAccess(callingUid, packageName)) { 1512 throw new SecurityException("Caller not allowed to open blob; " 1513 + "callingUid=" + callingUid + ", callingPackage=" + packageName); 1514 } 1515 1516 try { 1517 return openBlobInternal(blobHandle, callingUid, packageName); 1518 } catch (IOException e) { 1519 throw ExceptionUtils.wrap(e); 1520 } 1521 } 1522 1523 @Override acquireLease(@onNull BlobHandle blobHandle, @IdRes int descriptionResId, @Nullable CharSequence description, @CurrentTimeSecondsLong long leaseExpiryTimeMillis, @NonNull String packageName)1524 public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId, 1525 @Nullable CharSequence description, 1526 @CurrentTimeSecondsLong long leaseExpiryTimeMillis, @NonNull String packageName) { 1527 Objects.requireNonNull(blobHandle, "blobHandle must not be null"); 1528 blobHandle.assertIsValid(); 1529 Preconditions.checkArgument( 1530 ResourceId.isValid(descriptionResId) || description != null, 1531 "Description must be valid; descriptionId=" + descriptionResId 1532 + ", description=" + description); 1533 Preconditions.checkArgumentNonnegative(leaseExpiryTimeMillis, 1534 "leaseExpiryTimeMillis must not be negative"); 1535 Objects.requireNonNull(packageName, "packageName must not be null"); 1536 1537 description = BlobStoreConfig.getTruncatedLeaseDescription(description); 1538 1539 final int callingUid = Binder.getCallingUid(); 1540 verifyCallingPackage(callingUid, packageName); 1541 1542 if (!isAllowedBlobStoreAccess(callingUid, packageName)) { 1543 throw new SecurityException("Caller not allowed to open blob; " 1544 + "callingUid=" + callingUid + ", callingPackage=" + packageName); 1545 } 1546 1547 try { 1548 acquireLeaseInternal(blobHandle, descriptionResId, description, 1549 leaseExpiryTimeMillis, callingUid, packageName); 1550 } catch (Resources.NotFoundException e) { 1551 throw new IllegalArgumentException(e); 1552 } catch (LimitExceededException e) { 1553 throw new ParcelableException(e); 1554 } 1555 } 1556 1557 @Override releaseLease(@onNull BlobHandle blobHandle, @NonNull String packageName)1558 public void releaseLease(@NonNull BlobHandle blobHandle, @NonNull String packageName) { 1559 Objects.requireNonNull(blobHandle, "blobHandle must not be null"); 1560 blobHandle.assertIsValid(); 1561 Objects.requireNonNull(packageName, "packageName must not be null"); 1562 1563 final int callingUid = Binder.getCallingUid(); 1564 verifyCallingPackage(callingUid, packageName); 1565 1566 if (!isAllowedBlobStoreAccess(callingUid, packageName)) { 1567 throw new SecurityException("Caller not allowed to open blob; " 1568 + "callingUid=" + callingUid + ", callingPackage=" + packageName); 1569 } 1570 1571 releaseLeaseInternal(blobHandle, callingUid, packageName); 1572 } 1573 1574 @Override releaseAllLeases(@onNull String packageName)1575 public void releaseAllLeases(@NonNull String packageName) { 1576 Objects.requireNonNull(packageName, "packageName must not be null"); 1577 1578 final int callingUid = Binder.getCallingUid(); 1579 verifyCallingPackage(callingUid, packageName); 1580 1581 if (!isAllowedBlobStoreAccess(callingUid, packageName)) { 1582 throw new SecurityException("Caller not allowed to open blob; " 1583 + "callingUid=" + callingUid + ", callingPackage=" + packageName); 1584 } 1585 1586 releaseAllLeasesInternal(callingUid, packageName); 1587 } 1588 1589 @Override getRemainingLeaseQuotaBytes(@onNull String packageName)1590 public long getRemainingLeaseQuotaBytes(@NonNull String packageName) { 1591 final int callingUid = Binder.getCallingUid(); 1592 verifyCallingPackage(callingUid, packageName); 1593 1594 return getRemainingLeaseQuotaBytesInternal(callingUid, packageName); 1595 } 1596 1597 @Override waitForIdle(@onNull RemoteCallback remoteCallback)1598 public void waitForIdle(@NonNull RemoteCallback remoteCallback) { 1599 Objects.requireNonNull(remoteCallback, "remoteCallback must not be null"); 1600 1601 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, 1602 "Caller is not allowed to call this; caller=" + Binder.getCallingUid()); 1603 // We post messages back and forth between mHandler thread and mBackgroundHandler 1604 // thread while committing a blob. We need to replicate the same pattern here to 1605 // ensure pending messages have been handled. 1606 mHandler.post(() -> { 1607 mBackgroundHandler.post(() -> { 1608 mHandler.post(PooledLambda.obtainRunnable(remoteCallback::sendResult, null) 1609 .recycleOnUse()); 1610 }); 1611 }); 1612 } 1613 1614 @Override 1615 @NonNull queryBlobsForUser(@serIdInt int userId)1616 public List<BlobInfo> queryBlobsForUser(@UserIdInt int userId) { 1617 verifyCallerIsSystemUid("queryBlobsForUser"); 1618 1619 final int resolvedUserId = userId == USER_CURRENT 1620 ? ActivityManager.getCurrentUser() : userId; 1621 // Don't allow any other special user ids apart from USER_CURRENT 1622 final ActivityManagerInternal amInternal = LocalServices.getService( 1623 ActivityManagerInternal.class); 1624 amInternal.ensureNotSpecialUser(resolvedUserId); 1625 1626 return queryBlobsForUserInternal(resolvedUserId); 1627 } 1628 1629 @Override deleteBlob(long blobId)1630 public void deleteBlob(long blobId) { 1631 verifyCallerIsSystemUid("deleteBlob"); 1632 1633 deleteBlobInternal(blobId); 1634 } 1635 1636 @Override 1637 @NonNull getLeasedBlobs(@onNull String packageName)1638 public List<BlobHandle> getLeasedBlobs(@NonNull String packageName) { 1639 Objects.requireNonNull(packageName, "packageName must not be null"); 1640 1641 final int callingUid = Binder.getCallingUid(); 1642 verifyCallingPackage(callingUid, packageName); 1643 1644 return getLeasedBlobsInternal(callingUid, packageName); 1645 } 1646 1647 @Override 1648 @Nullable getLeaseInfo(@onNull BlobHandle blobHandle, @NonNull String packageName)1649 public LeaseInfo getLeaseInfo(@NonNull BlobHandle blobHandle, @NonNull String packageName) { 1650 Objects.requireNonNull(blobHandle, "blobHandle must not be null"); 1651 blobHandle.assertIsValid(); 1652 Objects.requireNonNull(packageName, "packageName must not be null"); 1653 1654 final int callingUid = Binder.getCallingUid(); 1655 verifyCallingPackage(callingUid, packageName); 1656 1657 if (!isAllowedBlobStoreAccess(callingUid, packageName)) { 1658 throw new SecurityException("Caller not allowed to open blob; " 1659 + "callingUid=" + callingUid + ", callingPackage=" + packageName); 1660 } 1661 1662 return getLeaseInfoInternal(blobHandle, callingUid, packageName); 1663 } 1664 1665 @Override dump(@onNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args)1666 public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, 1667 @Nullable String[] args) { 1668 // TODO: add proto-based version of this. 1669 if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, writer)) return; 1670 1671 final DumpArgs dumpArgs = DumpArgs.parse(args); 1672 1673 final IndentingPrintWriter fout = new IndentingPrintWriter(writer, " "); 1674 if (dumpArgs.shouldDumpHelp()) { 1675 writer.println("dumpsys blob_store [options]:"); 1676 fout.increaseIndent(); 1677 dumpArgs.dumpArgsUsage(fout); 1678 fout.decreaseIndent(); 1679 return; 1680 } 1681 1682 synchronized (mBlobsLock) { 1683 if (dumpArgs.shouldDumpAllSections()) { 1684 fout.println("mCurrentMaxSessionId: " + mCurrentMaxSessionId); 1685 fout.println(); 1686 } 1687 1688 if (dumpArgs.shouldDumpSessions()) { 1689 dumpSessionsLocked(fout, dumpArgs); 1690 fout.println(); 1691 } 1692 if (dumpArgs.shouldDumpBlobs()) { 1693 dumpBlobsLocked(fout, dumpArgs); 1694 fout.println(); 1695 } 1696 } 1697 1698 if (dumpArgs.shouldDumpConfig()) { 1699 fout.println("BlobStore config:"); 1700 fout.increaseIndent(); 1701 BlobStoreConfig.dump(fout, mContext); 1702 fout.decreaseIndent(); 1703 fout.println(); 1704 } 1705 } 1706 1707 @Override handleShellCommand(@onNull ParcelFileDescriptor in, @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, @NonNull String[] args)1708 public int handleShellCommand(@NonNull ParcelFileDescriptor in, 1709 @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, 1710 @NonNull String[] args) { 1711 return new BlobStoreManagerShellCommand(BlobStoreManagerService.this).exec(this, 1712 in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args); 1713 } 1714 1715 /** 1716 * Verify if the caller is an admin user's app with system uid 1717 */ verifyCallerIsSystemUid(final String operation)1718 private void verifyCallerIsSystemUid(final String operation) { 1719 if (UserHandle.getCallingAppId() != Process.SYSTEM_UID 1720 || !mContext.getSystemService(UserManager.class) 1721 .isUserAdmin(UserHandle.getCallingUserId())) { 1722 throw new SecurityException("Only admin user's app with system uid" 1723 + "are allowed to call #" + operation); 1724 } 1725 } 1726 } 1727 1728 static final class DumpArgs { 1729 private static final int FLAG_DUMP_SESSIONS = 1 << 0; 1730 private static final int FLAG_DUMP_BLOBS = 1 << 1; 1731 private static final int FLAG_DUMP_CONFIG = 1 << 2; 1732 1733 private int mSelectedSectionFlags; 1734 private boolean mDumpUnredacted; 1735 private final ArrayList<String> mDumpPackages = new ArrayList<>(); 1736 private final ArrayList<Integer> mDumpUids = new ArrayList<>(); 1737 private final ArrayList<Integer> mDumpUserIds = new ArrayList<>(); 1738 private final ArrayList<Long> mDumpBlobIds = new ArrayList<>(); 1739 private boolean mDumpHelp; 1740 private boolean mDumpAll; 1741 shouldDumpSession(String packageName, int uid, long blobId)1742 public boolean shouldDumpSession(String packageName, int uid, long blobId) { 1743 if (!CollectionUtils.isEmpty(mDumpPackages) 1744 && mDumpPackages.indexOf(packageName) < 0) { 1745 return false; 1746 } 1747 if (!CollectionUtils.isEmpty(mDumpUids) 1748 && mDumpUids.indexOf(uid) < 0) { 1749 return false; 1750 } 1751 if (!CollectionUtils.isEmpty(mDumpBlobIds) 1752 && mDumpBlobIds.indexOf(blobId) < 0) { 1753 return false; 1754 } 1755 return true; 1756 } 1757 shouldDumpAllSections()1758 public boolean shouldDumpAllSections() { 1759 return mDumpAll || (mSelectedSectionFlags == 0); 1760 } 1761 allowDumpSessions()1762 public void allowDumpSessions() { 1763 mSelectedSectionFlags |= FLAG_DUMP_SESSIONS; 1764 } 1765 shouldDumpSessions()1766 public boolean shouldDumpSessions() { 1767 if (shouldDumpAllSections()) { 1768 return true; 1769 } 1770 return (mSelectedSectionFlags & FLAG_DUMP_SESSIONS) != 0; 1771 } 1772 allowDumpBlobs()1773 public void allowDumpBlobs() { 1774 mSelectedSectionFlags |= FLAG_DUMP_BLOBS; 1775 } 1776 shouldDumpBlobs()1777 public boolean shouldDumpBlobs() { 1778 if (shouldDumpAllSections()) { 1779 return true; 1780 } 1781 return (mSelectedSectionFlags & FLAG_DUMP_BLOBS) != 0; 1782 } 1783 allowDumpConfig()1784 public void allowDumpConfig() { 1785 mSelectedSectionFlags |= FLAG_DUMP_CONFIG; 1786 } 1787 shouldDumpConfig()1788 public boolean shouldDumpConfig() { 1789 if (shouldDumpAllSections()) { 1790 return true; 1791 } 1792 return (mSelectedSectionFlags & FLAG_DUMP_CONFIG) != 0; 1793 } 1794 shouldDumpBlob(long blobId)1795 public boolean shouldDumpBlob(long blobId) { 1796 return CollectionUtils.isEmpty(mDumpBlobIds) 1797 || mDumpBlobIds.indexOf(blobId) >= 0; 1798 } 1799 shouldDumpFull()1800 public boolean shouldDumpFull() { 1801 return mDumpUnredacted; 1802 } 1803 shouldDumpUser(int userId)1804 public boolean shouldDumpUser(int userId) { 1805 return CollectionUtils.isEmpty(mDumpUserIds) 1806 || mDumpUserIds.indexOf(userId) >= 0; 1807 } 1808 shouldDumpHelp()1809 public boolean shouldDumpHelp() { 1810 return mDumpHelp; 1811 } 1812 DumpArgs()1813 private DumpArgs() {} 1814 parse(String[] args)1815 public static DumpArgs parse(String[] args) { 1816 final DumpArgs dumpArgs = new DumpArgs(); 1817 if (args == null) { 1818 return dumpArgs; 1819 } 1820 1821 for (int i = 0; i < args.length; ++i) { 1822 final String opt = args[i]; 1823 if ("--all".equals(opt) || "-a".equals(opt)) { 1824 dumpArgs.mDumpAll = true; 1825 } else if ("--unredacted".equals(opt) || "-u".equals(opt)) { 1826 final int callingUid = Binder.getCallingUid(); 1827 if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) { 1828 dumpArgs.mDumpUnredacted = true; 1829 } 1830 } else if ("--sessions".equals(opt)) { 1831 dumpArgs.allowDumpSessions(); 1832 } else if ("--blobs".equals(opt)) { 1833 dumpArgs.allowDumpBlobs(); 1834 } else if ("--config".equals(opt)) { 1835 dumpArgs.allowDumpConfig(); 1836 } else if ("--package".equals(opt) || "-p".equals(opt)) { 1837 dumpArgs.mDumpPackages.add(getStringArgRequired(args, ++i, "packageName")); 1838 } else if ("--uid".equals(opt)) { 1839 dumpArgs.mDumpUids.add(getIntArgRequired(args, ++i, "uid")); 1840 } else if ("--user".equals(opt)) { 1841 dumpArgs.mDumpUserIds.add(getIntArgRequired(args, ++i, "userId")); 1842 } else if ("--blob".equals(opt) || "-b".equals(opt)) { 1843 dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, ++i, "blobId")); 1844 } else if ("--help".equals(opt) || "-h".equals(opt)) { 1845 dumpArgs.mDumpHelp = true; 1846 } else { 1847 // Everything else is assumed to be blob ids. 1848 dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, i, "blobId")); 1849 } 1850 } 1851 return dumpArgs; 1852 } 1853 getStringArgRequired(String[] args, int index, String argName)1854 private static String getStringArgRequired(String[] args, int index, String argName) { 1855 if (index >= args.length) { 1856 throw new IllegalArgumentException("Missing " + argName); 1857 } 1858 return args[index]; 1859 } 1860 getIntArgRequired(String[] args, int index, String argName)1861 private static int getIntArgRequired(String[] args, int index, String argName) { 1862 if (index >= args.length) { 1863 throw new IllegalArgumentException("Missing " + argName); 1864 } 1865 final int value; 1866 try { 1867 value = Integer.parseInt(args[index]); 1868 } catch (NumberFormatException e) { 1869 throw new IllegalArgumentException("Invalid " + argName + ": " + args[index]); 1870 } 1871 return value; 1872 } 1873 getLongArgRequired(String[] args, int index, String argName)1874 private static long getLongArgRequired(String[] args, int index, String argName) { 1875 if (index >= args.length) { 1876 throw new IllegalArgumentException("Missing " + argName); 1877 } 1878 final long value; 1879 try { 1880 value = Long.parseLong(args[index]); 1881 } catch (NumberFormatException e) { 1882 throw new IllegalArgumentException("Invalid " + argName + ": " + args[index]); 1883 } 1884 return value; 1885 } 1886 dumpArgsUsage(IndentingPrintWriter pw)1887 private void dumpArgsUsage(IndentingPrintWriter pw) { 1888 pw.println("--help | -h"); 1889 printWithIndent(pw, "Dump this help text"); 1890 pw.println("--sessions"); 1891 printWithIndent(pw, "Dump only the sessions info"); 1892 pw.println("--blobs"); 1893 printWithIndent(pw, "Dump only the committed blobs info"); 1894 pw.println("--config"); 1895 printWithIndent(pw, "Dump only the config values"); 1896 pw.println("--package | -p [package-name]"); 1897 printWithIndent(pw, "Dump blobs info associated with the given package"); 1898 pw.println("--uid | -u [uid]"); 1899 printWithIndent(pw, "Dump blobs info associated with the given uid"); 1900 pw.println("--user [user-id]"); 1901 printWithIndent(pw, "Dump blobs info in the given user"); 1902 pw.println("--blob | -b [session-id | blob-id]"); 1903 printWithIndent(pw, "Dump blob info corresponding to the given ID"); 1904 pw.println("--full | -f"); 1905 printWithIndent(pw, "Dump full unredacted blobs data"); 1906 } 1907 printWithIndent(IndentingPrintWriter pw, String str)1908 private void printWithIndent(IndentingPrintWriter pw, String str) { 1909 pw.increaseIndent(); 1910 pw.println(str); 1911 pw.decreaseIndent(); 1912 } 1913 } 1914 registerBlobStorePuller()1915 private void registerBlobStorePuller() { 1916 mStatsManager.setPullAtomCallback( 1917 FrameworkStatsLog.BLOB_INFO, 1918 null, // use default PullAtomMetadata values 1919 DIRECT_EXECUTOR, 1920 mStatsCallbackImpl 1921 ); 1922 } 1923 1924 private class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback { 1925 @Override onPullAtom(int atomTag, List<StatsEvent> data)1926 public int onPullAtom(int atomTag, List<StatsEvent> data) { 1927 switch (atomTag) { 1928 case FrameworkStatsLog.BLOB_INFO: 1929 return pullBlobData(atomTag, data); 1930 default: 1931 throw new UnsupportedOperationException("Unknown tagId=" + atomTag); 1932 } 1933 } 1934 } 1935 pullBlobData(int atomTag, List<StatsEvent> data)1936 private int pullBlobData(int atomTag, List<StatsEvent> data) { 1937 forEachBlob(blobMetadata -> data.add(blobMetadata.dumpAsStatsEvent(atomTag))); 1938 return StatsManager.PULL_SUCCESS; 1939 } 1940 1941 private class LocalService extends BlobStoreManagerInternal { 1942 @Override onIdleMaintenance()1943 public void onIdleMaintenance() { 1944 runIdleMaintenance(); 1945 } 1946 } 1947 1948 @VisibleForTesting 1949 static class Injector { initializeMessageHandler()1950 public Handler initializeMessageHandler() { 1951 return BlobStoreManagerService.initializeMessageHandler(); 1952 } 1953 getBackgroundHandler()1954 public Handler getBackgroundHandler() { 1955 return BackgroundThread.getHandler(); 1956 } 1957 } 1958 } 1959