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

Commit eb0340b0 authored by Dmitry Dementyev's avatar Dmitry Dementyev
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.

Change-Id: I88a0fef8e2e7bc232768bd5f7aa3f4bf87cb1c2c
parent b5ef2c67
Loading
Loading
Loading
Loading
+103 −0
Original line number Original line Diff line number Diff line
@@ -185,6 +185,8 @@ public class AccountManagerService
    final MessageHandler mHandler;
    final MessageHandler mHandler;


    private static final int TIMEOUT_DELAY_MS = 1000 * 60 * 15;
    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
    // Messages that can be sent on mHandler
    private static final int MESSAGE_TIMED_OUT = 3;
    private static final int MESSAGE_TIMED_OUT = 3;
    private static final int MESSAGE_COPY_SHARED_ACCOUNT = 4;
    private static final int MESSAGE_COPY_SHARED_ACCOUNT = 4;
@@ -224,6 +226,8 @@ public class AccountManagerService
        private final TokenCache accountTokenCaches = new TokenCache();
        private final TokenCache accountTokenCaches = new TokenCache();
        /** protected by the {@link #cacheLock} */
        /** protected by the {@link #cacheLock} */
        private final Map<Account, Map<String, Integer>> visibilityCache = new HashMap<>();
        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},
        /** protected by the {@link #mReceiversForType},
         *  type -> (packageName -> number of active receivers)
         *  type -> (packageName -> number of active receivers)
@@ -1108,6 +1112,65 @@ public class AccountManagerService
        validateAccountsInternal(accounts, true /* invalidateAuthenticatorCache */);
        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
     * Validate internal set of accounts against installed authenticators for
     * given user. Clear cached authenticators before validating when requested.
     * given user. Clear cached authenticators before validating when requested.
@@ -1225,6 +1288,7 @@ public class AccountManagerService
                            accounts.authTokenCache.remove(account);
                            accounts.authTokenCache.remove(account);
                            accounts.accountTokenCaches.remove(account);
                            accounts.accountTokenCaches.remove(account);
                            accounts.visibilityCache.remove(account);
                            accounts.visibilityCache.remove(account);
                            accounts.mCacheSizeForAccount.remove(account);


                            for (Entry<String, Integer> packageToVisibility :
                            for (Entry<String, Integer> packageToVisibility :
                                    packagesToVisibility.entrySet()) {
                                    packagesToVisibility.entrySet()) {
@@ -1834,6 +1898,10 @@ public class AccountManagerService
            Log.w(TAG, "Account cannot be added - Name longer than 200 chars");
            Log.w(TAG, "Account cannot be added - Name longer than 200 chars");
            return false;
            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)) {
        if (!isLocalUnlockedUser(accounts.userId)) {
            Log.w(TAG, "Account " + account.toSafeString() + " cannot be added - user "
            Log.w(TAG, "Account " + account.toSafeString() + " cannot be added - user "
                    + accounts.userId + " is locked. callingUid=" + callingUid);
                    + accounts.userId + " is locked. callingUid=" + callingUid);
@@ -2187,6 +2255,7 @@ public class AccountManagerService
                        renamedAccount,
                        renamedAccount,
                        new AtomicReference<>(accountToRename.name));
                        new AtomicReference<>(accountToRename.name));
                resultAccount = renamedAccount;
                resultAccount = renamedAccount;
                recomputeCacheSizeForAccountLocked(accounts, renamedAccount);


                int parentUserId = accounts.userId;
                int parentUserId = accounts.userId;
                if (canHaveProfile(parentUserId)) {
                if (canHaveProfile(parentUserId)) {
@@ -2569,6 +2638,10 @@ public class AccountManagerService
        cancelNotification(getSigninRequiredNotificationId(accounts, account),
        cancelNotification(getSigninRequiredNotificationId(accounts, account),
                UserHandle.of(accounts.userId));
                UserHandle.of(accounts.userId));
        synchronized (accounts.dbLock) {
        synchronized (accounts.dbLock) {
            boolean shouldBlockWrite = false;
            synchronized (accounts.cacheLock) {
                shouldBlockWrite = shouldBlockDatabaseWrite(accounts, account, type, authToken);
            }
            accounts.accountsDb.beginTransaction();
            accounts.accountsDb.beginTransaction();
            boolean updateCache = false;
            boolean updateCache = false;
            try {
            try {
@@ -2577,6 +2650,11 @@ public class AccountManagerService
                    return false;
                    return false;
                }
                }
                accounts.accountsDb.deleteAuthtokensByAccountIdAndType(accountId, type);
                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) {
                if (accounts.accountsDb.insertAuthToken(accountId, type, authToken) >= 0) {
                    accounts.accountsDb.setTransactionSuccessful();
                    accounts.accountsDb.setTransactionSuccessful();
                    updateCache = true;
                    updateCache = true;
@@ -2686,6 +2764,10 @@ public class AccountManagerService
        if (account == null) {
        if (account == null) {
            return;
            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;
        boolean isChanged = false;
        synchronized (accounts.dbLock) {
        synchronized (accounts.dbLock) {
            synchronized (accounts.cacheLock) {
            synchronized (accounts.cacheLock) {
@@ -2794,6 +2876,14 @@ public class AccountManagerService
    private void setUserdataInternal(UserAccounts accounts, Account account, String key,
    private void setUserdataInternal(UserAccounts accounts, Account account, String key,
            String value) {
            String value) {
        synchronized (accounts.dbLock) {
        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();
            accounts.accountsDb.beginTransaction();
            try {
            try {
                long accountId = accounts.accountsDb.findDeAccountId(account);
                long accountId = accounts.accountsDb.findDeAccountId(account);
@@ -6024,6 +6114,7 @@ public class AccountManagerService
        accounts.authTokenCache.remove(account);
        accounts.authTokenCache.remove(account);
        accounts.previousNameCache.remove(account);
        accounts.previousNameCache.remove(account);
        accounts.visibilityCache.remove(account);
        accounts.visibilityCache.remove(account);
        accounts.mCacheSizeForAccount.remove(account);


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


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


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