Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 416d7d1e authored by Sudheer Shanka's avatar Sudheer Shanka Committed by Automerger Merge Worker
Browse files

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

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/11978930

Change-Id: I139c690f2062a8f43cbace96b1b64eca8948b242
parents c3f595fd c9f2fdc0
Loading
Loading
Loading
Loading
+18 −11
Original line number Diff line number Diff line
@@ -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();
+80 −0
Original line number Diff line number Diff line
@@ -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();
+88 −3
Original line number Diff line number Diff line
@@ -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)
            try {
                return createSessionInternal(blobHandle, callingUid, packageName);
            } catch (LimitExceededException e) {
                throw new ParcelableException(e);
            }
        }

        @Override
+12 −1
Original line number Diff line number Diff line
@@ -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);
        }
    }