From 2801cb86a532f1362a0258d060667f916e2f6714 Mon Sep 17 00:00:00 2001 From: Dmitry Dementyev Date: Wed, 4 Jun 2025 12:34:28 -0700 Subject: [SP 2025-09-01] 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 (cherry picked from commit 6b8138ca5279b89d99884092cd3a5ca777f027be) --- .../server/accounts/AccountManagerService.java | 103 +++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index dc59b65a343d..77d848cc513d 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -206,6 +206,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; @@ -256,6 +258,8 @@ public class AccountManagerService private final TokenCache accountTokenCaches = new TokenCache(); /** protected by the {@link #cacheLock} */ private final Map> visibilityCache = new HashMap<>(); + /** protected by the {@link #cacheLock} */ + private final Map mCacheSizeForAccount = new HashMap<>(); /** protected by the {@link #mReceiversForType}, * type -> (packageName -> number of active receivers) @@ -1157,6 +1161,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 userDataForAccount = accounts.userDataCache.get(account); + Map authTokensForAccount = accounts.authTokenCache.get(account); + int usedStorage = 0; + if (userDataForAccount != null) { + for (Map.Entry entry : userDataForAccount.entrySet()) { + usedStorage = usedStorage + computeEntrySize(entry.getKey(), entry.getValue()); + } + } + if (authTokensForAccount != null) { + for (Map.Entry 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. @@ -1286,6 +1349,7 @@ public class AccountManagerService accounts.authTokenCache.remove(account); accounts.accountTokenCaches.remove(account); accounts.visibilityCache.remove(account); + accounts.mCacheSizeForAccount.remove(account); for (Entry packageToVisibility : packagesToVisibility.entrySet()) { @@ -1916,6 +1980,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); @@ -2291,6 +2359,7 @@ public class AccountManagerService renamedAccount, new AtomicReference<>(accountToRename.name)); resultAccount = renamedAccount; + recomputeCacheSizeForAccountLocked(accounts, renamedAccount); int parentUserId = accounts.userId; if (canHaveProfile(parentUserId)) { @@ -2709,6 +2778,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 { @@ -2717,6 +2790,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; @@ -2826,6 +2904,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) { @@ -2944,6 +3026,14 @@ public class AccountManagerService private void setUserdataInternal(UserAccounts accounts, Account account, String key, String value, int callingUid) { 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); @@ -6282,6 +6372,7 @@ public class AccountManagerService accounts.authTokenCache.remove(account); accounts.previousNameCache.remove(account); accounts.visibilityCache.remove(account); + accounts.mCacheSizeForAccount.remove(account); AccountManager.invalidateLocalAccountsDataCaches(); } @@ -6459,15 +6550,20 @@ public class AccountManagerService protected void writeUserDataIntoCacheLocked(UserAccounts accounts, Account account, String key, String value) { Map 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( @@ -6486,15 +6582,20 @@ public class AccountManagerService protected void writeAuthTokenIntoCacheLocked(UserAccounts accounts, Account account, String key, String value) { Map 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, @@ -6514,6 +6615,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); } @@ -6535,6 +6637,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); } } } -- cgit v1.2.3-59-g8ed1b