Merge "Add a limit on no. of active sessions, committed/leased blobs." into rvc-dev

This commit is contained in:
Sudheer Shanka
2020-06-24 22:07:59 +00:00
committed by Android (Google) Code Review
4 changed files with 198 additions and 15 deletions

View File

@@ -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();

View File

@@ -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();

View File

@@ -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

View File

@@ -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);
}
}