summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java103
1 files changed, 103 insertions, 0 deletions
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<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)
@@ -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<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.
@@ -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<String, Integer> 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<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(
@@ -6486,15 +6582,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,
@@ -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);
}
}
}