From fba17d9f8e6437fc675608c0507d6a00f830aaea Mon Sep 17 00:00:00 2001
From: Samuel Cabrero <scabrero@suse.de>
Date: Wed, 4 Jan 2023 17:50:04 +0100
Subject: [PATCH 40/40] CVE-2022-38023 s3:rpc_server/netlogon: implement
 "server schannel require seal[:COMPUTERACCOUNT]"

By default we'll now require schannel connections with
privacy/sealing/encryption.

But we allow exceptions for specific computer/trust accounts.

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

Signed-off-by: Samuel Cabrero <scabrero@suse.de>

Conflict: remove selftest/target/Samba3.pm
Reference: https://attachments.samba.org/attachment.cgi?id=17736
---
 selftest/target/Samba3.pm                   |  14 ++
 source3/rpc_server/netlogon/srv_netlog_nt.c | 237 +++++++++++++++++++-
 2 files changed, 249 insertions(+), 2 deletions(-)

diff --git a/source3/rpc_server/netlogon/srv_netlog_nt.c b/source3/rpc_server/netlogon/srv_netlog_nt.c
index f238d7ce42b..df305e94479 100644
--- a/source3/rpc_server/netlogon/srv_netlog_nt.c
+++ b/source3/rpc_server/netlogon/srv_netlog_nt.c
@@ -1075,14 +1075,22 @@ static NTSTATUS netr_check_schannel(struct pipes_struct *p,
 	bool schannel_global_required = (lp_server_schannel() == true) ? true:false;
 	bool schannel_required = schannel_global_required;
 	bool schannel_explicitly_set = false;
+	bool seal_global_required = (lp_server_schannel_require_seal() == true) ? true:false;
+	bool seal_required = seal_global_required;
+	bool seal_explicitly_set = false;
 	int CVE_2020_1472_warn_level = lp_parm_int(GLOBAL_SECTION_SNUM,
 		"CVE_2020_1472", "warn_about_unused_debug_level", DBGLVL_ERR);
 	int CVE_2020_1472_error_level = lp_parm_int(GLOBAL_SECTION_SNUM,
 		"CVE_2020_1472", "error_debug_level", DBGLVL_ERR);
+	int CVE_2022_38023_warn_level = lp_parm_int(GLOBAL_SECTION_SNUM,
+		"CVE_2022_38023", "warn_about_unused_debug_level", DBGLVL_ERR);
+	int CVE_2022_38023_error_level = lp_parm_int(GLOBAL_SECTION_SNUM,
+		"CVE_2022_38023", "error_debug_level", DBGLVL_ERR);
 	unsigned int dbg_lvl = DBGLVL_DEBUG;
 	const char *opname = "<unknown>";
 	const char *reason = "<unknown>";
 	static bool warned_global_schannel_once = false;
+	static bool warned_global_seal_once = false;
 
 	if (opnum < ndr_table_netlogon.num_calls) {
 		opname = ndr_table_netlogon.calls[opnum].name;
@@ -1119,6 +1127,20 @@ static NTSTATUS netr_check_schannel(struct pipes_struct *p,
 		return status;
 	}
 
+	/*
+	 * We don't use lp_parm_bool(), as we
+	 * need the explicit_opt pointer in order to
+	 * adjust the debug messages.
+	 */
+	explicit_opt = lp_parm_const_string(GLOBAL_SECTION_SNUM,
+					    "server schannel require seal",
+					    creds->account_name,
+					    NULL);
+	if (explicit_opt != NULL) {
+		seal_required = lp_bool(explicit_opt);
+	}
+	seal_explicitly_set = explicit_opt != NULL;
+
 	/*
 	 * We don't use lp_parm_bool(), as we
 	 * need the explicit_opt pointer in order to
@@ -1134,7 +1156,96 @@ static NTSTATUS netr_check_schannel(struct pipes_struct *p,
 	}
 	schannel_explicitly_set = explicit_opt != NULL;
 
+	if (auth_type == DCERPC_AUTH_TYPE_SCHANNEL &&
+	    auth_level == DCERPC_AUTH_LEVEL_PRIVACY)
+	{
+		status = NT_STATUS_OK;
+
+		if (schannel_explicitly_set && !schannel_required) {
+			dbg_lvl = MIN(dbg_lvl, CVE_2020_1472_warn_level);
+		} else if (!schannel_required) {
+			dbg_lvl = MIN(dbg_lvl, DBGLVL_INFO);
+		}
+		if (seal_explicitly_set && !seal_required) {
+			dbg_lvl = MIN(dbg_lvl, CVE_2022_38023_warn_level);
+		} else if (!seal_required) {
+			dbg_lvl = MIN(dbg_lvl, DBGLVL_INFO);
+		}
+
+		DEBUG(dbg_lvl, (
+		      "CVE-2020-1472(ZeroLogon)/CVE-2022-38023: "
+		      "%s request (opnum[%u]) %s schannel from "
+		      "client_account[%s] client_computer_name[%s] %s\n",
+		      opname, opnum, reason,
+		      log_escape(frame, creds->account_name),
+		      log_escape(frame, creds->computer_name),
+		      nt_errstr(status)));
+
+		if (schannel_explicitly_set && !schannel_required) {
+			DEBUG(CVE_2020_1472_warn_level, (
+			      "CVE-2020-1472(ZeroLogon): "
+			      "Option 'server require schannel:%s = no' not needed for '%s'!\n",
+			      log_escape(frame, creds->account_name),
+			      log_escape(frame, creds->computer_name)));
+		}
+
+		if (seal_explicitly_set && !seal_required) {
+			DEBUG(CVE_2022_38023_warn_level, (
+			      "CVE-2022-38023: "
+			      "Option 'server schannel require seal:%s = no' not needed for '%s'!\n",
+			      log_escape(frame, creds->account_name),
+			      log_escape(frame, creds->computer_name)));
+		}
+
+		TALLOC_FREE(frame);
+		return status;
+	}
+
 	if (auth_type == DCERPC_AUTH_TYPE_SCHANNEL) {
+		if (seal_required) {
+			status = NT_STATUS_ACCESS_DENIED;
+
+			if (seal_explicitly_set) {
+				dbg_lvl = DBGLVL_NOTICE;
+			} else {
+				dbg_lvl = MIN(dbg_lvl, CVE_2022_38023_error_level);
+			}
+			if (schannel_explicitly_set && !schannel_required) {
+				dbg_lvl = MIN(dbg_lvl, CVE_2022_38023_warn_level);
+			}
+
+			DEBUG(dbg_lvl, (
+			      "CVE-2022-38023: "
+			      "%s request (opnum[%u]) %s schannel from "
+			      "from client_account[%s] client_computer_name[%s] %s\n",
+			      opname, opnum, reason,
+			      log_escape(frame, creds->account_name),
+			      log_escape(frame, creds->computer_name),
+			      nt_errstr(status)));
+			if (seal_explicitly_set) {
+				D_NOTICE("CVE-2022-38023: Option "
+					 "'server schannel require seal:%s = yes' "
+					 "rejects access for client.\n",
+					 log_escape(frame, creds->account_name));
+			} else {
+				DEBUG(CVE_2020_1472_error_level, (
+				      "CVE-2022-38023: Check if option "
+				      "'server schannel require seal:%s = no' "
+				      "might be needed for a legacy client.\n",
+				      log_escape(frame, creds->account_name)));
+			}
+			if (schannel_explicitly_set && !schannel_required) {
+				DEBUG(CVE_2020_1472_warn_level, (
+				      "CVE-2020-1472(ZeroLogon): Option "
+				      "'server require schannel:%s = no' "
+				      "not needed for '%s'!\n",
+				      log_escape(frame, creds->account_name),
+				      log_escape(frame, creds->computer_name)));
+			}
+			TALLOC_FREE(frame);
+			return status;
+		}
+
 		status = NT_STATUS_OK;
 
 		if (schannel_explicitly_set && !schannel_required) {
@@ -1142,6 +1253,11 @@ static NTSTATUS netr_check_schannel(struct pipes_struct *p,
 		} else if (!schannel_required) {
 			dbg_lvl = MIN(dbg_lvl, DBGLVL_INFO);
 		}
+		if (seal_explicitly_set && !seal_required) {
+			dbg_lvl = MIN(dbg_lvl, DBGLVL_INFO);
+		} else if (!seal_required) {
+			dbg_lvl = MIN(dbg_lvl, CVE_2022_38023_error_level);
+		}
 
 		DEBUG(dbg_lvl, (
 		      "CVE-2020-1472(ZeroLogon): "
@@ -1151,7 +1267,6 @@ static NTSTATUS netr_check_schannel(struct pipes_struct *p,
 		      log_escape(frame, creds->account_name),
 		      log_escape(frame, creds->computer_name),
 		      nt_errstr(status)));
-
 		if (schannel_explicitly_set && !schannel_required) {
 			DEBUG(CVE_2020_1472_warn_level, (
 			      "CVE-2020-1472(ZeroLogon): "
@@ -1159,7 +1274,77 @@ static NTSTATUS netr_check_schannel(struct pipes_struct *p,
 			      log_escape(frame, creds->account_name),
 			      log_escape(frame, creds->computer_name)));
 		}
+		if (seal_explicitly_set && !seal_required) {
+			D_INFO("CVE-2022-38023: "
+			       "Option 'server schannel require seal:%s = no' still needed for '%s'!\n",
+			       log_escape(frame, creds->account_name),
+			       log_escape(frame, creds->computer_name));
+		} else if (!seal_required) {
+			/*
+			 * admins should set
+			 * server schannel require seal:COMPUTER$ = no
+			 * in order to avoid the level 0 messages.
+			 * Over time they can switch the global value
+			 * to be strict.
+			 */
+			DEBUG(CVE_2022_38023_error_level, (
+			      "CVE-2022-38023: "
+			      "Please use 'server schannel require seal:%s = no' "
+			      "for '%s' to avoid this warning!\n",
+			      log_escape(frame, creds->account_name),
+			      log_escape(frame, creds->computer_name)));
+		}
+
+		TALLOC_FREE(frame);
+		return status;
+	}
+
+	if (seal_required) {
+		status = NT_STATUS_ACCESS_DENIED;
 
+		if (seal_explicitly_set) {
+			dbg_lvl = MIN(dbg_lvl, DBGLVL_NOTICE);
+		} else {
+			dbg_lvl = MIN(dbg_lvl, CVE_2022_38023_error_level);
+		}
+		if (!schannel_explicitly_set) {
+			dbg_lvl = MIN(dbg_lvl, CVE_2020_1472_error_level);
+		} else if (schannel_required) {
+			dbg_lvl = MIN(dbg_lvl, DBGLVL_NOTICE);
+		}
+
+		DEBUG(dbg_lvl, (
+		      "CVE-2020-1472(ZeroLogon)/CVE-2022-38023: "
+		      "%s request (opnum[%u]) %s schannel from "
+		      "from client_account[%s] client_computer_name[%s] %s\n",
+		      opname, opnum, reason,
+		      log_escape(frame, creds->account_name),
+		      log_escape(frame, creds->computer_name),
+		      nt_errstr(status)));
+		if (seal_explicitly_set) {
+			D_NOTICE("CVE-2022-38023: Option "
+			         "'server schannel require seal:%s = yes' "
+			         "rejects access for client.\n",
+			         log_escape(frame, creds->account_name));
+		} else {
+			DEBUG(CVE_2022_38023_error_level, (
+			      "CVE-2022-38023: Check if option "
+			      "'server schannel require seal:%s = no' "
+			      "might be needed for a legacy client.\n",
+			      log_escape(frame, creds->account_name)));
+		}
+		if (!schannel_explicitly_set) {
+			DEBUG(CVE_2020_1472_error_level, (
+			      "CVE-2020-1472(ZeroLogon): Check if option "
+			      "'server require schannel:%s = no' "
+			      "might be needed for a legacy client.\n",
+			      log_escape(frame, creds->account_name)));
+		} else if (schannel_required) {
+			D_NOTICE("CVE-2022-38023: Option "
+			         "'server require schannel:%s = yes' "
+			         "also rejects access for client.\n",
+			         log_escape(frame, creds->account_name));
+		}
 		TALLOC_FREE(frame);
 		return status;
 	}
