From 363010734da15a049e31001d29cdb6f579edc282 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Mon, 11 Jan 2021 12:11:35 -0800 Subject: [PATCH 1/5] 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, after every password is checked (regardless of if 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. This also ensures we have a race-free lockout detection. Discovered by Nathaniel W. Turner. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14611 Signed-off-by: Jeremy Allison Reviewed-by: Andrew Bartlett --- source3/auth/check_samsec.c | 77 +++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/source3/auth/check_samsec.c b/source3/auth/check_samsec.c index 53b6da53dc1..4870fb00de2 100644 --- a/source3/auth/check_samsec.c +++ b/source3/auth/check_samsec.c @@ -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; } -- 2.25.1 From 77325842bda716a42a01f86eb90cebf7456950f7 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Mon, 1 Feb 2021 15:39:00 +1300 Subject: [PATCH 2/5] CVE-2021-20251: s4 rpc samr: Read the objectGUID Ensure that the objectGUID is read, it will be used to re-read the user details inside a transaction by authsam_update_bad_pwd_count and authsam_logon_success_accounting BUG: https://bugzilla.samba.org/show_bug.cgi?id=14611 Signed-off-by: Gary Lockyer Reviewed-by: Andrew Bartlett --- source4/rpc_server/samr/samr_password.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source4/rpc_server/samr/samr_password.c b/source4/rpc_server/samr/samr_password.c index 52a644176e2..f2e957fa628 100644 --- a/source4/rpc_server/samr/samr_password.c +++ b/source4/rpc_server/samr/samr_password.c @@ -342,7 +342,9 @@ NTSTATUS dcesrv_samr_ChangePasswordUser3(struct dcesrv_call_state *dce_call, "msDS-ResultantPSO", "msDS-User-Account-Control-Computed", "badPwdCount", "badPasswordTime", - "objectSid", NULL }; + "objectSid", + "objectGUID", + NULL }; struct samr_Password *nt_pwd, *lm_pwd; struct samr_DomInfo1 *dominfo = NULL; struct userPwdChangeFailureInformation *reject = NULL; -- 2.25.1 From f902ca49dbd15fb366c4453cecb338cf74e2b5c2 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Wed, 27 Jan 2021 14:24:58 +1300 Subject: [PATCH 3/5] CVE-2021-20251: s4 auth: make bad password count increment atomic Ensure that the bad password count is incremented atomically. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14611 Signed-off-by: Gary Lockyer Reviewed-by: Andrew Bartlett --- source4/auth/sam.c | 224 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 202 insertions(+), 22 deletions(-) diff --git a/source4/auth/sam.c b/source4/auth/sam.c index 39e48c26b52..effcda79b21 100644 --- a/source4/auth/sam.c +++ b/source4/auth/sam.c @@ -813,6 +813,82 @@ static int authsam_get_user_pso(struct ldb_context *sam_ctx, return LDB_SUCCESS; } +/* + * Re-read the bad password and successful logon data for a user. + * + * the passed user record, must contain the "objectGUID", as this is + * used as the re-read key + */ +static NTSTATUS authsam_reread_user_logon_data( + struct ldb_context *sam_ctx, + TALLOC_CTX *mem_ctx, + const struct ldb_message *user_msg, + struct ldb_message **current, + struct GUID *guid) +{ + const struct ldb_val *v = NULL; + NTSTATUS status = NT_STATUS_OK; + struct ldb_result *res = NULL; + uint16_t acct_flags = 0; + const char *attr_name = "msDS-User-Account-Control-Computed"; + + int ret; + + /* + * Get the GUID of the user account + */ + v = ldb_msg_find_ldb_val(user_msg, "objectGUID"); + if (v == NULL) { + DBG_ERR("Unable to get objectGUID for %s\n", + ldb_dn_get_linearized(user_msg->dn)); + return NT_STATUS_INTERNAL_ERROR; + } + + status = GUID_from_ndr_blob(v, guid); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Unable to extract objectGUID for %s\n", + ldb_dn_get_linearized(user_msg->dn)); + return NT_STATUS_INTERNAL_ERROR; + } + + /* + * Re-read the account details, using the GUID in case the DN + * is being changed. + * + * We re read all the attributes in user_attrs, rather than using a + * subset to ensure that we can reuse existing validation code. + */ + ret = dsdb_search_by_dn_guid( + sam_ctx, mem_ctx, &res, guid, user_attrs, 0); + if (ret != LDB_SUCCESS) { + DBG_ERR("Unable to re-read account control data for %s\n", + ldb_dn_get_linearized(user_msg->dn)); + return NT_STATUS_INTERNAL_ERROR; + } + + /* + * Ensure the account has not been locked out by another request + */ + v = ldb_msg_find_ldb_val(res->msgs[0], attr_name); + if (v == NULL || v->data == NULL) { + DBG_ERR("No %s attribute for %s\n", + attr_name, + ldb_dn_get_linearized(user_msg->dn)); + TALLOC_FREE(res); + return NT_STATUS_INTERNAL_ERROR; + } + acct_flags = samdb_result_acct_flags(res->msgs[0], attr_name); + if (acct_flags & ACB_AUTOLOCK) { + DBG_WARNING( + "Account for user %s was locked out.\n", + ldb_dn_get_linearized(user_msg->dn)); + TALLOC_FREE(res); + return NT_STATUS_ACCOUNT_LOCKED_OUT; + } + *current = res->msgs[0]; + return NT_STATUS_OK; +} + NTSTATUS authsam_update_bad_pwd_count(struct ldb_context *sam_ctx, struct ldb_message *msg, struct ldb_dn *domain_dn) @@ -823,10 +899,13 @@ NTSTATUS authsam_update_bad_pwd_count(struct ldb_context *sam_ctx, "pwdProperties", NULL }; int ret; - NTSTATUS status; - struct ldb_result *domain_res; + NTSTATUS status = NT_STATUS_OK; + struct ldb_result *domain_res = NULL; struct ldb_message *msg_mod = NULL; + struct ldb_message *current = NULL; struct ldb_message *pso_msg = NULL; + bool txn_active = false; + struct GUID guid; TALLOC_CTX *mem_ctx; mem_ctx = talloc_new(msg); @@ -847,20 +926,77 @@ NTSTATUS authsam_update_bad_pwd_count(struct ldb_context *sam_ctx, * fallback to using the domain defaults so that we still * record the bad password attempt */ - DBG_ERR("Error (%d) checking PSO for %s", + DBG_ERR("Error (%d) checking PSO for %s\n", ret, ldb_dn_get_linearized(msg->dn)); } - status = dsdb_update_bad_pwd_count(mem_ctx, sam_ctx, - msg, domain_res->msgs[0], pso_msg, - &msg_mod); + /* + * To ensure that the bad password count is updated atomically, + * we need to: + * begin a transaction + * re-read the account details, using the objectGUID + * update the bad password count + * commit the transaction. + */ + + /* + * Start a new transaction + */ + ret = ldb_transaction_start(sam_ctx); + if (ret != LDB_SUCCESS) { + status = NT_STATUS_INTERNAL_ERROR; + goto error; + } + txn_active = true; + + /* + * Re-read the account details, using the GUID in case the DN + * is being changed. + */ + status = authsam_reread_user_logon_data( + sam_ctx, mem_ctx, msg, ¤t, &guid); if (!NT_STATUS_IS_OK(status)) { - TALLOC_FREE(mem_ctx); - return status; + /* The re-read can return account locked out, as well + * as an internal error + */ + goto error; } + /* + * Update the bad password count and if required lock the account + */ + status = dsdb_update_bad_pwd_count( + mem_ctx, + sam_ctx, + current, + domain_res->msgs[0], + pso_msg, + &msg_mod); + if (!NT_STATUS_IS_OK(status)) { + status = NT_STATUS_INTERNAL_ERROR; + goto error; + } + + /* + * Write the data back to disk if required. + */ if (msg_mod != NULL) { struct ldb_request *req; + struct ldb_dn *dn; + + /* + * Use the GUID, in case the DN has been changed + */ + dn = ldb_dn_new_fmt( + mem_ctx, + sam_ctx, + "", + GUID_string(mem_ctx, &guid)); + if (dn == NULL) { + status = NT_STATUS_INTERNAL_ERROR; + goto error; + } + msg_mod->dn = ldb_dn_copy(msg_mod, dn); ret = ldb_build_mod_req(&req, sam_ctx, sam_ctx, msg_mod, @@ -869,35 +1005,79 @@ NTSTATUS authsam_update_bad_pwd_count(struct ldb_context *sam_ctx, ldb_op_default_callback, NULL); if (ret != LDB_SUCCESS) { - goto done; + TALLOC_FREE(msg_mod); + status = NT_STATUS_INTERNAL_ERROR; + goto error; } - ret = ldb_request_add_control(req, - DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE, - false, NULL); + ret = ldb_request_add_control( + req, + DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE, + false, + NULL); if (ret != LDB_SUCCESS) { talloc_free(req); - goto done; + status = NT_STATUS_INTERNAL_ERROR; + goto error; } - ret = dsdb_autotransaction_request(sam_ctx, req); + /* + * As we're in a transaction, make the ldb request directly + * to avoid the nested transaction that would result if we + * called dsdb_autotransaction_request + */ + ret = ldb_request(sam_ctx, req); + if (ret == LDB_SUCCESS) { + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + } talloc_free(req); + if (ret != LDB_SUCCESS) { + status = NT_STATUS_INTERNAL_ERROR; + goto error; + } } -done: + /* + * Note that we may not have updated the user record, but + * committing the transaction in that case is still the correct + * thing to do. + * If the transaction was cancelled, this would be logged by + * the dsdb audit log as a failure. When in fact it is expected + * behaviour. + */ + TALLOC_FREE(mem_ctx); + ret = ldb_transaction_commit(sam_ctx); if (ret != LDB_SUCCESS) { - DBG_ERR("Failed to update badPwdCount, badPasswordTime or " - "set lockoutTime on %s: %s\n", - ldb_dn_get_linearized(msg->dn), - ldb_errstring(sam_ctx)); - TALLOC_FREE(mem_ctx); + DBG_ERR("Error (%d) %s, committing transaction," + " while updating bad password count" + " for (%s)\n", + ret, + ldb_errstring(sam_ctx), + ldb_dn_get_linearized(msg->dn)); return NT_STATUS_INTERNAL_ERROR; } + return NT_STATUS_OK; +error: + DBG_ERR("Failed to update badPwdCount, badPasswordTime or " + "set lockoutTime on %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(sam_ctx) != NULL ? + ldb_errstring(sam_ctx) :nt_errstr(status)); + if (txn_active) { + ret = ldb_transaction_cancel(sam_ctx); + if (ret != LDB_SUCCESS) { + DBG_ERR("Error rolling back transaction," + " while updating bad password count" + " on %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(sam_ctx)); + } + } TALLOC_FREE(mem_ctx); - return NT_STATUS_OK; -} + return status; +} static NTSTATUS authsam_update_lastlogon_timestamp(struct ldb_context *sam_ctx, struct ldb_message *msg_mod, -- 2.25.1 From c097c686571661ab3a1b273cfa6b6639e4f50d41 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Tue, 2 Feb 2021 12:06:16 +1300 Subject: [PATCH 4/5] CVE-2021-20251: s4 auth: make logon success accounting update atomic Ensure that the successful logon accounting data is updated atomically. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14611 Signed-off-by: Gary Lockyer Reviewed-by: Andrew Bartlett --- source4/auth/sam.c | 194 +++++++++++++++++++++++++++++++++------------ 1 file changed, 142 insertions(+), 52 deletions(-) diff --git a/source4/auth/sam.c b/source4/auth/sam.c index effcda79b21..804290abed8 100644 --- a/source4/auth/sam.c +++ b/source4/auth/sam.c @@ -1215,7 +1215,7 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, struct netr_SendToSamBase **send_to_sam) { int ret; - NTSTATUS status; + NTSTATUS status = NT_STATUS_OK; int badPwdCount; int dbBadPwdCount; int64_t lockoutTime; @@ -1225,30 +1225,66 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, NTTIME now; NTTIME lastLogonTimestamp; bool am_rodc = false; + bool txn_active = false; + struct ldb_message *current = NULL; + struct GUID guid; mem_ctx = talloc_new(msg); if (mem_ctx == NULL) { return NT_STATUS_NO_MEMORY; } - lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0); - dbBadPwdCount = ldb_msg_find_attr_as_int(msg, "badPwdCount", 0); + /* + * Any update of the last logon data, needs to be done inside a + * transaction. + * And the user data needs to be re-read, and the account re-checked + * for lockout + */ + + /* + * Start a new transaction + */ + ret = ldb_transaction_start(sam_ctx); + if (ret != LDB_SUCCESS) { + status = NT_STATUS_INTERNAL_ERROR; + goto error; + } + txn_active = true; + + /* + * Re-read the account details, using the GUID in case the DN + * is being changed. + */ + status = authsam_reread_user_logon_data( + sam_ctx, mem_ctx, msg, ¤t, &guid); + if (!NT_STATUS_IS_OK(status)) { + /* The re-read can return account locked out, as well + * as an internal error + */ + goto error; + } + + lockoutTime = ldb_msg_find_attr_as_int64( + current, "lockoutTime", 0); + dbBadPwdCount = ldb_msg_find_attr_as_int( + current, "badPwdCount", 0); if (interactive_or_kerberos) { badPwdCount = dbBadPwdCount; } else { - badPwdCount = samdb_result_effective_badPwdCount(sam_ctx, mem_ctx, - domain_dn, msg); + badPwdCount = samdb_result_effective_badPwdCount( + sam_ctx, mem_ctx, domain_dn, current); } lastLogonTimestamp = - ldb_msg_find_attr_as_int64(msg, "lastLogonTimestamp", 0); + ldb_msg_find_attr_as_int64( + current, "lastLogonTimestamp", 0); - DEBUG(5, ("lastLogonTimestamp is %lld\n", - (long long int)lastLogonTimestamp)); + DBG_INFO("lastLogonTimestamp is %lld\n", + (long long int)lastLogonTimestamp); msg_mod = ldb_msg_new(mem_ctx); if (msg_mod == NULL) { - TALLOC_FREE(mem_ctx); - return NT_STATUS_NO_MEMORY; + status = NT_STATUS_NO_MEMORY; + goto error; } msg_mod->dn = msg->dn; @@ -1256,16 +1292,18 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, /* * This implies "badPwdCount" = 0, see samldb_lockout_time() */ - ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod, "lockoutTime", 0); + ret = samdb_msg_add_int( + sam_ctx, msg_mod, msg_mod, "lockoutTime", 0); if (ret != LDB_SUCCESS) { - TALLOC_FREE(mem_ctx); - return NT_STATUS_NO_MEMORY; + status = NT_STATUS_NO_MEMORY; + goto error; } } else if (badPwdCount != 0) { - ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod, "badPwdCount", 0); + ret = samdb_msg_add_int( + sam_ctx, msg_mod, msg_mod, "badPwdCount", 0); if (ret != LDB_SUCCESS) { - TALLOC_FREE(mem_ctx); - return NT_STATUS_NO_MEMORY; + status = NT_STATUS_NO_MEMORY; + goto error; } } @@ -1274,11 +1312,11 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, if (interactive_or_kerberos || (badPwdCount != 0 && lockoutTime == 0)) { - ret = samdb_msg_add_int64(sam_ctx, msg_mod, msg_mod, - "lastLogon", now); + ret = samdb_msg_add_int64( + sam_ctx, msg_mod, msg_mod, "lastLogon", now); if (ret != LDB_SUCCESS) { - TALLOC_FREE(mem_ctx); - return NT_STATUS_NO_MEMORY; + status = NT_STATUS_NO_MEMORY; + goto error; } } @@ -1289,42 +1327,42 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, logonCount += 1; - ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod, - "logonCount", logonCount); + ret = samdb_msg_add_int( + sam_ctx, msg_mod, msg_mod, "logonCount", logonCount); if (ret != LDB_SUCCESS) { - TALLOC_FREE(mem_ctx); - return NT_STATUS_NO_MEMORY; + status = NT_STATUS_NO_MEMORY; + goto error; } } else { /* Set an unset logonCount to 0 on first successful login */ if (ldb_msg_find_ldb_val(msg, "logonCount") == NULL) { - ret = samdb_msg_add_int(sam_ctx, msg_mod, msg_mod, - "logonCount", 0); + ret = samdb_msg_add_int( + sam_ctx, msg_mod, msg_mod, "logonCount", 0); if (ret != LDB_SUCCESS) { - TALLOC_FREE(mem_ctx); - return NT_STATUS_NO_MEMORY; + status = NT_STATUS_NO_MEMORY; + goto error; } } } ret = samdb_rodc(sam_ctx, &am_rodc); if (ret != LDB_SUCCESS) { - TALLOC_FREE(mem_ctx); - return NT_STATUS_INTERNAL_ERROR; + status = NT_STATUS_INTERNAL_ERROR; + goto error; } if (!am_rodc) { - status = authsam_update_lastlogon_timestamp(sam_ctx, msg_mod, domain_dn, - lastLogonTimestamp, now); + status = authsam_update_lastlogon_timestamp( + sam_ctx, msg_mod, domain_dn, lastLogonTimestamp, now); if (!NT_STATUS_IS_OK(status)) { - TALLOC_FREE(mem_ctx); - return NT_STATUS_NO_MEMORY; + status = NT_STATUS_NO_MEMORY; + goto error; } } else { /* Perform the (async) SendToSAM calls for MS-SAMS */ if (dbBadPwdCount != 0 && send_to_sam != NULL) { struct netr_SendToSamBase *base_msg; - struct GUID guid = samdb_result_guid(msg, "objectGUID"); + guid = samdb_result_guid(msg, "objectGUID"); base_msg = talloc_zero(msg, struct netr_SendToSamBase); base_msg->message_type = SendToSamResetBadPasswordCount; @@ -1337,6 +1375,21 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, if (msg_mod->num_elements > 0) { unsigned int i; struct ldb_request *req; + struct ldb_dn *dn; + + /* + * Use the GUID, in case the DN has been changed + */ + dn = ldb_dn_new_fmt( + mem_ctx, + sam_ctx, + "", + GUID_string(mem_ctx, &guid)); + if (dn == NULL) { + status = NT_STATUS_INTERNAL_ERROR; + goto error; + } + msg_mod->dn = ldb_dn_copy(msg_mod, dn); /* mark all the message elements as LDB_FLAG_MOD_REPLACE */ for (i=0;inum_elements;i++) { @@ -1350,32 +1403,69 @@ NTSTATUS authsam_logon_success_accounting(struct ldb_context *sam_ctx, ldb_op_default_callback, NULL); if (ret != LDB_SUCCESS) { - goto done; + status = NT_STATUS_INTERNAL_ERROR; + goto error; } - ret = ldb_request_add_control(req, - DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE, - false, NULL); + ret = ldb_request_add_control( + req, DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE, false, NULL); if (ret != LDB_SUCCESS) { - talloc_free(req); - goto done; + TALLOC_FREE(req); + status = NT_STATUS_INTERNAL_ERROR; + goto error; + } + /* + * As we're in a transaction, make the ldb request directly + * to avoid the nested transaction that would result if we + * called dsdb_autotransaction_request */ + ret = ldb_request(sam_ctx, req); + if (ret == LDB_SUCCESS) { + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + } + TALLOC_FREE(req); + if (ret != LDB_SUCCESS) { + status = NT_STATUS_INTERNAL_ERROR; + goto error; } - - ret = dsdb_autotransaction_request(sam_ctx, req); - talloc_free(req); } -done: + /* + * Note that we may not have updated the user record, but + * committing the transaction in that case is still the correct + * thing to do. + * If the transaction was cancelled, this would be logged by + * the dsdb audit log as a failure. When in fact it is expected + * behaviour. + */ + TALLOC_FREE(mem_ctx); + ret = ldb_transaction_commit(sam_ctx); if (ret != LDB_SUCCESS) { - DEBUG(0, ("Failed to set badPwdCount and lockoutTime " - "to 0 and/or lastlogon to now (%lld) " - "%s: %s\n", (long long int)now, - ldb_dn_get_linearized(msg_mod->dn), - ldb_errstring(sam_ctx))); - TALLOC_FREE(mem_ctx); + DBG_ERR("Error (%d) %s, committing transaction," + " while updating successful logon accounting" + " for (%s)\n", + ret, + ldb_errstring(sam_ctx), + ldb_dn_get_linearized(msg->dn)); return NT_STATUS_INTERNAL_ERROR; } + return NT_STATUS_OK; +error: + DBG_ERR("Failed to update badPwdCount, badPasswordTime or " + "set lockoutTime on %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(sam_ctx) != NULL ? + ldb_errstring(sam_ctx) :nt_errstr(status)); + if (txn_active) { + ret = ldb_transaction_cancel(sam_ctx); + if (ret != LDB_SUCCESS) { + DBG_ERR("Error rolling back transaction," + " while updating bad password count" + " on %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(sam_ctx)); + } + } TALLOC_FREE(mem_ctx); - return NT_STATUS_OK; + return status; } -- 2.25.1 From 2f4f44504a26fa7a27343c351827607f4c3c33e4 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Tue, 9 Feb 2021 11:59:05 +1300 Subject: [PATCH 5/5] CVE-2021-20251: s4 auth test: Unit tests for source4/auth/sam.c cmocka unit tests for the authsam_reread_user_logon_data in source4/auth/sam.c The tests exercise the error paths to ensure that the error handling functions correctly. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14611 (backported from commit to master due to conflicts in tests.py) Signed-off-by: Gary Lockyer Reviewed-by: Andrew Bartlett --- selftest/tests.py | 2 + source4/auth/tests/sam.c | 1946 ++++++++++++++++++++++++++++++++++++ source4/auth/wscript_build | 7 + 3 files changed, 1955 insertions(+) create mode 100644 source4/auth/tests/sam.c diff --git a/selftest/tests.py b/selftest/tests.py index 554b19f4243..6bc6c75aaa9 100644 --- a/selftest/tests.py +++ b/selftest/tests.py @@ -407,3 +407,5 @@ plantestsuite("samba.unittests.test_oLschema2ldif", "none", [os.path.join(bindir(), "default/source4/utils/oLschema2ldif/test_oLschema2ldif")]) plantestsuite("samba.unittests.mdsparser_es", "none", [os.path.join(bindir(), "default/source3/test_mdsparser_es")] + [configuration]) +plantestsuite("samba.unittests.auth.sam", "none", + [os.path.join(bindir(), "test_auth_sam")]) diff --git a/source4/auth/tests/sam.c b/source4/auth/tests/sam.c new file mode 100644 index 00000000000..d9a376d6002 --- /dev/null +++ b/source4/auth/tests/sam.c @@ -0,0 +1,1946 @@ +/* + * Unit tests for source4/auth/sam.c + * + * Copyright (C) Catalyst.NET Ltd 2021 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "includes.h" +#include "auth/sam.c" +#include "ldb.h" +#include "ntstatus.h" + +/***************************************************************************** + * Mock implementations + *****************************************************************************/ +int dsdb_search_by_dn_guid_ret = LDB_SUCCESS; +struct ldb_result *dsdb_search_by_dn_guid_res = NULL; +int dsdb_search_by_dn_guid(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + struct ldb_result **_result, + const struct GUID *guid, + const char * const *attrs, + uint32_t dsdb_flags) +{ + *_result = talloc_steal(mem_ctx, dsdb_search_by_dn_guid_res); + return dsdb_search_by_dn_guid_ret; +} + +int dsdb_search_dn_ret = LDB_SUCCESS; +struct ldb_result *dsdb_search_dn_res = NULL; +int dsdb_search_dn(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + struct ldb_result **_result, + struct ldb_dn *basedn, + const char * const *attrs, + uint32_t dsdb_flags) +{ + *_result = talloc_steal(mem_ctx, dsdb_search_dn_res); + return dsdb_search_dn_ret; +} + +int ldb_transaction_start_ret = LDB_SUCCESS; +bool in_transaction = false; +int ldb_transaction_start(struct ldb_context *ldb) { + if (ldb_transaction_start_ret == LDB_SUCCESS) { + in_transaction = true; + } + return ldb_transaction_start_ret; +} + +int ldb_transaction_cancel_ret = LDB_SUCCESS; +bool transaction_cancelled = false; +int ldb_transaction_cancel(struct ldb_context *ldb) { + assert_true(in_transaction); + if (ldb_transaction_cancel_ret == LDB_SUCCESS) { + in_transaction = false; + transaction_cancelled = true; + } + return ldb_transaction_cancel_ret; +} + +int ldb_transaction_commit_ret = LDB_SUCCESS; +bool transaction_committed = false; +int ldb_transaction_commit(struct ldb_context *ldb) { + assert_true(in_transaction); + if (ldb_transaction_commit_ret == LDB_SUCCESS) { + in_transaction = false; + transaction_committed = true; + } + return ldb_transaction_commit_ret; +} + +NTSTATUS dsdb_update_bad_pwd_count_ret = NT_STATUS_OK; +struct ldb_message *dsdb_update_bad_pwd_count_res = NULL; +NTSTATUS dsdb_update_bad_pwd_count(TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ctx, + struct ldb_message *user_msg, + struct ldb_message *domain_msg, + struct ldb_message *pso_msg, + struct ldb_message **_mod_msg) { + + *_mod_msg = talloc_steal(mem_ctx, dsdb_update_bad_pwd_count_res); + return dsdb_update_bad_pwd_count_ret; +} + +bool ldb_dn_new_fmt_fail = false; +struct ldb_dn *ldb_dn_new_fmt(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const char *new_fmt, ...) +{ + char *strdn; + const char *guid = "975ac5fa-35d9-431d-b86a-845bcd34fff9"; + + if (ldb_dn_new_fmt_fail) { + return NULL; + } + strdn = talloc_asprintf(mem_ctx, "GUID=<%s>", guid); + + if (strdn) { + struct ldb_dn *dn = ldb_dn_new(mem_ctx, ldb, strdn); + talloc_free(strdn); + return dn; + } + + return NULL; +} + +int ldb_build_mod_req_ret = LDB_SUCCESS; +struct ldb_request *ldb_build_mod_req_res = NULL; +int ldb_build_mod_req(struct ldb_request **ret_req, + struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + const struct ldb_message *message, + struct ldb_control **controls, + void *context, + ldb_request_callback_t callback, + struct ldb_request *parent) +{ + *ret_req = talloc_steal(mem_ctx, ldb_build_mod_req_res); + return ldb_build_mod_req_ret; +} + +int ldb_request_add_control_ret = LDB_SUCCESS; +int ldb_request_add_control(struct ldb_request *req, + const char *oid, + bool critical, + void *data) +{ + return ldb_request_add_control_ret; +} + +int ldb_request_ret = LDB_SUCCESS; +int ldb_request(struct ldb_context *ldb, + struct ldb_request *req) +{ + return ldb_request_ret; +} + +int ldb_wait_ret = LDB_SUCCESS; +int ldb_wait(struct ldb_handle *handle, + enum ldb_wait_type type) +{ + return ldb_wait_ret; +} +bool ldb_msg_new_fail = false; +struct ldb_message *ldb_msg_new(TALLOC_CTX *mem_ctx) +{ + if (ldb_msg_new_fail) { + return NULL; + } else { + return talloc_zero(mem_ctx, struct ldb_message); + } +} + +int samdb_rodc_ret = LDB_SUCCESS; +bool samdb_rodc_res = false; + +int samdb_rodc( + struct ldb_context *sam_ctx, + bool *am_rodc) +{ + + *am_rodc = samdb_rodc_res; + return samdb_rodc_ret; +} + +/* + * Set the globals used by the mocked functions to a known and consistent state + * + * return codes set to success + * pointers set to NULL + * + */ +static void init_mock_results(void) +{ + dsdb_search_dn_ret = LDB_SUCCESS; + dsdb_search_dn_res = NULL; + + ldb_transaction_start_ret = LDB_SUCCESS; + in_transaction = false; + + ldb_transaction_cancel_ret = LDB_SUCCESS; + transaction_cancelled = false; + + ldb_transaction_commit_ret = LDB_SUCCESS; + transaction_committed = false; + + dsdb_update_bad_pwd_count_ret = NT_STATUS_OK; + dsdb_update_bad_pwd_count_res = NULL; + + ldb_dn_new_fmt_fail = false; + + ldb_build_mod_req_ret = LDB_SUCCESS; + ldb_build_mod_req_res = NULL; + + ldb_request_add_control_ret = LDB_SUCCESS; + ldb_request_ret = LDB_SUCCESS; + ldb_wait_ret = LDB_SUCCESS; + + dsdb_search_by_dn_guid_ret = LDB_SUCCESS; + dsdb_search_by_dn_guid_res = NULL; + + ldb_msg_new_fail = false; + + samdb_rodc_ret = LDB_SUCCESS; + samdb_rodc_res = false; + +} + +/***************************************************************************** + * Unit test set up and tear down + *****************************************************************************/ +static int setup(void **state) { + init_mock_results(); + return 0; +} + +static int teardown(void **state) { + init_mock_results(); + return 0; +} + +/****************************************************************************** + * Helper functions + ******************************************************************************/ + +/* + * Build the "Original" user details record, i.e. the user being + * authenticated + */ +static struct ldb_message* create_message(TALLOC_CTX *ctx) +{ + + int ret; + struct timeval tv_now = timeval_current(); + NTTIME now = timeval_to_nttime(&tv_now); + + struct ldb_message *msg = ldb_msg_new(ctx); + + assert_non_null(msg); + ret = samdb_msg_add_int(ctx, msg, msg, "badPwdCount", 10); + assert_int_equal(LDB_SUCCESS, ret); + ret = samdb_msg_add_int64(ctx, msg, msg, "badPasswordTime", now); + assert_int_equal(LDB_SUCCESS, ret); + ret = samdb_msg_add_int64(ctx, msg, msg, "lockoutTime", now); + assert_int_equal(LDB_SUCCESS, ret); + return msg; +} + +/* + * Add the objectGUID element to a message + */ +static void add_object_guid(struct ldb_message *msg, + struct GUID *guid) +{ + + NTSTATUS status; + int ret; + DATA_BLOB guid_blob; + struct ldb_val *new_val; + + status = GUID_to_ndr_blob(guid, msg, &guid_blob); + assert_true(NT_STATUS_IS_OK(status)); + new_val = talloc(msg, struct ldb_val); + assert_non_null(new_val); + new_val->data = talloc_steal(new_val, guid_blob.data); + new_val->length = guid_blob.length; + ret = ldb_msg_add_value(msg, "objectGUID", new_val, NULL); + assert_int_equal(LDB_SUCCESS, ret); +} + +/* + * Build an ldb_result, for the re-reading of a user record + * + * if account_control < 0 then the msDS-User-Account-Control-Computed + * element is not included + * otherwise it is set to the value passed in account_control. + * + */ +static struct ldb_result *build_reread_result( + struct ldb_context *ldb, + TALLOC_CTX *ctx, + int account_control) +{ + struct ldb_message *msg = NULL; + int ret; + + struct ldb_result *res = talloc_zero(ctx, struct ldb_result); + + assert_non_null(res); + res->count = 1; + res->msgs = talloc_array(res, struct ldb_message *, 1); + + msg = create_message(res); + if (account_control >= 0) { + ret = samdb_msg_add_int( + ldb, + msg, + msg, + "msDS-User-Account-Control-Computed", + account_control); + assert_int_equal(LDB_SUCCESS, ret); + } + + res->msgs[0] = msg; + return res; +} + +/* + * Build a mock domain pso ldb_result + */ +static struct ldb_result *build_domain_pso_result( + struct ldb_context *ldb, + TALLOC_CTX *ctx) +{ + struct ldb_message *msg = NULL; + struct ldb_result *res = talloc_zero(ctx, struct ldb_result); + + assert_non_null(res); + res->count = 1; + res->msgs = talloc_array(res, struct ldb_message *, 1); + assert_non_null(res->msgs); + msg = talloc_zero(res, struct ldb_message); + assert_non_null(msg); + res->msgs[0] = msg; + return res; +} + +/***************************************************************************** + * authsam_reread_user_logon_data unit tests + *****************************************************************************/ +/* + * Pass an empty user message to authsam_reread_user_logon_data + * + */ +static void test_reread_empty_msg(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_message *cur = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = ldb_msg_new(ldb); + assert_non_null(msg); + + before = talloc_total_size(ctx); + + status = authsam_reread_user_logon_data(ldb, ctx, msg, &cur, &guid); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * Pass a user message to authsam_reread_user_logon_data that does not have + * a objectGUID element + * + */ +static void test_reread_no_objectGUID(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_message *cur = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + + before = talloc_total_size(ctx); + + status = authsam_reread_user_logon_data(ldb, ctx, msg, &cur, &guid); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * Pass a user message to authsam_reread_user_logon_data that has an invalid + * objectGUID element + * + */ +static void test_reread_invalid_objectGUID(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_message *cur = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + int ret; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + ret = ldb_msg_add_string(msg, "objectGUID", "wibble"); + assert_int_equal(LDB_SUCCESS, ret); + + before = talloc_total_size(ctx); + + status = authsam_reread_user_logon_data(ldb, ctx, msg, &cur, &guid); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_reread_user_logon_data unable to re-read the user record. + * + */ +static void test_reread_read_failure(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_message *cur = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_ret = LDB_ERR_NO_SUCH_OBJECT; + + status = authsam_reread_user_logon_data(ldb, ctx, msg, &cur, &guid); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_reread_user_logon_data account control flags missing from + * re-read data + * + */ +static void test_reread_missing_account_control(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_message *cur = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, -1); + dsdb_search_by_dn_guid_ret = LDB_SUCCESS; + + status = authsam_reread_user_logon_data(ldb, ctx, msg, &cur, &guid); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_reread_user_logon_data account locked + * re-read data + * + */ +static void test_reread_account_locked(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_message *cur = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, UF_LOCKOUT); + + status = authsam_reread_user_logon_data(ldb, ctx, msg, &cur, &guid); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_ACCOUNT_LOCKED_OUT)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_reread_user_logon_data account is not locked + * re-read data + * + */ +static void test_reread_account_not_locked(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_message *cur = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + /* + * Function will succeed so the re-read result is the callers + * responsibility + */ + before = talloc_total_size(ctx); + + status = authsam_reread_user_logon_data(ldb, ctx, msg, &cur, &guid); + assert_true(NT_STATUS_IS_OK(status)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + + +/***************************************************************************** + * authsam_update_bad_pwd_count unit tests + *****************************************************************************/ + +/* + * authsam_update_bad_pwd_account + * + * Unable to read the domain_dn record + * + */ +static void test_update_bad_domain_dn_search_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = talloc_zero(ctx, struct ldb_message); + assert_non_null(msg); + + before = talloc_total_size(ctx); + + dsdb_search_dn_ret = LDB_ERR_NO_SUCH_OBJECT; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_DB_CORRUPTION)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_update_bad_pwd_account + * + * start_transaction failure + * + */ +static void test_update_bad_start_txn_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = talloc_zero(ctx, struct ldb_message); + assert_non_null(msg); + + before = talloc_total_size(ctx); + + ldb_transaction_start_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_update_bad_pwd_account + * + * User details re-read failed + * + */ +static void test_update_bad_reread_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = talloc_zero(ctx, struct ldb_message); + assert_non_null(msg); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_ret = LDB_ERR_NO_SUCH_OBJECT; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_update_bad_pwd_account + * + * Transaction cancel failure + */ +static void test_update_bad_txn_cancel_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = talloc_zero(ctx, struct ldb_message); + assert_non_null(msg); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_ret = LDB_ERR_NO_SUCH_OBJECT; + ldb_transaction_cancel_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_false(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_update_bad_pwd_account + * + * dsdb_update_bad_pwd_count failure + * + */ +static void test_update_bad_update_count_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + dsdb_search_dn_res = build_domain_pso_result(ldb, ctx); + dsdb_update_bad_pwd_count_ret = NT_STATUS_INTERNAL_ERROR; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_update_bad_pwd_account + * + * No need to update the bad password stats + * + */ +static void test_update_bad_no_update_required(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + dsdb_search_dn_res = build_domain_pso_result(ldb, ctx); + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_IS_OK(status)); + assert_true(transaction_committed); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_update_bad_pwd_account + * + * Transaction commit failure + * + */ +static void test_update_bad_commit_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + dsdb_search_dn_res = build_domain_pso_result(ldb, ctx); + ldb_transaction_commit_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_false(transaction_committed); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} +/* + * authsam_update_bad_pwd_account + * + * ldb_dn_new_fmt_failed building the GUID when updating + * the password stats + * + */ +static void test_update_bad_ldb_dn_new_fmt_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + dsdb_search_dn_res = build_domain_pso_result(ldb, ctx); + dsdb_update_bad_pwd_count_res = + talloc_zero(ctx, struct ldb_message); + ldb_dn_new_fmt_fail = true; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_update_bad_pwd_account + * + * ldb_build_mod_req failed building the user update details + * + */ +static void test_update_bad_build_mod_request_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + dsdb_search_dn_res = build_domain_pso_result(ldb, ctx); + dsdb_update_bad_pwd_count_res = + talloc_zero(ctx, struct ldb_message); + ldb_build_mod_req_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_update_bad_pwd_account + * + * ldb_request_add_control failed to add DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE + * to the user update record. + * + */ +static void test_update_bad_add_control_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + dsdb_search_dn_res = build_domain_pso_result(ldb, ctx); + dsdb_update_bad_pwd_count_res = + talloc_zero(dsdb_search_dn_res, struct ldb_message); + ldb_build_mod_req_res = + talloc_zero(dsdb_search_dn_res, struct ldb_request); + ldb_request_add_control_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_update_bad_pwd_account + * + * call to ldb_request failed + * + */ +static void test_update_bad_ldb_request_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + dsdb_search_dn_res = build_domain_pso_result(ldb, ctx); + dsdb_update_bad_pwd_count_res = + talloc_zero(dsdb_search_dn_res, struct ldb_message); + ldb_build_mod_req_res = + talloc_zero(dsdb_search_dn_res, struct ldb_request); + ldb_request_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_update_bad_pwd_account + * + * call to ldb_wait failed + * + */ +static void test_update_bad_ldb_wait_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + dsdb_search_dn_res = build_domain_pso_result(ldb, ctx); + dsdb_update_bad_pwd_count_res = + talloc_zero(dsdb_search_dn_res, struct ldb_message); + ldb_build_mod_req_res = + talloc_zero(dsdb_search_dn_res, struct ldb_request); + ldb_wait_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_update_bad_pwd_count(ldb, msg, domain_dn); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/***************************************************************************** + * authsam_logon_success_accounting unit tests + *****************************************************************************/ +/* + * authsam_logon_success_accounting + * + * start_transaction failure + * + */ +static void test_success_accounting_start_txn_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + ldb_transaction_start_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * User details re-read failed + * + */ +static void test_success_accounting_reread_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_ret = LDB_ERR_NO_SUCH_OBJECT; + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * ldb_msg_new failed + * + */ +static void test_success_accounting_ldb_msg_new_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + ldb_msg_new_fail = true; + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * samdb_rodc failed + * + */ +static void test_success_accounting_samdb_rodc_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + samdb_rodc_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * authsam_update_lastlogon_timestamp failed + * + */ +static void test_success_accounting_update_lastlogon_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + dsdb_search_dn_ret = LDB_ERR_OPERATIONS_ERROR; + /* + * Note: authsam_update_lastlogon_timestamp returns NT_STATUS_NO_MEMORY + * in this case. + */ + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * ldb_dn_new_fmt failed + * + */ +static void test_success_accounting_dn_new_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + dsdb_search_dn_res = build_domain_pso_result(ldb, ctx); + ldb_dn_new_fmt_fail = true; + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * ldb_dn_new_fmt failed + * + */ +static void test_success_accounting_build_mod_req_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + dsdb_search_dn_res = build_domain_pso_result(ldb, ctx); + ldb_build_mod_req_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * ldb_request_add_control failed + * + */ +static void test_success_accounting_add_control_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + dsdb_search_dn_res = build_domain_pso_result(ldb, ctx); + ldb_build_mod_req_res = + talloc_zero(ldb, struct ldb_request); + ldb_request_add_control_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * ldb_request failed + * + */ +static void test_success_accounting_ldb_request_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + dsdb_search_dn_res = build_domain_pso_result(ldb, ctx); + ldb_build_mod_req_res = + talloc_zero(ldb, struct ldb_request); + ldb_request_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * ldb_wait failed + * + */ +static void test_success_accounting_ldb_wait_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + dsdb_search_dn_res = build_domain_pso_result(ldb, ctx); + ldb_build_mod_req_res = + talloc_zero(ldb, struct ldb_request); + ldb_wait_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_true(transaction_cancelled); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * ldb_transaction_commit failed + * + */ +static void test_success_accounting_commit_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + dsdb_search_dn_res = build_domain_pso_result(ldb, ctx); + ldb_build_mod_req_res = + talloc_zero(ldb, struct ldb_request); + ldb_transaction_commit_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_false(transaction_cancelled); + assert_false(transaction_committed); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +/* + * authsam_logon_success_accounting + * + * ldb_wait failed and then ldb_transaction_cancel failed + * + */ +static void test_success_accounting_rollback_failed(void **state) { + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *domain_dn = NULL; + struct GUID guid; + TALLOC_CTX *ctx = NULL; + size_t before = 0; + size_t after = 0; + NTSTATUS status; + + ctx = talloc_new(NULL); + assert_non_null(ctx); + + ldb = ldb_init(ctx, NULL); + assert_non_null(ldb); + + msg = create_message(ctx); + guid = GUID_random(); + add_object_guid(msg, &guid); + + before = talloc_total_size(ctx); + + dsdb_search_by_dn_guid_res = build_reread_result(ldb, ctx, 0); + dsdb_search_dn_res = build_domain_pso_result(ldb, ctx); + ldb_build_mod_req_res = + talloc_zero(ldb, struct ldb_request); + ldb_wait_ret = LDB_ERR_OPERATIONS_ERROR; + ldb_transaction_cancel_ret = LDB_ERR_OPERATIONS_ERROR; + + status = authsam_logon_success_accounting( + ldb, msg, domain_dn, true, NULL); + assert_true(NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)); + assert_false(transaction_cancelled); + assert_false(transaction_committed); + + /* + * Check that all allocated memory was freed + */ + after = talloc_total_size(ctx); + assert_int_equal(before, after); + + /* + * Clean up + */ + TALLOC_FREE(ctx); +} + +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + test_reread_empty_msg, setup, teardown), + cmocka_unit_test_setup_teardown( + test_reread_no_objectGUID, setup, teardown), + cmocka_unit_test_setup_teardown( + test_reread_invalid_objectGUID, setup, teardown), + cmocka_unit_test_setup_teardown( + test_reread_read_failure, setup, teardown), + cmocka_unit_test_setup_teardown( + test_reread_missing_account_control, setup, teardown), + cmocka_unit_test_setup_teardown( + test_reread_account_locked, setup, teardown), + cmocka_unit_test_setup_teardown( + test_reread_account_not_locked, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_domain_dn_search_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_start_txn_failed, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_reread_failed, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_update_count_failed, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_no_update_required, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_ldb_dn_new_fmt_failed, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_build_mod_request_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_add_control_failed, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_ldb_request_failed, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_ldb_wait_failed, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_txn_cancel_failed, setup, teardown), + cmocka_unit_test_setup_teardown( + test_update_bad_commit_failed, setup, teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_start_txn_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_reread_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_ldb_msg_new_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_samdb_rodc_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_update_lastlogon_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_dn_new_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_build_mod_req_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_add_control_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_ldb_request_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_ldb_wait_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_commit_failed, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_success_accounting_rollback_failed, + setup, + teardown), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/source4/auth/wscript_build b/source4/auth/wscript_build index 381a7b19bf0..9688fb1c010 100644 --- a/source4/auth/wscript_build +++ b/source4/auth/wscript_build @@ -49,6 +49,13 @@ bld.SAMBA_BINARY('test_kerberos', for_selftest=True ) +bld.SAMBA_BINARY('test_auth_sam', + source='tests/sam.c', + deps='cmocka samdb samba-security ldb tevent', + local_include=False, + for_selftest=True + ) + pytalloc_util = bld.pyembed_libname('pytalloc-util') pyparam_util = bld.pyembed_libname('pyparam_util') pyldb_util = bld.pyembed_libname('pyldb-util') -- 2.25.1