1
0
mirror of https://github.com/samba-team/samba.git synced 2025-02-14 01:57:53 +03:00

CVE-2021-20251 s3: ensure bad password count atomic updates

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.

Discovered by Nathaniel W. Turner.

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

Signed-off-by: Jeremy Allison <jra@samba.org>
Reviewed-by: Andreas Schneider <asn@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
(cherry picked from commit 8587734bf989aeaafa9d09d78d0f381caf52d285)
This commit is contained in:
Jeremy Allison 2021-01-11 12:11:35 -08:00 committed by Jule Anger
parent 13efa62618
commit 5c8bbe3e74

View File

@ -379,6 +379,8 @@ NTSTATUS check_sam_security(const DATA_BLOB *challenge,
const uint8_t *nt_pw;
const uint8_t *lm_pw;
uint32_t acct_ctrl;
char *mutex_name_by_user = NULL;
struct named_mutex *mtx = NULL;
/* the returned struct gets kept on the server_info, by means
of a steal further down */
@ -418,6 +420,79 @@ NTSTATUS check_sam_security(const DATA_BLOB *challenge,
challenge, lm_pw, nt_pw,
user_info, &user_sess_key, &lm_sess_key);
/*
* 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);
acct_ctrl = 0;
username = NULL;
nt_pw = NULL;
lm_pw = NULL;
sampass = samu_new(mem_ctx);
if (sampass == NULL) {
return NT_STATUS_NO_MEMORY;
}
mutex_name_by_user = talloc_asprintf(mem_ctx,
"check_sam_security_mutex_%s",
user_info->mapped.account_name);
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(mem_ctx, mutex_name_by_user, 30);
if (mtx != NULL) {
/* Re-load the account information if we got the mutex. */
ret = pdb_getsampwnam(sampass, user_info->mapped.account_name);
}
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_info->mapped.account_name);
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_info->mapped.account_name);
nt_status = NT_STATUS_NO_SUCH_USER;
goto done;
}
/* Re-load the account control info. */
acct_ctrl = pdb_get_acct_ctrl(sampass);
username = pdb_get_username(sampass);
/*
* 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 (acct_ctrl & ACB_AUTOLOCK) {
DBG_NOTICE("Account for user %s was locked out.\n", username);
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 */
@ -510,6 +585,8 @@ done:
TALLOC_FREE(sampass);
data_blob_free(&user_sess_key);
data_blob_free(&lm_sess_key);
TALLOC_FREE(mutex_name_by_user);
TALLOC_FREE(mtx);
return nt_status;
}