@@ -1172,6 +1357,9 @@ static NTSTATUS netr_check_schannel(struct pipes_struct *p,
 		} else {
 			dbg_lvl = MIN(dbg_lvl, CVE_2020_1472_error_level);
 		}
+		if (!seal_explicitly_set) {
+			dbg_lvl = MIN(dbg_lvl, CVE_2022_38023_error_level);
+		}
 
 		DEBUG(dbg_lvl, (
 		      "CVE-2020-1472(ZeroLogon)/CVE-2022-38023: "
@@ -1193,6 +1381,13 @@ static NTSTATUS netr_check_schannel(struct pipes_struct *p,
 			      "might be needed for a legacy client.\n",
 			      log_escape(frame, creds->account_name)));
 		}
+		if (!seal_explicitly_set) {
+			DEBUG(CVE_2022_38023_error_level, (
+			      "CVE-2022-38023: Check if option "
+			      "'server schannel require seal:%s = no' "
+			      "might be needed for a legacy client.\n",
+			      log_escape(frame, creds->account_name)));
+		}
 		TALLOC_FREE(frame);
 		return status;
 	}
@@ -1207,8 +1402,24 @@ static NTSTATUS netr_check_schannel(struct pipes_struct *p,
 		warned_global_schannel_once = true;
 	}
 
