From a19b425cccd935c77cf15f3123ed2611fc30cea6 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze@samba.org>
Date: Fri, 25 Nov 2022 14:05:30 +0100
Subject: [PATCH 28/30] CVE-2022-38023 s4: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: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
(cherry picked from commit b3ed90a0541a271a7c6d4bee1201fa47adc3c0c1)

Conflict: NA
Reference: https://attachments.samba.org/attachment.cgi?id=17692
---
 selftest/target/Samba4.pm                     |  28 ++
 source4/rpc_server/netlogon/dcerpc_netlogon.c | 244 +++++++++++++++++-
 2 files changed, 271 insertions(+), 1 deletion(-)

diff --git a/selftest/target/Samba4.pm b/selftest/target/Samba4.pm
index 3328cf617225..5c83bcbd4038 100755
--- a/selftest/target/Samba4.pm
+++ b/selftest/target/Samba4.pm
@@ -1646,9 +1646,23 @@ sub provision_ad_dc_ntvfs($$$)
 	server require schannel:schannel10\$ = no
 	server require schannel:schannel11\$ = no
 	server require schannel:torturetest\$ = no
+	server schannel require seal:schannel0\$ = no
+	server schannel require seal:schannel1\$ = no
+	server schannel require seal:schannel2\$ = no
+	server schannel require seal:schannel3\$ = no
+	server schannel require seal:schannel4\$ = no
+	server schannel require seal:schannel5\$ = no
+	server schannel require seal:schannel6\$ = no
+	server schannel require seal:schannel7\$ = no
+	server schannel require seal:schannel8\$ = no
+	server schannel require seal:schannel9\$ = no
+	server schannel require seal:schannel10\$ = no
+	server schannel require seal:schannel11\$ = no
+	server schannel require seal:torturetest\$ = no
 
 	# needed for 'samba.tests.auth_log' tests
 	server require schannel:LOCALDC\$ = no
+	server schannel require seal:LOCALDC\$ = no
 	";
 	push (@{$extra_provision_options}, "--use-ntvfs");
 	my $ret = $self->provision($prefix,
@@ -2050,6 +2064,19 @@ sub provision_ad_dc($$$$$$$)
 	server require schannel:schannel10\$ = no
 	server require schannel:schannel11\$ = no
 	server require schannel:torturetest\$ = no
+	server schannel require seal:schannel0\$ = no
+	server schannel require seal:schannel1\$ = no
+	server schannel require seal:schannel2\$ = no
+	server schannel require seal:schannel3\$ = no
+	server schannel require seal:schannel4\$ = no
+	server schannel require seal:schannel5\$ = no
+	server schannel require seal:schannel6\$ = no
+	server schannel require seal:schannel7\$ = no
+	server schannel require seal:schannel8\$ = no
+	server schannel require seal:schannel9\$ = no
+	server schannel require seal:schannel10\$ = no
+	server schannel require seal:schannel11\$ = no
+	server schannel require seal:torturetest\$ = no
 
 	auth event notification = true
 	dsdb event notification = true
@@ -2743,6 +2770,7 @@ sub setup_ad_dc_smb1
 
 	# needed for 'samba.tests.auth_log' tests
 	server require schannel:ADDCSMB1\$ = no
+	server schannel require seal:ADDCSMB1\$ = no
 ";
 	return _setup_ad_dc($self, $path, $conf_opts, "addcsmb1", "addom2.samba.example.com");
 }
diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c
index 45dcec133fb3..aa24a539abfd 100644
--- a/source4/rpc_server/netlogon/dcerpc_netlogon.c
+++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c
@@ -68,9 +68,11 @@ static NTSTATUS dcesrv_interface_netlogon_bind(struct dcesrv_connection_context
 	bool global_reject_md5_client = lpcfg_reject_md5_clients(lp_ctx);
 	int schannel = lpcfg_server_schannel(lp_ctx);
 	bool schannel_global_required = (schannel == true);
+	bool global_require_seal = lpcfg_server_schannel_require_seal(lp_ctx);
 	static bool warned_global_nt4_once = false;
 	static bool warned_global_md5_once = false;
 	static bool warned_global_schannel_once = false;
+	static bool warned_global_seal_once = false;
 
 	if (global_allow_nt4_crypto && !warned_global_nt4_once) {
 		/*
@@ -102,6 +104,16 @@ static NTSTATUS dcesrv_interface_netlogon_bind(struct dcesrv_connection_context
 		warned_global_schannel_once = true;
 	}
 
+	if (!global_require_seal && !warned_global_seal_once) {
+		/*
+		 * We want admins to notice their misconfiguration!
+		 */
+		D_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;
+	}
+
 	return dcesrv_interface_bind_reject_connect(context, iface);
 }
 
