From 635ef20b636637f1890a47b5f78d2a1273cb8d9c Mon Sep 17 00:00:00 2001
From: Daniel Markstedt <daniel@mindani.net>
Date: Thu, 25 Jun 2026 20:42:54 +0200
Subject: [PATCH] CVE-2026-62318: uams: harden auth state for DHX2 UAM

Reported-by: Alejandro Ramos <aramosf@gmail.com>
Signed-off-by: Daniel Markstedt <daniel@mindani.net>
Reviewed-by: Andy Lemin (andylemin)
---
 etc/uams/uams_dhx2_pam.c    | 85 +++++++++++++++++++++++++++++++------
 etc/uams/uams_dhx2_passwd.c | 77 ++++++++++++++++++++++++++++-----
 2 files changed, 138 insertions(+), 24 deletions(-)

diff --git a/etc/uams/uams_dhx2_pam.c b/etc/uams/uams_dhx2_pam.c
index c4ea3962c..ad599b1b4 100644
--- a/etc/uams/uams_dhx2_pam.c
+++ b/etc/uams/uams_dhx2_pam.c
@@ -46,6 +46,33 @@ static char *K_MD5hash = NULL;
 static int K_hash_len;
 static uint16_t ID;
 
+enum dhx2_state {
+    DHX2_STATE_IDLE,
+    DHX2_STATE_EXPECT_CONT1,
+    DHX2_STATE_EXPECT_CONT2,
+};
+
+static enum dhx2_state dhx2_state = DHX2_STATE_IDLE;
+
+static void dhx2_release_mpi(gcry_mpi_t *mpi)
+{
+    if (mpi != NULL && *mpi != NULL) {
+        gcry_mpi_release(*mpi);
+        *mpi = NULL;
+    }
+}
+
+static void dhx2_clear_session(void)
+{
+    dhx2_release_mpi(&Ra);
+    dhx2_release_mpi(&serverNonce);
+    free(K_MD5hash);
+    K_MD5hash = NULL;
+    K_hash_len = 0;
+    ID = 0;
+    dhx2_state = DHX2_STATE_IDLE;
+}
+
 /* The initialization vectors for CAST128 are fixed by Apple. */
 static unsigned char dhx_c2siv[] = { 'L', 'W', 'a', 'l', 'l', 'a', 'c', 'e' };
 static unsigned char dhx_s2civ[] = { 'C', 'J', 'a', 'l', 'b', 'e', 'r', 't' };
