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