Package: release.debian.org
Severity: normal
Tags: trixie
X-Debbugs-Cc: [email protected]
Control: affects -1 + src:cyrus-sasl2
User: [email protected]
Usertags: pu

[ Reason ]
CVE-2026-107161 heap-based buffer overflow fix.

[ Impact ]
Crash of the application (denial of service).

[ Tests ]
I have introduced a regression test in autokpgtest that checks the
library for exploitability.

[ Risks ]
One line change with recalculation of one variable.
Usual library users are not affected by any behavior change.

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in stable
  [x] the issue is verified as fixed in unstable

[ Changes ]
The fix comes from the CVE-2026-107161 report.
diff -Nru cyrus-sasl2-2.1.28+dfsg1/debian/changelog 
cyrus-sasl2-2.1.28+dfsg1/debian/changelog
--- cyrus-sasl2-2.1.28+dfsg1/debian/changelog   2025-02-28 22:58:50.000000000 
+0100
+++ cyrus-sasl2-2.1.28+dfsg1/debian/changelog   2026-10-16 23:47:45.000000000 
+0200
@@ -1,3 +1,10 @@
+cyrus-sasl2 (2.1.28+dfsg1-9+deb13u1) trixie-security; urgency=medium
+
+  * Add CVE-2026-107161 regression test
+  * Add the CVE-2026-107161 report's suggested fix (Closes: #1150516)
+
+ -- Bastian Germann <[email protected]>  Fri, 16 Oct 2026 23:47:45 +0200
+
 cyrus-sasl2 (2.1.28+dfsg1-9) unstable; urgency=medium
 
   * Team upload
