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