+	if (!seal_global_required && !warned_global_seal_once) {
+		/*
+		 * We want admins to notice their misconfiguration!
+		 */
+		DBG_ERR("CVE-2022-38023 (and others): "
+			"Please configure 'server schannel require seal = yes' (the default), "
+			"See https://bugzilla.samba.org/show_bug.cgi?id=15240\n");
+		warned_global_seal_once = true;
+	}
+
 	status = NT_STATUS_OK;
 
+	if (seal_explicitly_set) {
+		dbg_lvl = MIN(dbg_lvl, DBGLVL_INFO);
+	} else {
+		dbg_lvl = MIN(dbg_lvl, CVE_2022_38023_error_level);
+	}
+
 	if (schannel_explicitly_set) {
 		dbg_lvl = MIN(dbg_lvl, DBGLVL_INFO);
 	} else {
@@ -1224,6 +1435,28 @@ static NTSTATUS netr_check_schannel(struct pipes_struct *p,
 	      log_escape(frame, creds->computer_name),
 	      nt_errstr(status)));
 
+	if (seal_explicitly_set) {
+		D_INFO("CVE-2022-38023: Option "
+		       "'server schannel require seal:%s = no' "
+		       "still needed for '%s'!\n",
+		       log_escape(frame, creds->account_name),
+		       log_escape(frame, creds->computer_name));
+	} else {
+		/*
+		 * admins should set
+		 * server schannel require seal:COMPUTER$ = no
+		 * in order to avoid the level 0 messages.
+		 * Over time they can switch the global value
+		 * to be strict.
+		 */
+		DEBUG(CVE_2022_38023_error_level, (
+		      "CVE-2022-38023: Please use "
+		       "'server schannel require seal:%s = no' "
+		      "for '%s' to avoid this warning!\n",
+		      log_escape(frame, creds->account_name),
+		      log_escape(frame, creds->computer_name)));
+	}
+
 	if (schannel_explicitly_set) {
 		D_INFO("CVE-2020-1472(ZeroLogon): Option "
 		       "'server require schannel:%s = no' "
@@ -1247,7 +1480,7 @@ static NTSTATUS netr_check_schannel(struct pipes_struct *p,
 	}
 
 	TALLOC_FREE(frame);
-	return NT_STATUS_OK;
+	return status;
 }
 
 static NTSTATUS netr_creds_server_step_check(struct pipes_struct *p,
-- 
2.39.0
