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

Commit 1d038c71 authored by Dmitry Dementyev's avatar Dmitry Dementyev Committed by Mohammed Althaf T
Browse files

Calculate how much memory is used per account.

Prevent storing new data in AccountManager DB if too much storage is used.

Test: manual
Bug:273501008
Flag: EXEMPT security fix.

(cherry picked from commit 6b8138ca)
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:14531c2047668c73106bcbb580601b9fd13bd171)
Merged-In: I88a0fef8e2e7bc232768bd5f7aa3f4bf87cb1c2c
Change-Id: I88a0fef8e2e7bc232768bd5f7aa3f4bf87cb1c2c
parent 78153cc8
Loading
Loading
Loading
Loading
+103 −0
Original line number Diff line number Diff line
@@ -192,6 +192,8 @@ public class AccountManagerService
    final MessageHandler mHandler;

    private static final int TIMEOUT_DELAY_MS = 1000 * 60 * 15;
    private static final int MAXIMUM_PASSWORD_LENGTH = 1000 * 1000;
    private static final int STORAGE_LIMIT_PER_USER = 30 * 1000 * 1000;
    // Messages that can be sent on mHandler
    private static final int MESSAGE_TIMED_OUT = 3;
    private static final int MESSAGE_COPY_SHARED_ACCOUNT = 4;
