diff options
author | 2020-06-23 14:06:47 -0700 | |
---|---|---|
committer | 2020-06-24 03:58:48 -0700 | |
commit | 07717c9017ae9a5d06570d84064a37760d580b4f (patch) | |
tree | 597d7620100c3e3954c1c877d76ef3d9ad0a1cfb /apex/blobstore | |
parent | 38c2ca7698e3040a3c3eda9ac5dd588358f9b853 (diff) |
Add a limit on no. of active sessions, committed/leased blobs.
Bug: 159754450
Test: atest --test-mapping apex/blobstore
Change-Id: Ib8a78b8dcf396e452081c8526fb8a9efd9dde039
Diffstat (limited to 'apex/blobstore')
4 files changed, 198 insertions, 15 deletions
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java index 483d2cc2ec57..9c1acafa800d 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java @@ -184,9 +184,8 @@ public class BlobStoreManager { * @throws SecurityException when the caller is not allowed to create a session, such * as when called from an Instant app. * @throws IllegalArgumentException when {@code blobHandle} is invalid. - * @throws IllegalStateException when a new session could not be created, such as when the - * caller is trying to create too many sessions or when the - * device is running low on space. + * @throws LimitExceededException when a new session could not be created, such as when the + * caller is trying to create too many sessions. */ public @IntRange(from = 1) long createSession(@NonNull BlobHandle blobHandle) throws IOException { @@ -194,6 +193,7 @@ public class BlobStoreManager { return mService.createSession(blobHandle, mContext.getOpPackageName()); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); + e.maybeRethrow(LimitExceededException.class); throw new RuntimeException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -302,8 +302,9 @@ public class BlobStoreManager { * if the {@code leaseExpiryTimeMillis} is greater than the * {@link BlobHandle#getExpiryTimeMillis()}. * @throws LimitExceededException when a lease could not be acquired, such as when the - * caller is trying to acquire leases on too much data. Apps - * can avoid this by checking the remaining quota using + * caller is trying to acquire too many leases or acquire + * leases on too much data. Apps can avoid this by checking + * the remaining quota using * {@link #getRemainingLeaseQuotaBytes()} before trying to * acquire a lease. * @@ -362,8 +363,9 @@ public class BlobStoreManager { * if the {@code leaseExpiryTimeMillis} is greater than the * {@link BlobHandle#getExpiryTimeMillis()}. * @throws LimitExceededException when a lease could not be acquired, such as when the - * caller is trying to acquire leases on too much data. Apps - * can avoid this by checking the remaining quota using + * caller is trying to acquire too many leases or acquire + * leases on too much data. Apps can avoid this by checking + * the remaining quota using * {@link #getRemainingLeaseQuotaBytes()} before trying to * acquire a lease. * @@ -415,8 +417,9 @@ public class BlobStoreManager { * exist or the caller does not have access to it. * @throws IllegalArgumentException when {@code blobHandle} is invalid. * @throws LimitExceededException when a lease could not be acquired, such as when the - * caller is trying to acquire leases on too much data. Apps - * can avoid this by checking the remaining quota using + * caller is trying to acquire too many leases or acquire + * leases on too much data. Apps can avoid this by checking + * the remaining quota using * {@link #getRemainingLeaseQuotaBytes()} before trying to * acquire a lease. * @@ -462,8 +465,9 @@ public class BlobStoreManager { * exist or the caller does not have access to it. * @throws IllegalArgumentException when {@code blobHandle} is invalid. * @throws LimitExceededException when a lease could not be acquired, such as when the - * caller is trying to acquire leases on too much data. Apps - * can avoid this by checking the remaining quota using + * caller is trying to acquire too many leases or acquire + * leases on too much data. Apps can avoid this by checking + * the remaining quota using * {@link #getRemainingLeaseQuotaBytes()} before trying to * acquire a lease. * @@ -757,6 +761,8 @@ public class BlobStoreManager { * @throws SecurityException when the caller is not the owner of the session. * @throws IllegalStateException when the caller tries to change access for a blob which is * already committed. + * @throws LimitExceededException when the caller tries to explicitly allow too + * many packages using this API. */ public void allowPackageAccess(@NonNull String packageName, @NonNull byte[] certificate) throws IOException { @@ -764,6 +770,7 @@ public class BlobStoreManager { mSession.allowPackageAccess(packageName, certificate); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); + e.maybeRethrow(LimitExceededException.class); throw new RuntimeException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java index 265479f7c533..79cd1b17a5b5 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java @@ -141,6 +141,36 @@ class BlobStoreConfig { public static long DELETE_ON_LAST_LEASE_DELAY_MS = DEFAULT_DELETE_ON_LAST_LEASE_DELAY_MS; + /** + * Denotes the maximum number of active sessions per app at any time. + */ + public static final String KEY_MAX_ACTIVE_SESSIONS = "max_active_sessions"; + public static int DEFAULT_MAX_ACTIVE_SESSIONS = 250; + public static int MAX_ACTIVE_SESSIONS = DEFAULT_MAX_ACTIVE_SESSIONS; + + /** + * Denotes the maximum number of committed blobs per app at any time. + */ + public static final String KEY_MAX_COMMITTED_BLOBS = "max_committed_blobs"; + public static int DEFAULT_MAX_COMMITTED_BLOBS = 1000; + public static int MAX_COMMITTED_BLOBS = DEFAULT_MAX_COMMITTED_BLOBS; + + /** + * Denotes the maximum number of leased blobs per app at any time. + */ + public static final String KEY_MAX_LEASED_BLOBS = "max_leased_blobs"; + public static int DEFAULT_MAX_LEASED_BLOBS = 500; + public static int MAX_LEASED_BLOBS = DEFAULT_MAX_LEASED_BLOBS; + + /** + * Denotes the maximum number of packages explicitly permitted to access a blob + * (permitted as part of creating a {@link BlobAccessMode}). + */ + public static final String KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES = "max_permitted_pks"; + public static int DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES = 300; + public static int MAX_BLOB_ACCESS_PERMITTED_PACKAGES = + DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES; + static void refresh(Properties properties) { if (!NAMESPACE_BLOBSTORE.equals(properties.getNamespace())) { return; @@ -178,6 +208,19 @@ class BlobStoreConfig { DELETE_ON_LAST_LEASE_DELAY_MS = properties.getLong(key, DEFAULT_DELETE_ON_LAST_LEASE_DELAY_MS); break; + case KEY_MAX_ACTIVE_SESSIONS: + MAX_ACTIVE_SESSIONS = properties.getInt(key, DEFAULT_MAX_ACTIVE_SESSIONS); + break; + case KEY_MAX_COMMITTED_BLOBS: + MAX_COMMITTED_BLOBS = properties.getInt(key, DEFAULT_MAX_COMMITTED_BLOBS); + break; + case KEY_MAX_LEASED_BLOBS: + MAX_LEASED_BLOBS = properties.getInt(key, DEFAULT_MAX_LEASED_BLOBS); + break; + case KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES: + MAX_BLOB_ACCESS_PERMITTED_PACKAGES = properties.getInt(key, + DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES); + break; default: Slog.wtf(TAG, "Unknown key in device config properties: " + key); } @@ -210,6 +253,15 @@ class BlobStoreConfig { fout.println(String.format(dumpFormat, KEY_DELETE_ON_LAST_LEASE_DELAY_MS, TimeUtils.formatDuration(DELETE_ON_LAST_LEASE_DELAY_MS), TimeUtils.formatDuration(DEFAULT_DELETE_ON_LAST_LEASE_DELAY_MS))); + fout.println(String.format(dumpFormat, KEY_MAX_ACTIVE_SESSIONS, + MAX_ACTIVE_SESSIONS, DEFAULT_MAX_ACTIVE_SESSIONS)); + fout.println(String.format(dumpFormat, KEY_MAX_COMMITTED_BLOBS, + MAX_COMMITTED_BLOBS, DEFAULT_MAX_COMMITTED_BLOBS)); + fout.println(String.format(dumpFormat, KEY_MAX_LEASED_BLOBS, + MAX_LEASED_BLOBS, DEFAULT_MAX_LEASED_BLOBS)); + fout.println(String.format(dumpFormat, KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES, + MAX_BLOB_ACCESS_PERMITTED_PACKAGES, + DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES)); } } @@ -288,6 +340,34 @@ class BlobStoreConfig { return DeviceConfigProperties.DELETE_ON_LAST_LEASE_DELAY_MS; } + /** + * Returns the maximum number of active sessions per app. + */ + public static int getMaxActiveSessions() { + return DeviceConfigProperties.MAX_ACTIVE_SESSIONS; + } + + /** + * Returns the maximum number of committed blobs per app. + */ + public static int getMaxCommittedBlobs() { + return DeviceConfigProperties.MAX_COMMITTED_BLOBS; + } + + /** + * Returns the maximum number of leased blobs per app. + */ + public static int getMaxLeasedBlobs() { + return DeviceConfigProperties.MAX_LEASED_BLOBS; + } + + /** + * Returns the maximum number of packages explicitly permitted to access a blob. + */ + public static int getMaxPermittedPackages() { + return DeviceConfigProperties.MAX_BLOB_ACCESS_PERMITTED_PACKAGES; + } + @Nullable public static File prepareBlobFile(long sessionId) { final File blobsDir = prepareBlobsDir(); diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java index a90536fee904..7a6c884848d8 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -35,6 +35,9 @@ import static com.android.server.blob.BlobStoreConfig.TAG; import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT; import static com.android.server.blob.BlobStoreConfig.getAdjustedCommitTimeMs; import static com.android.server.blob.BlobStoreConfig.getDeletionOnLastLeaseDelayMs; +import static com.android.server.blob.BlobStoreConfig.getMaxActiveSessions; +import static com.android.server.blob.BlobStoreConfig.getMaxCommittedBlobs; +import static com.android.server.blob.BlobStoreConfig.getMaxLeasedBlobs; import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED; import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED; import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_INVALID; @@ -124,6 +127,7 @@ import java.util.List; import java.util.Objects; import java.util.Random; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; import java.util.function.Function; @@ -332,9 +336,26 @@ public class BlobStoreManagerService extends SystemService { mKnownBlobIds.add(id); } + @GuardedBy("mBlobsLock") + private int getSessionsCountLocked(int uid, String packageName) { + // TODO: Maintain a counter instead of traversing all the sessions + final AtomicInteger sessionsCount = new AtomicInteger(0); + forEachSessionInUser(session -> { + if (session.getOwnerUid() == uid && session.getOwnerPackageName().equals(packageName)) { + sessionsCount.getAndIncrement(); + } + }, UserHandle.getUserId(uid)); + return sessionsCount.get(); + } + private long createSessionInternal(BlobHandle blobHandle, int callingUid, String callingPackage) { synchronized (mBlobsLock) { + final int sessionsCount = getSessionsCountLocked(callingUid, callingPackage); + if (sessionsCount >= getMaxActiveSessions()) { + throw new LimitExceededException("Too many active sessions for the caller: " + + sessionsCount); + } // TODO: throw if there is already an active session associated with blobHandle. final long sessionId = generateNextSessionIdLocked(); final BlobStoreSession session = new BlobStoreSession(mContext, @@ -408,10 +429,39 @@ public class BlobStoreManagerService extends SystemService { } } + @GuardedBy("mBlobsLock") + private int getCommittedBlobsCountLocked(int uid, String packageName) { + // TODO: Maintain a counter instead of traversing all the blobs + final AtomicInteger blobsCount = new AtomicInteger(0); + forEachBlobInUser((blobMetadata) -> { + if (blobMetadata.isACommitter(packageName, uid)) { + blobsCount.getAndIncrement(); + } + }, UserHandle.getUserId(uid)); + return blobsCount.get(); + } + + @GuardedBy("mBlobsLock") + private int getLeasedBlobsCountLocked(int uid, String packageName) { + // TODO: Maintain a counter instead of traversing all the blobs + final AtomicInteger blobsCount = new AtomicInteger(0); + forEachBlobInUser((blobMetadata) -> { + if (blobMetadata.isALeasee(packageName, uid)) { + blobsCount.getAndIncrement(); + } + }, UserHandle.getUserId(uid)); + return blobsCount.get(); + } + private void acquireLeaseInternal(BlobHandle blobHandle, int descriptionResId, CharSequence description, long leaseExpiryTimeMillis, int callingUid, String callingPackage) { synchronized (mBlobsLock) { + final int leasesCount = getLeasedBlobsCountLocked(callingUid, callingPackage); + if (leasesCount >= getMaxLeasedBlobs()) { + throw new LimitExceededException("Too many leased blobs for the caller: " + + leasesCount); + } final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid)) .get(blobHandle); if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller( @@ -626,6 +676,18 @@ public class BlobStoreManagerService extends SystemService { break; case STATE_VERIFIED_VALID: synchronized (mBlobsLock) { + final int committedBlobsCount = getCommittedBlobsCountLocked( + session.getOwnerUid(), session.getOwnerPackageName()); + if (committedBlobsCount >= getMaxCommittedBlobs()) { + Slog.d(TAG, "Failed to commit: too many committed blobs. count: " + + committedBlobsCount + "; blob: " + session); + session.sendCommitCallbackResult(COMMIT_RESULT_ERROR); + session.getSessionFile().delete(); + mActiveBlobIds.remove(session.getSessionId()); + getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())) + .remove(session.getSessionId()); + break; + } final int userId = UserHandle.getUserId(session.getOwnerUid()); final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked( userId); @@ -656,7 +718,7 @@ public class BlobStoreManagerService extends SystemService { } else { blob.addOrReplaceCommitter(existingCommitter); } - Slog.d(TAG, "Error committing the blob", e); + Slog.d(TAG, "Error committing the blob: " + session, e); FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED, session.getOwnerUid(), blob.getBlobId(), blob.getSize(), FrameworkStatsLog.BLOB_COMMITTED__RESULT__ERROR_DURING_COMMIT); @@ -1096,6 +1158,16 @@ public class BlobStoreManagerService extends SystemService { void runClearAllSessions(@UserIdInt int userId) { synchronized (mBlobsLock) { + for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { + final int sessionUserId = mSessions.keyAt(i); + if (userId != UserHandle.USER_ALL && userId != sessionUserId) { + continue; + } + final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i); + for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) { + mActiveBlobIds.remove(userSessions.valueAt(j).getSessionId()); + } + } if (userId == UserHandle.USER_ALL) { mSessions.clear(); } else { @@ -1107,6 +1179,16 @@ public class BlobStoreManagerService extends SystemService { void runClearAllBlobs(@UserIdInt int userId) { synchronized (mBlobsLock) { + for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { + final int blobUserId = mBlobsMap.keyAt(i); + if (userId != UserHandle.USER_ALL && userId != blobUserId) { + continue; + } + final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i); + for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) { + mActiveBlobIds.remove(userBlobs.valueAt(j).getBlobId()); + } + } if (userId == UserHandle.USER_ALL) { mBlobsMap.clear(); } else { @@ -1331,8 +1413,11 @@ public class BlobStoreManagerService extends SystemService { + "callingUid=" + callingUid + ", callingPackage=" + packageName); } - // TODO: Verify caller request is within limits (no. of calls/blob sessions/blobs) - return createSessionInternal(blobHandle, callingUid, packageName); + try { + return createSessionInternal(blobHandle, callingUid, packageName); + } catch (LimitExceededException e) { + throw new ParcelableException(e); + } } @Override diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java index baafff53d072..53e296ba3d11 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java @@ -32,6 +32,7 @@ import static android.text.format.Formatter.formatFileSize; import static com.android.server.blob.BlobStoreConfig.TAG; import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_SESSION_CREATION_TIME; +import static com.android.server.blob.BlobStoreConfig.getMaxPermittedPackages; import static com.android.server.blob.BlobStoreConfig.hasSessionExpired; import android.annotation.BytesLong; @@ -43,7 +44,9 @@ import android.app.blob.IBlobStoreSession; import android.content.Context; import android.os.Binder; import android.os.FileUtils; +import android.os.LimitExceededException; import android.os.ParcelFileDescriptor; +import android.os.ParcelableException; import android.os.RemoteException; import android.os.RevocableFileDescriptor; import android.os.Trace; @@ -76,7 +79,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Objects; -/** TODO: add doc */ +/** + * Class to represent the state corresponding to an ongoing + * {@link android.app.blob.BlobStoreManager.Session} + */ @VisibleForTesting class BlobStoreSession extends IBlobStoreSession.Stub { @@ -326,6 +332,11 @@ class BlobStoreSession extends IBlobStoreSession.Stub { throw new IllegalStateException("Not allowed to change access type in state: " + stateToString(mState)); } + if (mBlobAccessMode.getNumWhitelistedPackages() >= getMaxPermittedPackages()) { + throw new ParcelableException(new LimitExceededException( + "Too many packages permitted to access the blob: " + + mBlobAccessMode.getNumWhitelistedPackages())); + } mBlobAccessMode.allowPackageAccess(packageName, certificate); } } |