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; +}

