Force garbage collection after credential verification
This is to sanitize memory containing sensitive user
lockscreen credentials. Most of LockSettingsService
already sanitizes credentials when needed, but there
is one copy of the credential unmarshalled from the
binder transaction and passed into LSS as argument
which is not easily sanitiziable manually except
by forcing a garbage collection.
Bug: 144537463
Test: atest com.android.server.locksettings
Test: go through Settings password change flow,
then take a heapdump of system_server and
verifies no password shard exists in the dump.
Change-Id: I3b0a2dab5766c40bc3ba9b38311c039337c408d3
This commit is contained in:
@@ -1616,6 +1616,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
synchronized (mSeparateChallengeLock) {
|
||||
if (!setLockCredentialInternal(credential, savedCredential,
|
||||
userId, /* isLockTiedToParent= */ false)) {
|
||||
scheduleGc();
|
||||
return false;
|
||||
}
|
||||
setSeparateProfileChallengeEnabledLocked(userId, true, /* unused */ null);
|
||||
@@ -1626,6 +1627,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
setDeviceUnlockedForUser(userId);
|
||||
}
|
||||
notifySeparateProfileChallengeChanged(userId);
|
||||
scheduleGc();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1965,7 +1967,11 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
public VerifyCredentialResponse checkCredential(LockscreenCredential credential, int userId,
|
||||
ICheckCredentialProgressCallback progressCallback) {
|
||||
checkPasswordReadPermission(userId);
|
||||
return doVerifyCredential(credential, CHALLENGE_NONE, 0, userId, progressCallback);
|
||||
try {
|
||||
return doVerifyCredential(credential, CHALLENGE_NONE, 0, userId, progressCallback);
|
||||
} finally {
|
||||
scheduleGc();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1978,8 +1984,12 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
challengeType = CHALLENGE_NONE;
|
||||
|
||||
}
|
||||
return doVerifyCredential(credential, challengeType, challenge, userId,
|
||||
null /* progressCallback */);
|
||||
try {
|
||||
return doVerifyCredential(credential, challengeType, challenge, userId,
|
||||
null /* progressCallback */);
|
||||
} finally {
|
||||
scheduleGc();
|
||||
}
|
||||
}
|
||||
|
||||
private VerifyCredentialResponse doVerifyCredential(LockscreenCredential credential,
|
||||
@@ -2070,6 +2080,8 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
| BadPaddingException | CertificateException | IOException e) {
|
||||
Slog.e(TAG, "Failed to decrypt child profile key", e);
|
||||
throw new IllegalStateException("Unable to get tied profile token");
|
||||
} finally {
|
||||
scheduleGc();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2983,27 +2995,31 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
@Override
|
||||
public byte[] getHashFactor(LockscreenCredential currentCredential, int userId) {
|
||||
checkPasswordReadPermission(userId);
|
||||
if (isManagedProfileWithUnifiedLock(userId)) {
|
||||
try {
|
||||
currentCredential = getDecryptedPasswordForTiedProfile(userId);
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "Failed to get work profile credential", e);
|
||||
return null;
|
||||
try {
|
||||
if (isManagedProfileWithUnifiedLock(userId)) {
|
||||
try {
|
||||
currentCredential = getDecryptedPasswordForTiedProfile(userId);
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "Failed to get work profile credential", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
synchronized (mSpManager) {
|
||||
if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
|
||||
Slog.w(TAG, "Synthetic password not enabled");
|
||||
return null;
|
||||
synchronized (mSpManager) {
|
||||
if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
|
||||
Slog.w(TAG, "Synthetic password not enabled");
|
||||
return null;
|
||||
}
|
||||
long handle = getSyntheticPasswordHandleLocked(userId);
|
||||
AuthenticationResult auth = mSpManager.unwrapPasswordBasedSyntheticPassword(
|
||||
getGateKeeperService(), handle, currentCredential, userId, null);
|
||||
if (auth.authToken == null) {
|
||||
Slog.w(TAG, "Current credential is incorrect");
|
||||
return null;
|
||||
}
|
||||
return auth.authToken.derivePasswordHashFactor();
|
||||
}
|
||||
long handle = getSyntheticPasswordHandleLocked(userId);
|
||||
AuthenticationResult auth = mSpManager.unwrapPasswordBasedSyntheticPassword(
|
||||
getGateKeeperService(), handle, currentCredential, userId, null);
|
||||
if (auth.authToken == null) {
|
||||
Slog.w(TAG, "Current credential is incorrect");
|
||||
return null;
|
||||
}
|
||||
return auth.authToken.derivePasswordHashFactor();
|
||||
} finally {
|
||||
scheduleGc();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3287,6 +3303,22 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules garbage collection to sanitize lockscreen credential remnants in memory.
|
||||
*
|
||||
* One source of leftover lockscreen credentials is the unmarshalled binder method arguments.
|
||||
* Since this method will be called within the binder implementation method, a small delay is
|
||||
* added before the GC operation to allow the enclosing binder proxy code to complete and
|
||||
* release references to the argument.
|
||||
*/
|
||||
private void scheduleGc() {
|
||||
mHandler.postDelayed(() -> {
|
||||
System.gc();
|
||||
System.runFinalization();
|
||||
System.gc();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
private class DeviceProvisionedObserver extends ContentObserver {
|
||||
private final Uri mDeviceProvisionedUri = Settings.Global.getUriFor(
|
||||
Settings.Global.DEVICE_PROVISIONED);
|
||||
|
||||
Reference in New Issue
Block a user