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 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> (cherry picked from commit 65c473d4a53fc8a22a0d531aff45203ea3a4d99b)
This commit is contained in:
parent
5c8bbe3e74
commit
3e54aabd9e
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user