@@ -261,6 +288,7 @@ static int dhx2_setup(void *obj, char *ibuf _U_, size_t ibuflen _U_,
     char *Ra_binary = NULL;
     uint16_t uint16;
     *rbuflen = 0;
+    dhx2_clear_session();
     Ra = gcry_mpi_new(0);
     Ma = gcry_mpi_new(0);
     /* Generate our random number Ra. */
@@ -316,9 +344,15 @@ static int dhx2_setup(void *obj, char *ibuf _U_, size_t ibuflen _U_,
     rbuf += PRIMEBITS / 8;
     *rbuflen += PRIMEBITS / 8;
     ret = AFPERR_AUTHCONT;
+    dhx2_state = DHX2_STATE_EXPECT_CONT1;
 error:              /* We exit here anyway */
     /* We will need Ra later, but mustn't forget to release it ! */
     gcry_mpi_release(Ma);
+
+    if (ret != AFPERR_AUTHCONT) {
+        dhx2_clear_session();
+    }
+
     return ret;
 }
 
@@ -432,6 +466,13 @@ static int logincont1(void *obj _U_, char *ibuf, size_t ibuflen, char *rbuf,
     gcry_error_t ctxerror;
     uint16_t uint16;
     *rbuflen = 0;
+
+    if (dhx2_state != DHX2_STATE_EXPECT_CONT1 || Ra == NULL) {
+        LOG(log_info, logtype_uams,
+            "DHX2: unexpected first login continuation");
+        return AFPERR_PARAM;
+    }
+
     Mb = gcry_mpi_new(0);
     K = gcry_mpi_new(0);
     clientNonce = gcry_mpi_new(0);
@@ -560,13 +601,17 @@ static int logincont1(void *obj _U_, char *ibuf, size_t ibuflen, char *rbuf,
 error_ctx:
     gcry_cipher_close(ctx);
 error_noctx:
-    gcry_mpi_release(serverNonce);
-    free(K_MD5hash);
-    K_MD5hash = NULL;
 exit:
+
+    if (ret == AFPERR_AUTHCONT) {
+        dhx2_release_mpi(&Ra);
+        dhx2_state = DHX2_STATE_EXPECT_CONT2;
+    } else {
+        dhx2_clear_session();
+    }
+
     gcry_mpi_release(K);
     gcry_mpi_release(Mb);
-    gcry_mpi_release(Ra);
     gcry_mpi_release(clientNonce);
     return ret;
 }
@@ -586,6 +631,14 @@ static int logincont2(void *obj_in, struct passwd **uam_pwd,
     char *utfpass = NULL;
     *rbuflen = 0;
 
+    if (dhx2_state != DHX2_STATE_EXPECT_CONT2 ||
+            K_MD5hash == NULL || serverNonce == NULL) {
+        LOG(log_info, logtype_uams,
+            "DHX2: unexpected second login continuation");
+        dhx2_clear_session();
+        return AFPERR_PARAM;
+    }
+
     /* Packet size should be: Session ID + ServerNonce + Passwd buffer (evantually +10 extra bytes, see Apples Docs) */
     if ((ibuflen != 2 + 16 + 256) && (ibuflen != 2 + 16 + 256 + 10)) {
         LOG(log_error, logtype_uams,
@@ -753,9 +806,7 @@ static int logincont2(void *obj_in, struct passwd **uam_pwd,
         free(utfpass);
     }
 
-    free(K_MD5hash);
-    K_MD5hash = NULL;
-    gcry_mpi_release(serverNonce);
+    dhx2_clear_session();
     gcry_mpi_release(retServerNonce);
     return ret;
 }
@@ -770,12 +821,14 @@ static int pam_logincont(void *obj, struct passwd **uam_pwd,
     memcpy(&retID, ibuf, sizeof(uint16_t));
     retID = ntohs(retID);
 
-    if (retID == ID) {
+    if (retID == ID && dhx2_state == DHX2_STATE_EXPECT_CONT1) {
         ret = logincont1(obj, ibuf, ibuflen, rbuf, rbuflen);
-    } else if (retID == ID + 1) {
+    } else if (retID == ID + 1 && dhx2_state == DHX2_STATE_EXPECT_CONT2) {
         ret = logincont2(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen);
     } else {
-        LOG(log_info, logtype_uams, "DHX2: Session ID Mismatch");
+        LOG(log_info, logtype_uams,
+            "DHX2: Session ID mismatch or unexpected login continuation");
+        dhx2_clear_session();
         ret = AFPERR_PARAM;
     }
 
@@ -824,6 +877,14 @@ static int changepw_3(void *obj _U_,
     *rbuflen = 0;
     LOG(log_error, logtype_uams, "DHX2 ChangePW: packet 3 processing");
 
+    if (dhx2_state != DHX2_STATE_EXPECT_CONT2 ||
+            K_MD5hash == NULL || serverNonce == NULL) {
+        LOG(log_info, logtype_uams,
+            "DHX2 ChangePW: unexpected password-change continuation");
+        ret = AFPERR_PARAM;
+        goto error_noctx;
+    }
+
     /* Packet size should be: Session ID + ServerNonce + 2*Passwd buffer */
     if (ibuflen != 2 + 16 + 2 * 256) {
         LOG(log_error, logtype_uams, "DHX2: Packet length not correct");
@@ -971,9 +1032,7 @@ static int changepw_3(void *obj _U_,
 error_ctx:
     gcry_cipher_close(ctx);
 error_noctx:
-    free(K_MD5hash);
-    K_MD5hash = NULL;
-    gcry_mpi_release(serverNonce);
+    dhx2_clear_session();
     gcry_mpi_release(retServerNonce);
     return ret;
 }
diff --git a/etc/uams/uams_dhx2_passwd.c b/etc/uams/uams_dhx2_passwd.c
index 54d5a5734..092de77f6 100644
--- a/etc/uams/uams_dhx2_passwd.c
+++ b/etc/uams/uams_dhx2_passwd.c
@@ -49,6 +49,34 @@ static char *K_MD5hash = NULL;
 static int K_hash_len;
 static uint16_t ID;
 
+enum dhx2_state {
+    DHX2_STATE_IDLE,
+    DHX2_STATE_EXPECT_CONT1,
+    DHX2_STATE_EXPECT_CONT2,
+};
+
+static enum dhx2_state dhx2_state = DHX2_STATE_IDLE;
+
+static void dhx2_release_mpi(gcry_mpi_t *mpi)
+{
+    if (mpi != NULL && *mpi != NULL) {
+        gcry_mpi_release(*mpi);
+        *mpi = NULL;
+    }
+}
+
+static void dhx2_clear_session(void)
+{
+    dhx2_release_mpi(&p);
+    dhx2_release_mpi(&Ra);
+    dhx2_release_mpi(&serverNonce);
+    free(K_MD5hash);
+    K_MD5hash = NULL;
+    K_hash_len = 0;
+    ID = 0;
+    dhx2_state = DHX2_STATE_IDLE;
+}
+
 /* The initialization vectors for CAST128 are fixed by Apple. */
 static unsigned char dhx_c2siv[] = { 'L', 'W', 'a', 'l', 'l', 'a', 'c', 'e' };
 static unsigned char dhx_s2civ[] = { 'C', 'J', 'a', 'l', 'b', 'e', 'r', 't' };
@@ -166,6 +194,7 @@ static int dhx2_setup(void *obj, char *ibuf _U_, size_t ibuflen _U_,
 #endif /* SHADOWPW */
     uint16_t uint16;
     *rbuflen = 0;
+    dhx2_clear_session();
     /* Initialize passwd/shadow */
 #ifdef SHADOWPW
 
@@ -248,10 +277,16 @@ static int dhx2_setup(void *obj, char *ibuf _U_, size_t ibuflen _U_,
     rbuf += PRIMEBITS / 8;
     *rbuflen += PRIMEBITS / 8;
     ret = AFPERR_AUTHCONT;
+    dhx2_state = DHX2_STATE_EXPECT_CONT1;
 error:              /* We exit here anyway */
     /* We will only need p and Ra later, but mustn't forget to release it ! */
     gcry_mpi_release(g);
     gcry_mpi_release(Ma);
+
+    if (ret != AFPERR_AUTHCONT) {
+        dhx2_clear_session();
+    }
+
     return ret;
 }
 
@@ -366,6 +401,13 @@ static int logincont1(void *obj _U_, struct passwd **uam_pwd _U_,
     gcry_error_t ctxerror;
     uint16_t uint16;
     *rbuflen = 0;
+
+    if (dhx2_state != DHX2_STATE_EXPECT_CONT1 || Ra == NULL) {
+        LOG(log_info, logtype_uams,
+            "DHX2: unexpected first login continuation");
+        return AFPERR_PARAM;
+    }
+
     Mb = gcry_mpi_new(0);
     K = gcry_mpi_new(0);
     clientNonce = gcry_mpi_new(0);
@@ -495,14 +537,18 @@ static int logincont1(void *obj _U_, struct passwd **uam_pwd _U_,
 error_ctx:
     gcry_cipher_close(ctx);
 error_noctx:
-    gcry_mpi_release(serverNonce);
-    free(K_MD5hash);
-    K_MD5hash = NULL;
 exit:
+
+    if (ret == AFPERR_AUTHCONT) {
+        dhx2_release_mpi(&Ra);
+        dhx2_release_mpi(&p);
+        dhx2_state = DHX2_STATE_EXPECT_CONT2;
+    } else {
+        dhx2_clear_session();
+    }
+
     gcry_mpi_release(K);
     gcry_mpi_release(Mb);
-    gcry_mpi_release(Ra);
-    gcry_mpi_release(p);
     gcry_mpi_release(clientNonce);
     return ret;
 }
@@ -522,6 +568,15 @@ static int logincont2(void *obj _U_, struct passwd **uam_pwd,
     *rbuflen = 0;
     retServerNonce = gcry_mpi_new(0);
 
+    if (dhx2_state != DHX2_STATE_EXPECT_CONT2 ||
+            K_MD5hash == NULL || serverNonce == NULL) {
+        LOG(log_info, logtype_uams,
+            "DHX2: unexpected second login continuation");
+        dhx2_clear_session();
+        gcry_mpi_release(retServerNonce);
+        return AFPERR_PARAM;
+    }
+
     /* Packet size should be: Session ID + ServerNonce + Passwd buffer (evantually +10 extra bytes, see Apples Docs)*/
     if ((ibuflen != 2 + 16 + 256) && (ibuflen != 2 + 16 + 256 + 10)) {
         LOG(log_error, logtype_uams,
@@ -615,9 +670,7 @@ static int logincont2(void *obj _U_, struct passwd **uam_pwd,
     gcry_cipher_close(ctx);
 error_noctx:
 exit:
-    free(K_MD5hash);
-    K_MD5hash = NULL;
-    gcry_mpi_release(serverNonce);
+    dhx2_clear_session();
     gcry_mpi_release(retServerNonce);
     return ret;
 }
@@ -632,12 +685,14 @@ static int passwd_logincont(void *obj, struct passwd **uam_pwd,
     memcpy(&retID, ibuf, sizeof(uint16_t));
     retID = ntohs(retID);
 
-    if (retID == ID) {
+    if (retID == ID && dhx2_state == DHX2_STATE_EXPECT_CONT1) {
         ret = logincont1(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen);
-    } else if (retID == ID + 1) {
+    } else if (retID == ID + 1 && dhx2_state == DHX2_STATE_EXPECT_CONT2) {
         ret = logincont2(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen);
     } else {
-        LOG(log_info, logtype_uams, "DHX2: Session ID Mismatch");
+        LOG(log_info, logtype_uams,
+            "DHX2: Session ID mismatch or unexpected login continuation");
+        dhx2_clear_session();
         ret = AFPERR_PARAM;
     }
 