@@ -242,6 +244,8 @@ public class AccountManagerService
        private final TokenCache accountTokenCaches = new TokenCache();
        /** protected by the {@link #cacheLock} */
        private final Map<Account, Map<String, Integer>> visibilityCache = new HashMap<>();
        /** protected by the {@link #cacheLock} */
        private final Map<Account, Integer> mCacheSizeForAccount = new HashMap<>();

        /** protected by the {@link #mReceiversForType},
         *  type -> (packageName -> number of active receivers)
@@ -1150,6 +1154,65 @@ public class AccountManagerService
        validateAccountsInternal(accounts, true /* invalidateAuthenticatorCache */);
    }

    private int computeEntrySize(@Nullable String key, @Nullable String value) {
        int keySize = key != null ? key.length() : 1;
        int valueSize = value != null ? value.length() : 1;
        return keySize + valueSize + 20;
    }

    /**
     * Restricts write operation if account uses too much storage.
     * Protected by the {@code cacheLock}
     */
    private boolean shouldBlockDatabaseWrite(UserAccounts accounts, Account account,
            @Nullable String key, @Nullable String value) {
        int usedStorage = accounts.mCacheSizeForAccount.getOrDefault(account, 0);
        // Estimation is not precise for updates to existing values.
        usedStorage = usedStorage + computeEntrySize(key, value);
        accounts.mCacheSizeForAccount.put(account, usedStorage);
        if (usedStorage < STORAGE_LIMIT_PER_USER / 100) {
            return false; // 100 is the upper bound for total number of accounts.
        }
        long numberOfAccounts = 0;
        for (Account[] accountsPerType : accounts.accountCache.values()) {
            if (accountsPerType != null) {
                numberOfAccounts = numberOfAccounts + accountsPerType.length;
            }
        }
        numberOfAccounts = numberOfAccounts != 0 ? numberOfAccounts : 1; // avoid division by zero.
        if (usedStorage < STORAGE_LIMIT_PER_USER / numberOfAccounts) {
            return false;
        }
        // Get more precise estimation of the  used storage before blocking operation.
        recomputeCacheSizeForAccountLocked(accounts, account);
        usedStorage = accounts.mCacheSizeForAccount.getOrDefault(account, 0);
        usedStorage = usedStorage + computeEntrySize(key, value);
        accounts.mCacheSizeForAccount.put(account, usedStorage);
        if (usedStorage < STORAGE_LIMIT_PER_USER / numberOfAccounts) {
            return false;
        }
        Log.w(TAG, "Account of type=" + account.type + " uses too much storage: " + usedStorage);
        return true;
    }

    /** protected by the {@code cacheLock} */
    private void recomputeCacheSizeForAccountLocked(UserAccounts accounts, Account account) {
        Map<String, String> userDataForAccount = accounts.userDataCache.get(account);
        Map<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
        int usedStorage = 0;
        if (userDataForAccount != null) {
            for (Map.Entry<String, String> entry : userDataForAccount.entrySet()) {
                usedStorage = usedStorage + computeEntrySize(entry.getKey(), entry.getValue());
            }
        }
        if (authTokensForAccount != null) {
            for (Map.Entry<String, String> entry : authTokensForAccount.entrySet()) {
                usedStorage = usedStorage + computeEntrySize(entry.getKey(), entry.getValue());
            }
        }
        accounts.mCacheSizeForAccount.put(account, usedStorage);
    }

    /**
     * Validate internal set of accounts against installed authenticators for
     * given user. Clear cached authenticators before validating when requested.
@@ -1269,6 +1332,7 @@ public class AccountManagerService
                            accounts.authTokenCache.remove(account);
                            accounts.accountTokenCaches.remove(account);
                            accounts.visibilityCache.remove(account);
                            accounts.mCacheSizeForAccount.remove(account);

                            for (Entry<String, Integer> packageToVisibility :
                                    packagesToVisibility.entrySet()) {
@@ -1891,6 +1955,10 @@ public class AccountManagerService
            Log.w(TAG, "Account cannot be added - Name longer than 200 chars");
            return false;
        }
        if (password != null && password.length() > MAXIMUM_PASSWORD_LENGTH) {
            Log.w(TAG, "Account cannot be added - password is too long");
            return false;
        }
        if (!isLocalUnlockedUser(accounts.userId)) {
            Log.w(TAG, "Account " + account.toSafeString() + " cannot be added - user "
                    + accounts.userId + " is locked. callingUid=" + callingUid);
@@ -2258,6 +2326,7 @@ public class AccountManagerService
                        renamedAccount,
                        new AtomicReference<>(accountToRename.name));
                resultAccount = renamedAccount;
                recomputeCacheSizeForAccountLocked(accounts, renamedAccount);

                int parentUserId = accounts.userId;
                if (canHaveProfile(parentUserId)) {
@@ -2664,6 +2733,10 @@ public class AccountManagerService
        }
        cancelNotification(getSigninRequiredNotificationId(accounts, account), accounts);
        synchronized (accounts.dbLock) {
            boolean shouldBlockWrite = false;
            synchronized (accounts.cacheLock) {
                shouldBlockWrite = shouldBlockDatabaseWrite(accounts, account, type, authToken);
            }
            accounts.accountsDb.beginTransaction();
            boolean updateCache = false;
            try {
@@ -2672,6 +2745,11 @@ public class AccountManagerService
                    return false;
                }
                accounts.accountsDb.deleteAuthtokensByAccountIdAndType(accountId, type);
                if (authToken != null && shouldBlockWrite) {
                    Log.w(TAG, "Too much storage is used - block token update for accountType="
                            + account.type);
                    return false; // fail silently.
                }
                if (accounts.accountsDb.insertAuthToken(accountId, type, authToken) >= 0) {
                    accounts.accountsDb.setTransactionSuccessful();
                    updateCache = true;
@@ -2781,6 +2859,10 @@ public class AccountManagerService
        if (account == null) {
            return;
        }
        if (password != null && password.length() > MAXIMUM_PASSWORD_LENGTH) {
            Log.w(TAG, "New password is too long for accountType=" + account.type);
            return;
        }
        boolean isChanged = false;
        synchronized (accounts.dbLock) {
            synchronized (accounts.cacheLock) {
@@ -2891,6 +2973,14 @@ public class AccountManagerService
    private void setUserdataInternal(UserAccounts accounts, Account account, String key,
            String value) {
        synchronized (accounts.dbLock) {
            synchronized (accounts.cacheLock) {
                if (value != null && shouldBlockDatabaseWrite(accounts, account, key, value)) {
                    Log.w(TAG, "Too much storage is used - block user data update for accountType="
                            + account.type);
                    return; // fail silently.
                }
            }

            accounts.accountsDb.beginTransaction();
            try {
                long accountId = accounts.accountsDb.findDeAccountId(account);
@@ -6181,6 +6271,7 @@ public class AccountManagerService
        accounts.authTokenCache.remove(account);
        accounts.previousNameCache.remove(account);
        accounts.visibilityCache.remove(account);
        accounts.mCacheSizeForAccount.remove(account);

        AccountManager.invalidateLocalAccountsDataCaches();
    }
@@ -6358,15 +6449,20 @@ public class AccountManagerService
    protected void writeUserDataIntoCacheLocked(UserAccounts accounts,
            Account account, String key, String value) {
        Map<String, String> userDataForAccount = accounts.userDataCache.get(account);
        boolean updateCacheSize = false;
        if (userDataForAccount == null) {
            userDataForAccount = accounts.accountsDb.findUserExtrasForAccount(account);
            accounts.userDataCache.put(account, userDataForAccount);
            updateCacheSize = true;
        }
        if (value == null) {
            userDataForAccount.remove(key);
        } else {
            userDataForAccount.put(key, value);
        }
        if (updateCacheSize) {
            recomputeCacheSizeForAccountLocked(accounts, account);
        }
    }

    protected TokenCache.Value readCachedTokenInternal(
@@ -6385,15 +6481,20 @@ public class AccountManagerService
    protected void writeAuthTokenIntoCacheLocked(UserAccounts accounts,
            Account account, String key, String value) {
        Map<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
        boolean updateCacheSize = false;
        if (authTokensForAccount == null) {
            authTokensForAccount = accounts.accountsDb.findAuthTokensByAccount(account);
            accounts.authTokenCache.put(account, authTokensForAccount);
            updateCacheSize = true;
        }
        if (value == null) {
            authTokensForAccount.remove(key);
        } else {
            authTokensForAccount.put(key, value);
        }
        if (updateCacheSize) {
            recomputeCacheSizeForAccountLocked(accounts, account);
        }
    }

    protected String readAuthTokenInternal(UserAccounts accounts, Account account,
@@ -6413,6 +6514,7 @@ public class AccountManagerService
                    // need to populate the cache for this account
                    authTokensForAccount = accounts.accountsDb.findAuthTokensByAccount(account);
                    accounts.authTokenCache.put(account, authTokensForAccount);
                    recomputeCacheSizeForAccountLocked(accounts, account);
                }
                return authTokensForAccount.get(authTokenType);
            }
@@ -6434,6 +6536,7 @@ public class AccountManagerService
                        // need to populate the cache for this account
                        userDataForAccount = accounts.accountsDb.findUserExtrasForAccount(account);
                        accounts.userDataCache.put(account, userDataForAccount);
                        recomputeCacheSizeForAccountLocked(accounts, account);
                    }
                }
            }