1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-22 13:34:15 +03:00

CVE-2021-20251 s3: Ensure bad password count atomic updates for SAMR password change

The bad password count is supposed to limit the number of failed login
attempt a user can make before being temporarily locked out, but race
conditions between processes have allowed determined attackers to make
many more than the specified number of attempts.  This is especially
bad on constrained or overcommitted hardware.

To fix this, once a bad password is detected, we reload the sam account
information under a user-specific mutex, ensuring we have an up to
date bad password count.

Derived from a similar patch to source3/auth/check_samsec.c by
Jeremy Allison <jra@samba.org>

BUG: https://bugzilla.samba.org/show_bug.cgi?id=14611

Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Andreas Schneider <asn@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
This commit is contained in:
Joseph Sutton 2022-07-05 20:17:33 +12:00 committed by Andrew Bartlett
parent 8587734bf9
commit 65c473d4a5

View File

@ -1205,6 +1205,8 @@ NTSTATUS pass_oem_change(char *user, const char *rhost,
bool ret = false;
bool updated_badpw = false;
NTSTATUS update_login_attempts_status;
char *mutex_name_by_user = NULL;
struct named_mutex *mtx = NULL;
if (!(sampass = samu_new(NULL))) {
return NT_STATUS_NO_MEMORY;
@ -1216,15 +1218,15 @@ NTSTATUS pass_oem_change(char *user, const char *rhost,
if (ret == false) {
DEBUG(0,("pass_oem_change: getsmbpwnam returned NULL\n"));
TALLOC_FREE(sampass);
return NT_STATUS_NO_SUCH_USER;
nt_status = NT_STATUS_NO_SUCH_USER;
goto done;
}
/* Quit if the account was locked out. */
if (pdb_get_acct_ctrl(sampass) & ACB_AUTOLOCK) {
DEBUG(3,("check_sam_security: Account for user %s was locked out.\n", user));
TALLOC_FREE(sampass);
return NT_STATUS_ACCOUNT_LOCKED_OUT;
nt_status = NT_STATUS_ACCOUNT_LOCKED_OUT;
goto done;
}
nt_status = check_oem_password(user,
@ -1235,6 +1237,71 @@ NTSTATUS pass_oem_change(char *user, const char *rhost,
sampass,
&new_passwd);
/*
* We must re-load the sam acount information under a mutex
* lock to ensure we don't miss any concurrent account lockout
* changes.
*/
/* Clear out old sampass info. */
TALLOC_FREE(sampass);
sampass = samu_new(NULL);
if (sampass == NULL) {
return NT_STATUS_NO_MEMORY;
}
mutex_name_by_user = talloc_asprintf(NULL,
"check_sam_security_mutex_%s",
user);
if (mutex_name_by_user == NULL) {
nt_status = NT_STATUS_NO_MEMORY;
goto done;
}
/* Grab the named mutex under root with 30 second timeout. */
become_root();
mtx = grab_named_mutex(NULL, mutex_name_by_user, 30);
if (mtx != NULL) {
/* Re-load the account information if we got the mutex. */
ret = pdb_getsampwnam(sampass, user);
}
unbecome_root();
/* Everything from here on until mtx is freed is done under the mutex.*/
if (mtx == NULL) {
DBG_ERR("Acquisition of mutex %s failed "
"for user %s\n",
mutex_name_by_user,
user);
nt_status = NT_STATUS_INTERNAL_ERROR;
goto done;
}
if (!ret) {
/*
* Re-load of account failed. This could only happen if the
* user was deleted in the meantime.
*/
DBG_NOTICE("reload of user '%s' in passdb failed.\n",
user);
nt_status = NT_STATUS_NO_SUCH_USER;
goto done;
}
/*
* Check if the account is now locked out - now under the mutex.
* This can happen if the server is under
* a password guess attack and the ACB_AUTOLOCK is set by
* another process.
*/
if (pdb_get_acct_ctrl(sampass) & ACB_AUTOLOCK) {
DBG_NOTICE("Account for user %s was locked out.\n", user);
nt_status = NT_STATUS_ACCOUNT_LOCKED_OUT;
goto done;
}
/*
* Notify passdb backend of login success/failure. If not
* NT_STATUS_OK the backend doesn't like the login
@ -1282,8 +1349,7 @@ NTSTATUS pass_oem_change(char *user, const char *rhost,
}
if (!NT_STATUS_IS_OK(nt_status)) {
TALLOC_FREE(sampass);
return nt_status;
goto done;
}
/* We've already checked the old password here.... */
@ -1294,7 +1360,10 @@ NTSTATUS pass_oem_change(char *user, const char *rhost,
memset(new_passwd, 0, strlen(new_passwd));
done:
TALLOC_FREE(sampass);
TALLOC_FREE(mutex_name_by_user);
TALLOC_FREE(mtx);
return nt_status;
}