@@ -886,6 +898,10 @@ struct dcesrv_netr_check_schannel_state {
 	bool schannel_required;
 	bool schannel_explicitly_set;
 
+	bool seal_global_required;
+	bool seal_required;
+	bool seal_explicitly_set;
+
 	NTSTATUS result;
 };
 
@@ -900,6 +916,9 @@ static NTSTATUS dcesrv_netr_check_schannel_get_state(struct dcesrv_call_state *d
 	bool schannel_global_required = (schannel == true);
 	bool schannel_required = schannel_global_required;
 	const char *explicit_opt = NULL;
+	bool global_require_seal = lpcfg_server_schannel_require_seal(lp_ctx);
+	bool require_seal = global_require_seal;
+	const char *explicit_seal_opt = NULL;
 #define DCESRV_NETR_CHECK_SCHANNEL_STATE_MAGIC (NETLOGON_SERVER_PIPE_STATE_MAGIC+1)
 	struct dcesrv_netr_check_schannel_state *s = NULL;
 	NTSTATUS status;
@@ -937,6 +956,19 @@ new_state:
 	s->auth_level = auth_level;
 	s->result = NT_STATUS_MORE_PROCESSING_REQUIRED;
 
+	/*
+	 * We don't use lpcfg_parm_bool(), as we
+	 * need the explicit_opt pointer in order to
+	 * adjust the debug messages.
+	 */
+	explicit_seal_opt = lpcfg_get_parametric(lp_ctx,
+						 NULL,
+						 "server schannel require seal",
+						 creds->account_name);
+	if (explicit_seal_opt != NULL) {
+		require_seal = lp_bool(explicit_seal_opt);
+	}
+
 	/*
 	 * We don't use lpcfg_parm_bool(), as we
 	 * need the explicit_opt pointer in order to
@@ -954,6 +986,10 @@ new_state:
 	s->schannel_required = schannel_required;
 	s->schannel_explicitly_set = explicit_opt != NULL;
 
+	s->seal_global_required = global_require_seal;
+	s->seal_required = require_seal;
+	s->seal_explicitly_set = explicit_seal_opt != NULL;
+
 	status = dcesrv_iface_state_store_conn(dce_call,
 			DCESRV_NETR_CHECK_SCHANNEL_STATE_MAGIC,
 			s);
@@ -975,6 +1011,10 @@ static NTSTATUS dcesrv_netr_check_schannel_once(struct dcesrv_call_state *dce_ca
 		"CVE_2020_1472", "warn_about_unused_debug_level", DBGLVL_ERR);
 	int CVE_2020_1472_error_level = lpcfg_parm_int(lp_ctx, NULL,
 		"CVE_2020_1472", "error_debug_level", DBGLVL_ERR);
+	int CVE_2022_38023_warn_level = lpcfg_parm_int(lp_ctx, NULL,
+		"CVE_2022_38023", "warn_about_unused_debug_level", DBGLVL_ERR);
+	int CVE_2022_38023_error_level = lpcfg_parm_int(lp_ctx, NULL,
+		"CVE_2022_38023", "error_debug_level", DBGLVL_ERR);
 	TALLOC_CTX *frame = talloc_stackframe();
 	unsigned int dbg_lvl = DBGLVL_DEBUG;
 	const char *opname = "<unknown>";
@@ -1004,18 +1044,107 @@ static NTSTATUS dcesrv_netr_check_schannel_once(struct dcesrv_call_state *dce_ca
 		}
 
 		DEBUG(dbg_lvl, (
-		      "CVE-2020-1472(ZeroLogon): "
+		      "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(s->result)));
+		TALLOC_FREE(frame);
+		return s->result;
+	}
+
+	if (s->auth_type == DCERPC_AUTH_TYPE_SCHANNEL &&
+	    s->auth_level == DCERPC_AUTH_LEVEL_PRIVACY)
+	{
+		s->result = NT_STATUS_OK;
+
+		if (s->schannel_explicitly_set && !s->schannel_required) {
+			dbg_lvl = MIN(dbg_lvl, CVE_2020_1472_warn_level);
+		} else if (!s->schannel_required) {
+			dbg_lvl = MIN(dbg_lvl, DBGLVL_INFO);
+		}
+		if (s->seal_explicitly_set && !s->seal_required) {
+			dbg_lvl = MIN(dbg_lvl, CVE_2022_38023_warn_level);
+		} else if (!s->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(s->result)));
+
+		if (s->schannel_explicitly_set && !s->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 (s->seal_explicitly_set && !s->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 s->result;
 	}
 
 	if (s->auth_type == DCERPC_AUTH_TYPE_SCHANNEL) {
+		if (s->seal_required) {
+			s->result = NT_STATUS_ACCESS_DENIED;
+
+			if (s->seal_explicitly_set) {
+				dbg_lvl = DBGLVL_NOTICE;
+			} else {
+				dbg_lvl = MIN(dbg_lvl, CVE_2022_38023_error_level);
+			}
+			if (s->schannel_explicitly_set && !s->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(s->result)));
+			if (s->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 (s->schannel_explicitly_set && !s->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 s->result;
+		}
+
 		s->result = NT_STATUS_OK;
 
 		if (s->schannel_explicitly_set && !s->schannel_required) {
@@ -1023,6 +1152,11 @@ static NTSTATUS dcesrv_netr_check_schannel_once(struct dcesrv_call_state *dce_ca
 		} else if (!s->schannel_required) {
 			dbg_lvl = MIN(dbg_lvl, DBGLVL_INFO);
 		}
+		if (s->seal_explicitly_set && !s->seal_required) {
+			dbg_lvl = MIN(dbg_lvl, DBGLVL_INFO);
+		} else if (!s->seal_required) {
+			dbg_lvl = MIN(dbg_lvl, CVE_2022_38023_error_level);
+		}
 
 		DEBUG(dbg_lvl, (
 		      "CVE-2020-1472(ZeroLogon): "
@@ -1039,7 +1173,77 @@ static NTSTATUS dcesrv_netr_check_schannel_once(struct dcesrv_call_state *dce_ca
 			      log_escape(frame, creds->account_name),
 			      log_escape(frame, creds->computer_name)));
 		}
+		if (s->seal_explicitly_set && !s->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 (!s->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 s->result;
+	}
+
+	if (s->seal_required) {
+		s->result = NT_STATUS_ACCESS_DENIED;
+
+		if (s->seal_explicitly_set) {
+			dbg_lvl = MIN(dbg_lvl, DBGLVL_NOTICE);
+		} else {
+			dbg_lvl = MIN(dbg_lvl, CVE_2022_38023_error_level);
+		}
+		if (!s->schannel_explicitly_set) {
+			dbg_lvl = MIN(dbg_lvl, CVE_2020_1472_error_level);
+		} else if (s->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(s->result)));
+		if (s->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 (!s->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 (s->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 s->result;
 	}
@@ -1052,6 +1256,9 @@ static NTSTATUS dcesrv_netr_check_schannel_once(struct dcesrv_call_state *dce_ca
 		} else {
 			dbg_lvl = MIN(dbg_lvl, CVE_2020_1472_error_level);
 		}
+		if (!s->seal_explicitly_set) {
+			dbg_lvl = MIN(dbg_lvl, CVE_2022_38023_error_level);
+		}
 
 		DEBUG(dbg_lvl, (
 		      "CVE-2020-1472(ZeroLogon)/CVE-2022-38023: "
@@ -1073,12 +1280,25 @@ static NTSTATUS dcesrv_netr_check_schannel_once(struct dcesrv_call_state *dce_ca
 			      "might be needed for a legacy client.\n",
 			      log_escape(frame, creds->account_name)));
 		}
+		if (!s->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 s->result;
 	}
 
 	s->result = NT_STATUS_OK;
 
+	if (s->seal_explicitly_set) {
+		dbg_lvl = MIN(dbg_lvl, DBGLVL_INFO);
+	} else {
+		dbg_lvl = MIN(dbg_lvl, CVE_2022_38023_error_level);
+	}
+
 	if (s->schannel_explicitly_set) {
 		dbg_lvl = MIN(dbg_lvl, DBGLVL_INFO);
 	} else {
@@ -1094,6 +1314,28 @@ static NTSTATUS dcesrv_netr_check_schannel_once(struct dcesrv_call_state *dce_ca
 	      log_escape(frame, creds->computer_name),
 	      nt_errstr(s->result)));
 
+	if (s->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 (s->schannel_explicitly_set) {
 		D_INFO("CVE-2020-1472(ZeroLogon): Option "
 		       "'server require schannel:%s = no' "
-- 
2.34.1
