From f77231f1ae963599b834dde167a8854da08ca6d7 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet@samba.org>
Date: Fri, 22 Oct 2021 16:07:46 +1300
Subject: [PATCH 066/266] CVE-2020-25722 dsdb: Prohibit mismatch between UF_
 account types and objectclass.

There are a lot of knownfail entries added with this commit.  These
all need to be addressed and removed in subsequent commits which
will restructure the tests to pass within this new reality.

The restriction is not applied to users with administrator rights,
as this breaks a lot of tests and provides no security benefit.

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

Signed-off-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>

Conflict:remove test
Reference:https://gitlab.com/samba-team/samba/-/commit/f77231f1ae963599b834dde167a8854da08ca6d7

---
 source4/dsdb/samdb/ldb_modules/samldb.c       | 153 ++++++++++++++----
 1 files changed, 122 insertions(+), 31 deletions(-)

diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c
index 8df86f29883..15459abcbca 100644
--- a/source4/dsdb/samdb/ldb_modules/samldb.c
+++ b/source4/dsdb/samdb/ldb_modules/samldb.c
@@ -1365,7 +1365,8 @@ static int samldb_check_user_account_control_rules(struct samldb_ctx *ac,
 						   struct dom_sid *sid,
 						   uint32_t req_uac,
 						   uint32_t user_account_control,
-						   uint32_t user_account_control_old);
+						   uint32_t user_account_control_old,
+						   bool is_computer_objectclass);
 
 /*
  * "Objectclass" trigger (MS-SAMR 3.1.1.8.1)
@@ -1484,21 +1485,12 @@ static int samldb_objectclass_trigger(struct samldb_ctx *ac)
 			ret = samldb_check_user_account_control_rules(ac, NULL,
 								      raw_uac,
 								      user_account_control,
-								      0);
+								      0,
+								      is_computer_objectclass);
 			if (ret != LDB_SUCCESS) {
 				return ret;
 			}
 
-			/* Workstation and (read-only) DC objects do need objectclass "computer" */
-			if ((samdb_find_attribute(ldb, ac->msg,
-						  "objectclass", "computer") == NULL) &&
-			    (user_account_control &
-			     (UF_SERVER_TRUST_ACCOUNT | UF_WORKSTATION_TRUST_ACCOUNT))) {
-				ldb_set_errstring(ldb,
-						  "samldb: Requested account type does need objectclass 'computer'!");
-				return LDB_ERR_OBJECT_CLASS_VIOLATION;
-			}
-
 			/* add "sAMAccountType" attribute */
 			ret = dsdb_user_obj_set_account_type(ldb, ac->msg, user_account_control, NULL);
 			if (ret != LDB_SUCCESS) {
@@ -1993,6 +1985,106 @@ static int samldb_check_user_account_control_invariants(struct samldb_ctx *ac,
 	return ret;
 }
 
+/*
+ * It would be best if these rules apply, always, but for now they
+ * apply only to non-admins
+ */
+static int samldb_check_user_account_control_objectclass_invariants(
+	struct samldb_ctx *ac,
+	uint32_t user_account_control,
+	uint32_t user_account_control_old,
+	bool is_computer_objectclass)
+{
+	struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+
+	uint32_t old_ufa = user_account_control_old & UF_ACCOUNT_TYPE_MASK;
+	uint32_t new_ufa = user_account_control & UF_ACCOUNT_TYPE_MASK;
+
+	uint32_t old_rodc = user_account_control_old & UF_PARTIAL_SECRETS_ACCOUNT;
+	uint32_t new_rodc = user_account_control & UF_PARTIAL_SECRETS_ACCOUNT;
+
+	bool is_admin;
+	struct security_token *user_token
+		= acl_user_token(ac->module);
+	if (user_token == NULL) {
+		return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+	}
+
+	is_admin
+		= security_token_has_builtin_administrators(user_token);
+
+
+	/*
+	 * We want to allow changes to (eg) disable an account
+	 * that was created wrong, only checking the
+	 * objectclass if the account type changes.
+	 */
+	if (old_ufa == new_ufa && old_rodc == new_rodc) {
+		return LDB_SUCCESS;
+	}
+
+	switch (new_ufa) {
+	case UF_NORMAL_ACCOUNT:
+		if (is_computer_objectclass && !is_admin) {
+			ldb_asprintf_errstring(ldb,
+				"%08X: samldb: UF_NORMAL_ACCOUNT "
+				"requires objectclass 'user' not 'computer'!",
+				W_ERROR_V(WERR_DS_MACHINE_ACCOUNT_CREATED_PRENT4));
+			return LDB_ERR_OBJECT_CLASS_VIOLATION;
+		}
+		break;
+
+	case UF_INTERDOMAIN_TRUST_ACCOUNT:
+		if (is_computer_objectclass) {
+			ldb_asprintf_errstring(ldb,
+				"%08X: samldb: UF_INTERDOMAIN_TRUST_ACCOUNT "
+				"requires objectclass 'user' not 'computer'!",
+				W_ERROR_V(WERR_DS_MACHINE_ACCOUNT_CREATED_PRENT4));
+			return LDB_ERR_OBJECT_CLASS_VIOLATION;
+		}
+		break;
+
+	case UF_WORKSTATION_TRUST_ACCOUNT:
+		if (!is_computer_objectclass) {
+			/*
+			 * Modify of a user account account into a
+			 * workstation without objectclass computer
+			 * as an admin is still permitted, but not
+			 * to make an RODC
+			 */
+			if (is_admin
+			    && ac->req->operation == LDB_MODIFY
+			    && new_rodc == 0) {
+				break;
+			}
+			ldb_asprintf_errstring(ldb,
+				"%08X: samldb: UF_WORKSTATION_TRUST_ACCOUNT "
+				"requires objectclass 'computer'!",
+				W_ERROR_V(WERR_DS_MACHINE_ACCOUNT_CREATED_PRENT4));
+			return LDB_ERR_OBJECT_CLASS_VIOLATION;
+		}
+		break;
+
+	case UF_SERVER_TRUST_ACCOUNT:
+		if (!is_computer_objectclass) {
+			ldb_asprintf_errstring(ldb,
+				"%08X: samldb: UF_SERVER_TRUST_ACCOUNT "
+				"requires objectclass 'computer'!",
+				W_ERROR_V(WERR_DS_MACHINE_ACCOUNT_CREATED_PRENT4));
+			return LDB_ERR_OBJECT_CLASS_VIOLATION;
+		}
+		break;
+
+	default:
+		ldb_asprintf_errstring(ldb,
+			"%08X: samldb: invalid userAccountControl[0x%08X]",
+			W_ERROR_V(WERR_INVALID_PARAMETER),
+				       user_account_control);
+		return LDB_ERR_OTHER;
+	}
+	return LDB_SUCCESS;
+}
+
 static int samldb_get_domain_secdesc(struct samldb_ctx *ac,
 				     struct security_descriptor **domain_sd)
 {
@@ -2191,7 +2283,8 @@ static int samldb_check_user_account_control_rules(struct samldb_ctx *ac,
 						   struct dom_sid *sid,
 						   uint32_t req_uac,
 						   uint32_t user_account_control,
-						   uint32_t user_account_control_old)
+						   uint32_t user_account_control_old,
+						   bool is_computer_objectclass)
 {
 	int ret;
 	struct dsdb_control_password_user_account_control *uac = NULL;
@@ -2200,6 +2293,14 @@ static int samldb_check_user_account_control_rules(struct samldb_ctx *ac,
 	if (ret != LDB_SUCCESS) {
 		return ret;
 	}
+	ret = samldb_check_user_account_control_objectclass_invariants(ac,
+								       user_account_control,
+								       user_account_control_old,
+								       is_computer_objectclass);
+	if (ret != LDB_SUCCESS) {
+		return ret;
+	}
+
 	ret = samldb_check_user_account_control_acl(ac, sid, user_account_control, user_account_control_old);
 	if (ret != LDB_SUCCESS) {
 		return ret;
@@ -2261,7 +2362,7 @@ static int samldb_user_account_control_change(struct samldb_ctx *ac)
 		"objectSid",
 		NULL
 	};
-	bool is_computer = false;
+	bool is_computer_objectclass = false;
 	bool old_is_critical = false;
 	bool new_is_critical = false;
 
@@ -2316,7 +2417,10 @@ static int samldb_user_account_control_change(struct samldb_ctx *ac)
 						     "lockoutTime", 0);
 	old_is_critical = ldb_msg_find_attr_as_bool(res->msgs[0],
 						    "isCriticalSystemObject", 0);
-	/* When we do not have objectclass "computer" we cannot switch to a (read-only) DC */
+	/*
+	 * When we do not have objectclass "computer" we cannot
+	 * switch to a workstation or (RO)DC
+	 */
 	el = ldb_msg_find_element(res->msgs[0], "objectClass");
 	if (el == NULL) {
 		return ldb_operr(ldb);
@@ -2324,7 +2428,7 @@ static int samldb_user_account_control_change(struct samldb_ctx *ac)
 	computer_val = data_blob_string_const("computer");
 	val = ldb_msg_find_val(el, &computer_val);
 	if (val != NULL) {
-		is_computer = true;
+		is_computer_objectclass = true;
 	}
 
 	old_ufa = old_uac & UF_ACCOUNT_TYPE_MASK;
@@ -2349,7 +2453,8 @@ static int samldb_user_account_control_change(struct samldb_ctx *ac)
 	ret = samldb_check_user_account_control_rules(ac, sid,
 						      raw_uac,
 						      new_uac,
-						      old_uac);
+						      old_uac,
+						      is_computer_objectclass);
 	if (ret != LDB_SUCCESS) {
 		return ret;
 	}
@@ -2371,25 +2476,11 @@ static int samldb_user_account_control_change(struct samldb_ctx *ac)
 	case UF_WORKSTATION_TRUST_ACCOUNT:
 		new_is_critical = false;
 		if (new_uac & UF_PARTIAL_SECRETS_ACCOUNT) {
-			if (!is_computer) {
-				ldb_asprintf_errstring(ldb,
-						       "%08X: samldb: UF_PARTIAL_SECRETS_ACCOUNT "
-						       "requires objectclass 'computer'!",
-						       W_ERROR_V(WERR_DS_MACHINE_ACCOUNT_CREATED_PRENT4));
-				return LDB_ERR_UNWILLING_TO_PERFORM;
-			}
 			new_is_critical = true;
 		}
 		break;
 
 	case UF_SERVER_TRUST_ACCOUNT:
-		if (!is_computer) {
-			ldb_asprintf_errstring(ldb,
-				"%08X: samldb: UF_SERVER_TRUST_ACCOUNT "
-				"requires objectclass 'computer'!",
-				W_ERROR_V(WERR_DS_MACHINE_ACCOUNT_CREATED_PRENT4));
-			return LDB_ERR_UNWILLING_TO_PERFORM;
-		}
 		new_is_critical = true;
 		break;
 
-- 
2.23.0

