diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index f6a923d8ce749..55a26fee407f8 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -1789,4 +1789,12 @@ public class LockPatternUtils { public void unlockUserKeyIfUnsecured(@UserIdInt int userId) { getLockSettingsInternal().unlockUserKeyIfUnsecured(userId); } + + public void createNewUser(@UserIdInt int userId, int userSerialNumber) { + getLockSettingsInternal().createNewUser(userId, userSerialNumber); + } + + public void removeUser(@UserIdInt int userId) { + getLockSettingsInternal().removeUser(userId); + } } diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java index 20592a5da9c54..5b08bb1f42d36 100644 --- a/core/java/com/android/internal/widget/LockSettingsInternal.java +++ b/core/java/com/android/internal/widget/LockSettingsInternal.java @@ -64,6 +64,26 @@ public abstract class LockSettingsInternal { */ public abstract void unlockUserKeyIfUnsecured(@UserIdInt int userId); + /** + * Creates the locksettings state for a new user. + *
+ * This includes creating a synthetic password and protecting it with an empty LSKF. + * + * @param userId the ID of the new user + * @param userSerialNumber the serial number of the new user + */ + public abstract void createNewUser(@UserIdInt int userId, int userSerialNumber); + + /** + * Removes the locksettings state for the given user. + *
+ * This includes removing the user's synthetic password and any protectors that are protecting + * it. + * + * @param userId the ID of the user being removed + */ + public abstract void removeUser(@UserIdInt int userId); + /** * Create an escrow token for the current user, which can later be used to unlock FBE * or change user password. diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 483ce92d8a65f..5a77c76ddb7ec 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -113,6 +113,7 @@ import android.util.EventLog; import android.util.LongSparseArray; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseIntArray; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; @@ -244,6 +245,17 @@ public class LockSettingsService extends ILockSettings.Stub { private final RebootEscrowManager mRebootEscrowManager; + // Locking order is mUserCreationAndRemovalLock -> mSpManager. + private final Object mUserCreationAndRemovalLock = new Object(); + // These two arrays are only used at boot time. To save memory, they are set to null when + // PHASE_BOOT_COMPLETED is reached. + @GuardedBy("mUserCreationAndRemovalLock") + private SparseIntArray mEarlyCreatedUsers = new SparseIntArray(); + @GuardedBy("mUserCreationAndRemovalLock") + private SparseIntArray mEarlyRemovedUsers = new SparseIntArray(); + @GuardedBy("mUserCreationAndRemovalLock") + private boolean mBootComplete; + // Current password metric for all users on the device. Updated when user unlocks // the device or changes password. Removed when user is stopped. @GuardedBy("this") @@ -284,9 +296,16 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public void onBootPhase(int phase) { super.onBootPhase(phase); - if (phase == PHASE_ACTIVITY_MANAGER_READY) { - mLockSettingsService.migrateOldDataAfterSystemReady(); - mLockSettingsService.loadEscrowData(); + switch (phase) { + case PHASE_ACTIVITY_MANAGER_READY: + mLockSettingsService.migrateOldDataAfterSystemReady(); + mLockSettingsService.loadEscrowData(); + break; + case PHASE_BOOT_COMPLETED: + mLockSettingsService.bootCompleted(); + break; + default: + break; } } @@ -578,7 +597,6 @@ public class LockSettingsService extends ILockSettings.Stub { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_ADDED); filter.addAction(Intent.ACTION_USER_STARTING); - filter.addAction(Intent.ACTION_USER_REMOVED); injector.getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null); @@ -721,28 +739,32 @@ public class LockSettingsService extends ILockSettings.Stub { } /** - * Clean up states associated with the given user, in case the userId is reused but LSS didn't - * get a chance to do cleanup previously during ACTION_USER_REMOVED. - * - * Internally, LSS stores serial number for each user and check it against the current user's - * serial number to determine if the userId is reused and invoke cleanup code. + * Removes the LSS state for the given userId if the userId was reused without its LSS state + * being fully removed. + *
+ * This is primarily needed for users that were removed by Android 13 or earlier, which didn't + * guarantee removal of LSS state as it relied on the {@code ACTION_USER_REMOVED} intent. It is + * also needed because {@link #removeUser()} delays requests to remove LSS state until the + * {@code PHASE_BOOT_COMPLETED} boot phase, so they can be lost. + *
+ * Stale state is detected by checking whether the user serial number changed. This works
+ * because user serial numbers are never reused.
*/
- private void cleanupDataForReusedUserIdIfNecessary(int userId) {
+ private void removeStateForReusedUserIdIfNecessary(@UserIdInt int userId, int serialNumber) {
if (userId == UserHandle.USER_SYSTEM) {
// Short circuit as we never clean up user 0.
return;
}
- // Serial number is never reusued, so we can use it as a distinguisher for user Id reuse.
- int serialNumber = mUserManager.getUserSerialNumber(userId);
-
int storedSerialNumber = mStorage.getInt(USER_SERIAL_NUMBER_KEY, -1, userId);
if (storedSerialNumber != serialNumber) {
// If LockSettingsStorage does not have a copy of the serial number, it could be either
// this is a user created before the serial number recording logic is introduced, or
// the user does not exist or was removed and cleaned up properly. In either case, don't
- // invoke removeUser().
+ // invoke removeUserState().
if (storedSerialNumber != -1) {
- removeUser(userId, /* unknownUser */ true);
+ Slogf.i(TAG, "Removing stale state for reused userId %d (serial %d => %d)", userId,
+ storedSerialNumber, serialNumber);
+ removeUserState(userId);
}
mStorage.setInt(USER_SERIAL_NUMBER_KEY, serialNumber, userId);
}
@@ -772,7 +794,6 @@ public class LockSettingsService extends ILockSettings.Stub {
mHandler.post(new Runnable() {
@Override
public void run() {
- cleanupDataForReusedUserIdIfNecessary(userId);
ensureProfileKeystoreUnlocked(userId);
// Hide notification first, as tie managed profile lock takes time
hideEncryptionNotification(new UserHandle(userId));
@@ -822,11 +843,6 @@ public class LockSettingsService extends ILockSettings.Stub {
} else if (Intent.ACTION_USER_STARTING.equals(intent.getAction())) {
final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
mStorage.prefetchUser(userHandle);
- } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
- final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
- if (userHandle > 0) {
- removeUser(userHandle, /* unknownUser= */ false);
- }
}
}
};
@@ -938,6 +954,53 @@ public class LockSettingsService extends ILockSettings.Stub {
return success;
}
+ private void bootCompleted() {
+ synchronized (mUserCreationAndRemovalLock) {
+ // Handle delayed calls to LSS.removeUser() and LSS.createNewUser().
+ for (int i = 0; i < mEarlyRemovedUsers.size(); i++) {
+ int userId = mEarlyRemovedUsers.keyAt(i);
+ Slogf.i(TAG, "Removing locksettings state for removed user %d now that boot "
+ + "is complete", userId);
+ removeUserState(userId);
+ }
+ mEarlyRemovedUsers = null; // no longer needed
+ for (int i = 0; i < mEarlyCreatedUsers.size(); i++) {
+ int userId = mEarlyCreatedUsers.keyAt(i);
+ int serialNumber = mEarlyCreatedUsers.valueAt(i);
+
+ removeStateForReusedUserIdIfNecessary(userId, serialNumber);
+ synchronized (mSpManager) {
+ if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
+ Slogf.i(TAG, "Creating locksettings state for user %d now that boot "
+ + "is complete", userId);
+ initializeSyntheticPasswordLocked(userId);
+ }
+ }
+ }
+ mEarlyCreatedUsers = null; // no longer needed
+
+ // Also do a one-time migration of all users to SP-based credentials. This is needed
+ // for the system user on the first boot of a device, as the system user is special and
+ // never goes through the user creation flow that other users do. It is also needed for
+ // existing users on a device upgraded from Android 13 or earlier, where users with no
+ // LSKF didn't necessarily have an SP.
+ if (getString("migrated_all_users_to_sp", null, 0) == null) {
+ for (UserInfo user : mUserManager.getAliveUsers()) {
+ removeStateForReusedUserIdIfNecessary(user.id, user.serialNumber);
+ synchronized (mSpManager) {
+ if (!isSyntheticPasswordBasedCredentialLocked(user.id)) {
+ Slogf.i(TAG, "Migrating user %d to SP-based credential", user.id);
+ initializeSyntheticPasswordLocked(user.id);
+ }
+ }
+ }
+ setString("migrated_all_users_to_sp", "true", 0);
+ }
+
+ mBootComplete = true;
+ }
+ }
+
/**
* Returns the lowest password quality that still presents the same UI for entering it.
*
@@ -1587,6 +1650,7 @@ public class LockSettingsService extends ILockSettings.Stub {
if (!savedCredential.isNone()) {
throw new IllegalStateException("Saved credential given, but user has no SP");
}
+ // TODO(b/232452368): this case is only needed by unit tests now; remove it.
initializeSyntheticPasswordLocked(userId);
} else if (savedCredential.isNone() && isProfileWithUnifiedLock(userId)) {
// get credential from keystore when profile has unified lock
@@ -2260,8 +2324,50 @@ public class LockSettingsService extends ILockSettings.Stub {
});
}
- private void removeUser(int userId, boolean unknownUser) {
- Slog.i(TAG, "RemoveUser: " + userId);
+ private void createNewUser(@UserIdInt int userId, int userSerialNumber) {
+ synchronized (mUserCreationAndRemovalLock) {
+ // Before PHASE_BOOT_COMPLETED, don't actually create the synthetic password yet, but
+ // rather automatically delay it to later. We do this because protecting the synthetic
+ // password requires the Weaver HAL if the device supports it, and some devices don't
+ // make Weaver available until fairly late in the boot process. This logic ensures a
+ // consistent flow across all devices, regardless of their Weaver implementation.
+ if (!mBootComplete) {
+ Slogf.i(TAG, "Delaying locksettings state creation for user %d until boot complete",
+ userId);
+ mEarlyCreatedUsers.put(userId, userSerialNumber);
+ mEarlyRemovedUsers.delete(userId);
+ return;
+ }
+ removeStateForReusedUserIdIfNecessary(userId, userSerialNumber);
+ synchronized (mSpManager) {
+ initializeSyntheticPasswordLocked(userId);
+ }
+ }
+ }
+
+ private void removeUser(@UserIdInt int userId) {
+ synchronized (mUserCreationAndRemovalLock) {
+ // Before PHASE_BOOT_COMPLETED, don't actually remove the LSS state yet, but rather
+ // automatically delay it to later. We do this because deleting synthetic password
+ // protectors requires the Weaver HAL if the device supports it, and some devices don't
+ // make Weaver available until fairly late in the boot process. This logic ensures a
+ // consistent flow across all devices, regardless of their Weaver implementation.
+ if (!mBootComplete) {
+ Slogf.i(TAG, "Delaying locksettings state removal for user %d until boot complete",
+ userId);
+ if (mEarlyCreatedUsers.indexOfKey(userId) >= 0) {
+ mEarlyCreatedUsers.delete(userId);
+ } else {
+ mEarlyRemovedUsers.put(userId, -1 /* unused */);
+ }
+ return;
+ }
+ Slogf.i(TAG, "Removing state for user %d", userId);
+ removeUserState(userId);
+ }
+ }
+
+ private void removeUserState(@UserIdInt int userId) {
removeBiometricsForUser(userId);
mSpManager.removeUser(getGateKeeperService(), userId);
mStrongAuth.removeUser(userId);
@@ -2270,11 +2376,9 @@ public class LockSettingsService extends ILockSettings.Stub {
mManagedProfilePasswordCache.removePassword(userId);
gateKeeperClearSecureUserId(userId);
- if (unknownUser || isCredentialSharableWithParent(userId)) {
- removeKeystoreProfileKey(userId);
- }
- // Clean up storage last, this is to ensure that cleanupDataForReusedUserIdIfNecessary()
- // can make the assumption that no USER_SERIAL_NUMBER_KEY means user is fully removed.
+ removeKeystoreProfileKey(userId);
+ // Clean up storage last, so that removeStateForReusedUserIdIfNecessary() can assume that no
+ // USER_SERIAL_NUMBER_KEY means user is fully removed.
mStorage.removeUser(userId);
}
@@ -2529,8 +2633,11 @@ public class LockSettingsService extends ILockSettings.Stub {
}
private void callToAuthSecretIfNeeded(@UserIdInt int userId, SyntheticPassword sp) {
- // Pass the primary user's auth secret to the HAL
- if (mAuthSecretService != null && mUserManager.getUserInfo(userId).isPrimary()) {
+ // If the given user is the primary user, pass the auth secret to the HAL. Only the system
+ // user can be primary. Check for the system user ID before calling getUserInfo(), as other
+ // users may still be under construction.
+ if (mAuthSecretService != null && userId == UserHandle.USER_SYSTEM &&
+ mUserManager.getUserInfo(userId).isPrimary()) {
try {
final byte[] rawSecret = sp.deriveVendorAuthSecret();
final ArrayList
+ * This is called just once in the lifetime of the user: at user creation time (possibly delayed
+ * until {@code PHASE_BOOT_COMPLETED} to ensure that the Weaver HAL is available if the device
+ * supports it), or when upgrading from Android 13 or earlier where users with no LSKF didn't
+ * necessarily have an SP.
*/
@GuardedBy("mSpManager")
@VisibleForTesting
@@ -2835,6 +2945,7 @@ public class LockSettingsService extends ILockSettings.Stub {
if (!isUserSecure(userId)) {
long protectorId = getCurrentLskfBasedProtectorId(userId);
if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
+ // TODO(b/232452368): this case is only needed by unit tests now; remove it.
sp = initializeSyntheticPasswordLocked(userId);
} else {
sp = mSpManager.unlockLskfBasedProtector(getGateKeeperService(), protectorId,
@@ -3072,6 +3183,9 @@ public class LockSettingsService extends ILockSettings.Stub {
pw.decreaseIndent();
pw.println("PasswordHandleCount: " + mGatekeeperPasswords.size());
+ synchronized (mUserCreationAndRemovalLock) {
+ pw.println("BootComplete: " + mBootComplete);
+ }
}
private void dumpKeystoreKeys(IndentingPrintWriter pw) {
@@ -3232,6 +3346,16 @@ public class LockSettingsService extends ILockSettings.Stub {
LockSettingsService.this.unlockUserKeyIfUnsecured(userId);
}
+ @Override
+ public void createNewUser(@UserIdInt int userId, int userSerialNumber) {
+ LockSettingsService.this.createNewUser(userId, userSerialNumber);
+ }
+
+ @Override
+ public void removeUser(@UserIdInt int userId) {
+ LockSettingsService.this.removeUser(userId);
+ }
+
@Override
public long addEscrowToken(byte[] token, int userId,
EscrowTokenStateChangeCallback callback) {
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index ed1c86769e31f..6ab067b7de724 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -49,6 +49,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+import com.android.server.utils.Slogf;
import libcore.util.HexEncoding;
@@ -1026,6 +1027,14 @@ public class SyntheticPasswordManager {
long protectorId, @NonNull LockscreenCredential credential, int userId,
ICheckCredentialProgressCallback progressCallback) {
AuthenticationResult result = new AuthenticationResult();
+
+ if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
+ // This should never happen, due to the migration done in LSS.bootCompleted().
+ Slogf.wtf(TAG, "Synthetic password not found for user %d", userId);
+ result.gkResponse = VerifyCredentialResponse.ERROR;
+ return result;
+ }
+
PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, protectorId,
userId));
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c39cbae0ebf24..0a89d131eda2a 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -88,8 +88,6 @@ import android.os.UserManager.QuietModeFlag;
import android.os.storage.StorageManager;
import android.os.storage.StorageManagerInternal;
import android.provider.Settings;
-import android.security.GateKeeper;
-import android.service.gatekeeper.IGateKeeperService;
import android.service.voice.VoiceInteractionManagerInternal;
import android.stats.devicepolicy.DevicePolicyEnums;
import android.text.TextUtils;
@@ -4664,6 +4662,10 @@ public class UserManagerService extends IUserManager.Stub {
StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
t.traceEnd();
+ t.traceBegin("LSS.createNewUser");
+ mLockPatternUtils.createNewUser(userId, userInfo.serialNumber);
+ t.traceEnd();
+
final Set