From f6c5a60336de8fd67a2ef371dd2ee4cf75c53904 Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Mon, 30 May 2022 19:17:41 +1200
Subject: [PATCH 58/99] CVE-2022-2031 s4:kpasswd: Add MIT fallback for decoding
 setpw structure

The target principal and realm fields of the setpw structure are
supposed to be optional, but in MIT Kerberos they are mandatory. For
better compatibility and ease of testing, fall back to parsing the
simpler (containing only the new password) structure if the MIT function
fails to decode it.

Although the target principal and realm fields should be optional, one
is not supposed to specified without the other, so we don't have to deal
with the case where only one is specified.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=15047
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15049
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15074

Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Andreas Schneider <asn@samba.org>
---
 source4/kdc/kpasswd-service-mit.c | 94 ++++++++++++++++++++++++++-----
 1 file changed, 79 insertions(+), 15 deletions(-)

diff --git a/source4/kdc/kpasswd-service-mit.c b/source4/kdc/kpasswd-service-mit.c
index b53c1a4618a..9c4d2801669 100644
--- a/source4/kdc/kpasswd-service-mit.c
+++ b/source4/kdc/kpasswd-service-mit.c
@@ -28,6 +28,7 @@
 #include "kdc/kpasswd_glue.h"
 #include "kdc/kpasswd-service.h"
 #include "kdc/kpasswd-helper.h"
+#include "../lib/util/asn1.h"
 
 #define RFC3244_VERSION 0xff80
 
@@ -35,6 +36,52 @@ krb5_error_code decode_krb5_setpw_req(const krb5_data *code,
 				      krb5_data **password_out,
 				      krb5_principal *target_out);
 
+/*
+ * A fallback for when MIT refuses to parse a setpw structure without the
+ * (optional) target principal and realm
+ */
+static bool decode_krb5_setpw_req_simple(TALLOC_CTX *mem_ctx,
+					 const DATA_BLOB *decoded_data,
+					 DATA_BLOB *clear_data)
+{
+	struct asn1_data *asn1 = NULL;
+	bool ret;
+
+	asn1 = asn1_init(mem_ctx, 3);
+	if (asn1 == NULL) {
+		return false;
+	}
+
+	ret = asn1_load(asn1, *decoded_data);
+	if (!ret) {
+		goto out;
+	}
+
+	ret = asn1_start_tag(asn1, ASN1_SEQUENCE(0));
+	if (!ret) {
+		goto out;
+	}
+	ret = asn1_start_tag(asn1, ASN1_CONTEXT(0));
+	if (!ret) {
+		goto out;
+	}
+	ret = asn1_read_OctetString(asn1, mem_ctx, clear_data);
+	if (!ret) {
+		goto out;
+	}
+
+	ret = asn1_end_tag(asn1);
+	if (!ret) {
+		goto out;
+	}
+	ret = asn1_end_tag(asn1);
+
+out:
+	asn1_free(asn1);
+
+	return ret;
+}
+
 static krb5_error_code kpasswd_change_password(struct kdc_server *kdc,
 					       TALLOC_CTX *mem_ctx,
 					       struct auth_session_info *session_info,
@@ -93,9 +140,10 @@ static krb5_error_code kpasswd_set_password(struct kdc_server *kdc,
 					    const char **error_string)
 {
 	krb5_context context = kdc->smb_krb5_context->krb5_context;
+	DATA_BLOB clear_data;
 	krb5_data k_dec_data;
-	krb5_data *k_clear_data;
-	krb5_principal target_principal;
+	krb5_data *k_clear_data = NULL;
+	krb5_principal target_principal = NULL;
 	krb5_error_code code;
 	DATA_BLOB password;
 	char *target_realm = NULL;
@@ -114,29 +162,45 @@ static krb5_error_code kpasswd_set_password(struct kdc_server *kdc,
 	code = decode_krb5_setpw_req(&k_dec_data,
 				     &k_clear_data,
 				     &target_principal);
-	if (code != 0) {
-		DBG_WARNING("decode_krb5_setpw_req failed: %s\n",
-			    error_message(code));
-		ok = kpasswd_make_error_reply(mem_ctx,
-					      KRB5_KPASSWD_MALFORMED,
-					      "Failed to decode packet",
-					      kpasswd_reply);
+	if (code == 0) {
+		clear_data.data = (uint8_t *)k_clear_data->data;
+		clear_data.length = k_clear_data->length;
+	} else {
+		target_principal = NULL;
+
+		/*
+		 * The MIT decode failed, so fall back to trying the simple
+		 * case, without target_principal.
+		 */
+		ok = decode_krb5_setpw_req_simple(mem_ctx,
+						  decoded_data,
+						  &clear_data);
 		if (!ok) {
-			*error_string = "Failed to create reply";
-			return KRB5_KPASSWD_HARDERROR;
+			DBG_WARNING("decode_krb5_setpw_req failed: %s\n",
+				    error_message(code));
+			ok = kpasswd_make_error_reply(mem_ctx,
+						      KRB5_KPASSWD_MALFORMED,
+						      "Failed to decode packet",
+						      kpasswd_reply);
+			if (!ok) {
+				*error_string = "Failed to create reply";
+				return KRB5_KPASSWD_HARDERROR;
+			}
+			return 0;
 		}
-		return 0;
 	}
 
 	ok = convert_string_talloc_handle(mem_ctx,
 					  lpcfg_iconv_handle(kdc->task->lp_ctx),
 					  CH_UTF8,
 					  CH_UTF16,
-					  (const char *)k_clear_data->data,
-					  k_clear_data->length,
+					  clear_data.data,
+					  clear_data.length,
 					  (void **)&password.data,
 					  &password.length);
-	krb5_free_data(context, k_clear_data);
+	if (k_clear_data != NULL) {
+		krb5_free_data(context, k_clear_data);
+	}
 	if (!ok) {
 		DBG_WARNING("String conversion failed\n");
 		*error_string = "String conversion failed";
-- 
2.25.1