diff -Nru cyrus-sasl2-2.1.28+dfsg1/debian/patches/0043-CVE-2026-107161.patch 
cyrus-sasl2-2.1.28+dfsg1/debian/patches/0043-CVE-2026-107161.patch
--- cyrus-sasl2-2.1.28+dfsg1/debian/patches/0043-CVE-2026-107161.patch  
1970-01-01 01:00:00.000000000 +0100
+++ cyrus-sasl2-2.1.28+dfsg1/debian/patches/0043-CVE-2026-107161.patch  
2026-10-16 23:46:34.000000000 +0200
@@ -0,0 +1,14 @@
+Origin: https://github.com/cyrusimap/cyrus-sasl/issues/889
+---
+diff --git a/plugins/digestmd5.c b/plugins/digestmd5.c
+index c6b5431..8712975 100644
+--- a/plugins/digestmd5.c
++++ b/plugins/digestmd5.c
+@@ -553,6 +553,7 @@ static int add_to_challenge(const sasl_utils_t *utils,
+           if (quoted == NULL)
+               MEMERROR(utils);
+           valuesize = strlen(quoted);
++          newlen = (unsigned) (*curlen + 1 + namesize + 2 + valuesize + 2);
+           /* As the quoted string is bigger, make sure we have enough
+              space now */
+           ret = _plug_buf_alloc(utils, str, buflen, newlen);
diff -Nru cyrus-sasl2-2.1.28+dfsg1/debian/patches/series 
cyrus-sasl2-2.1.28+dfsg1/debian/patches/series
--- cyrus-sasl2-2.1.28+dfsg1/debian/patches/series      2025-02-28 
22:58:34.000000000 +0100
+++ cyrus-sasl2-2.1.28+dfsg1/debian/patches/series      2026-10-16 
23:46:55.000000000 +0200
@@ -40,3 +40,4 @@
 0039-Harmonize-MD5-signatures-with-OpenSSL.patch
 0040-Harmonize-getsubopt-casts-with-glibc.patch
 0041-digestmd5-Free-RC4-cipher.patch
+0043-CVE-2026-107161.patch
diff -Nru cyrus-sasl2-2.1.28+dfsg1/debian/tests/control 
cyrus-sasl2-2.1.28+dfsg1/debian/tests/control
--- cyrus-sasl2-2.1.28+dfsg1/debian/tests/control       2025-02-28 
22:58:34.000000000 +0100
+++ cyrus-sasl2-2.1.28+dfsg1/debian/tests/control       2026-10-16 
23:46:34.000000000 +0200
@@ -13,3 +13,8 @@
 Tests: shared-secret-mechs
 Depends: sasl2-bin, libsasl2-modules, libsasl2-modules-db, slapd, ldap-utils
 Restrictions: isolation-container, needs-root
+
+Tests: digestmd5-cve-2026-107161
+# Regression test for CVE-2026-107161 (heap overflow in the DIGEST-MD5
+# plugin's add_to_challenge()).  Does not require root.
+Depends: libsasl2-2, libsasl2-modules, libsasl2-dev, gcc
diff -Nru cyrus-sasl2-2.1.28+dfsg1/debian/tests/digestmd5-cve-2026-107161 
cyrus-sasl2-2.1.28+dfsg1/debian/tests/digestmd5-cve-2026-107161
--- cyrus-sasl2-2.1.28+dfsg1/debian/tests/digestmd5-cve-2026-107161     
1970-01-01 01:00:00.000000000 +0100
+++ cyrus-sasl2-2.1.28+dfsg1/debian/tests/digestmd5-cve-2026-107161     
2026-10-16 23:46:34.000000000 +0200
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# Regression test for CVE-2026-107161: heap buffer overflow in the
+# DIGEST-MD5 plugin's add_to_challenge() function.
+#
+# The harness (digestmd5-cve-2026-107161.c) plays the role of a malicious
+# DIGEST-MD5 server.  It feeds a challenge whose realm value is full of
+# backslashes to the SASL client API.  A vulnerable libsasl2-modules
+# corrupts the heap while quoting that realm to build the client reply;
+# glibc detects the corruption (abort / non-zero exit), which fails the
+# test.  A fixed library completes without corruption and the test passes.
+
+set -eu
+
+here=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
+work=$(mktemp -d)
+trap 'rm -rf "$work"' EXIT HUP INT TERM
+
+echo "Building the DIGEST-MD5 CVE-2026-107161 regression harness"
+gcc -O2 -Wall -I/usr/include/sasl -o "$work/digestmd5-cve" \
+    "$here/digestmd5-cve-2026-107161.c" -lsasl2
+
+echo "Running the DIGEST-MD5 CVE-2026-107161 regression harness"
+if "$work/digestmd5-cve"; then
+    echo "PASS: no heap corruption in DIGEST-MD5 add_to_challenge()"
+else
+    echo "FAIL: heap corruption detected in DIGEST-MD5 add_to_challenge() 
(CVE-2026-107161)"
+    exit 1
+fi
diff -Nru cyrus-sasl2-2.1.28+dfsg1/debian/tests/digestmd5-cve-2026-107161.c 
cyrus-sasl2-2.1.28+dfsg1/debian/tests/digestmd5-cve-2026-107161.c
--- cyrus-sasl2-2.1.28+dfsg1/debian/tests/digestmd5-cve-2026-107161.c   
1970-01-01 01:00:00.000000000 +0100
+++ cyrus-sasl2-2.1.28+dfsg1/debian/tests/digestmd5-cve-2026-107161.c   
2026-10-16 23:46:34.000000000 +0200
@@ -0,0 +1,191 @@
+/*
+ * Regression test for CVE-2026-107161
+ * (https://github.com/cyrusimap/cyrus-sasl/issues/889)
+ *
+ * Heap buffer overflow in the DIGEST-MD5 plugin's add_to_challenge().
+ *
+ * A malicious server sends a DIGEST-MD5 challenge whose "realm" value
+ * contains characters that need escaping ('\\' or '"').  When the client
+ * builds its reply, add_to_challenge() calls quote(), which doubles the
+ * size of the value, but (before the fix) failed to recompute the required
+ * buffer size.  The subsequent strcat() then wrote past the end of the
+ * heap allocation.
+ *
+ * This program plays the role of the malicious server: it feeds a crafted
+ * challenge to the SASL client API and afterwards forces a round of heap
+ * activity.  Against an affected libsasl2-modules the overflow corrupts
+ * the heap and glibc aborts ("double free or corruption"/"malloc():
+ * corrupted top size" and similar), so the program exits non-zero.  Against
+ * a fixed library there is no heap corruption and it exits 0.
+ *
+ * The wire challenge may not exceed 2048 bytes
+ * (digestmd5_client_mech_step() rejects larger challenges), which bounds
+ * the realm size.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sasl.h>
+
+#define AUTHID "testuser"
+#define PASSWORD "testpass"
+#define REALM_BACKSLASHES 900 /* literal '\' in the parsed realm */
+
+static const char *g_authid = AUTHID;
+static const char *g_passwd = PASSWORD;
+
+/* SASL_CB_USER / SASL_CB_AUTHNAME */
+static int getid(void *context __attribute__((unused)),
+                int id,
+                const char **result,
+                unsigned *len)
+{
+    const char *value;
+
+    if (!result)
+       return SASL_BADPARAM;
+
+    switch (id) {
+    case SASL_CB_USER:
+    case SASL_CB_AUTHNAME:
+       value = g_authid;
+       break;
+    default:
+       return SASL_BADPARAM;
+    }
+
+    *result = value;
+    if (len)
+       *len = (unsigned) strlen(value);
+    return SASL_OK;
+}
+
+/* SASL_CB_PASS */
+static int getsecret(sasl_conn_t *conn,
+                    void *context __attribute__((unused)),
+                    int id,
+                    sasl_secret_t **psecret)
+{
+    static sasl_secret_t *secret;
+    size_t len;
+
+    if (!conn || !psecret || id != SASL_CB_PASS)
+       return SASL_BADPARAM;
+
+    len = strlen(g_passwd);
+
+    free(secret);
+    secret = malloc(sizeof(sasl_secret_t) + len);
+    if (!secret)
+       return SASL_NOMEM;
+
+    secret->len = (unsigned long) len;
+    memcpy(secret->data, g_passwd, len + 1);
+
+    *psecret = secret;
+    return SASL_OK;
+}
+
+static sasl_callback_t callbacks[] = {
+    { SASL_CB_USER,     (int (*)(void)) &getid,     NULL },
+    { SASL_CB_AUTHNAME, (int (*)(void)) &getid,     NULL },
+    { SASL_CB_PASS,     (int (*)(void)) &getsecret, NULL },
+    { SASL_CB_LIST_END, NULL, NULL }
+};
+
+int main(void)
+{
+    static char challenge[2048 + 1];
+    sasl_conn_t *conn = NULL;
+    const char *out = NULL;
+    const char *chosenmech = NULL;
+    unsigned outlen = 0;
+    unsigned challen = 0;
+    unsigned i;
+    int r;
+
+    /* Build the malicious challenge.  The realm carries
+     * REALM_BACKSLASHES literal backslashes; each one has to be doubled
+     * ("\\") in the wire format.  The client unquotes them again, so when
+     * it later re-quotes the realm to build its reply, each character is
+     * doubled once more, growing far beyond the pre-allocated buffer.
+     */
+    {
+       unsigned pos = (unsigned) snprintf(challenge, sizeof(challenge),
+                                          "realm=\"");
+
+       for (i = 0; i < REALM_BACKSLASHES; i++) {
+           challenge[pos++] = '\\';
+           challenge[pos++] = '\\';
+       }
+       challenge[pos++] = '"';
+       pos += (unsigned) snprintf(challenge + pos, sizeof(challenge) - pos,
+                                  ",nonce=\"abc123\",qop=\"auth\","
+                                  "algorithm=md5-sess");
+       challen = pos;
+    }
+
+    if (challen > 2048) {
+       fprintf(stderr, "test bug: generated challenge too long (%u)\n",
+               challen);
+       return EXIT_FAILURE;
+    }
+
+    r = sasl_client_init(callbacks);
+    if (r != SASL_OK) {
+       fprintf(stderr, "sasl_client_init failed: %d (%s)\n",
+               r, sasl_errstring(r, NULL, NULL));
+       return EXIT_FAILURE;
+    }
+
+    r = sasl_client_new("imap", "server.example.com", NULL, NULL,
+                       callbacks, 0, &conn);
+    if (r != SASL_OK) {
+       fprintf(stderr, "sasl_client_new failed: %d (%s)\n",
+               r, sasl_errstring(r, NULL, NULL));
+       goto done;
+    }
+
+    r = sasl_client_start(conn, "DIGEST-MD5", NULL,
+                         &out, &outlen, &chosenmech);
+    if (r != SASL_CONTINUE) {
+       fprintf(stderr, "sasl_client_start failed: %d (%s)\n",
+               r, sasl_errstring(r, NULL, NULL));
+       goto done;
+    }
+
+    /*
+     * On vulnerable versions the heap overflow happens inside this call
+     * (the client quotes the malicious realm while building its reply).
+     * The return value is not meaningful here: even on a fixed library the
+     * client refuses the oversized realm and returns an error.  What must
+     * not happen is heap corruption.
+     */
+    (void) sasl_client_step(conn, challenge, challen, NULL, &out, &outlen);
+
+    /*
+     * Force glibc libc to validate the heap: on unpatched versions the
+     * overflow already corrupted allocation metadata, and this
+     * malloc()/free() cycle (or the free inside sasl_dispose() below)
+     * trips glibc's consistency checks and aborts.
+     */
+    for (i = 0; i < 64; i++) {
+       char *guard = malloc(4096);
+
+       if (guard == NULL) {
+           fprintf(stderr, "malloc failed during heap check\n");
+           goto done;
+       }
+       memset(guard, 0x41, 4096);
+       free(guard);
+    }
+
+    printf("no heap corruption detected\n");
+
+  done:
+    if (conn)
+       sasl_dispose(&conn);
+    sasl_done();
+    return EXIT_SUCCESS;
+}

Reply via email to