From 427a5ed7048dda4d22f13c164a3a439e68604406 Mon Sep 17 00:00:00 2001 From: Mike Gorse Date: Thu, 8 Jan 2026 16:19:37 -0600 Subject: [PATCH] soup-auth-ntlm: Reject excessively long passwords According to https://learn.microsoft.com/en-us/troubleshoot/windows-server/windows-security/ntlm-user-authentication, the practical limit for a NTLM password is 128 Unicode characters, so it should be safe to reject passwords longer than 256 bytes. Previously, md4sum could overflow and cause an out-of-bounds memory access if an extremely long password was provided. Also update md4sum to use unsigned variables for size-related calculations, as a precaution. This is CVE-2026-0719. Closes #477. --- libsoup/auth/soup-auth-ntlm.c | 27 +++++++++++---- tests/ntlm-test.c | 64 +++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 7 deletions(-) diff --git a/libsoup/auth/soup-auth-ntlm.c b/libsoup/auth/soup-auth-ntlm.c index dc440ad1..a338389b 100644 --- a/libsoup/auth/soup-auth-ntlm.c +++ b/libsoup/auth/soup-auth-ntlm.c @@ -355,6 +355,14 @@ soup_auth_ntlm_update_connection (SoupConnectionAuth *auth, SoupMessage *msg, return FALSE; } + if (priv->password_state == SOUP_NTLM_PASSWORD_PROVIDED && !priv->nt_hash[0]) { + /* This can happen if an excessively long password was + * provided, in which case we don't try to hash */ + conn->state = SOUP_NTLM_FAILED; + priv->password_state = SOUP_NTLM_PASSWORD_REJECTED; + return TRUE; + } + if (!soup_ntlm_parse_challenge (auth_header + 5, &conn->nonce, priv->domain ? NULL : &priv->domain, &conn->ntlmv2_session, &conn->negotiate_target, @@ -449,8 +457,10 @@ soup_auth_ntlm_authenticate (SoupAuth *auth, const char *username, priv->username = g_strdup (username); } - soup_ntlm_nt_hash (password, priv->nt_hash); - soup_ntlm_lanmanager_hash (password, priv->lm_hash); + if (strlen (password) < 256) { + soup_ntlm_nt_hash (password, priv->nt_hash); + soup_ntlm_lanmanager_hash (password, priv->lm_hash); + } priv->password_state = SOUP_NTLM_PASSWORD_PROVIDED; } @@ -616,7 +626,7 @@ soup_auth_ntlm_class_init (SoupAuthNTLMClass *auth_ntlm_class) } static void md4sum (const unsigned char *in, - int nbytes, + size_t nbytes, unsigned char digest[16]); typedef guint32 DES_KS[16][2]; /* Single-key DES key schedule */ @@ -662,7 +672,7 @@ soup_ntlm_nt_hash (const char *password, guchar hash[21]) { unsigned char *buf, *p; - p = buf = g_malloc (strlen (password) * 2); + p = buf = g_malloc_n (strlen (password), 2); while (*password) { *p++ = *password++; @@ -1104,15 +1114,16 @@ calc_response (const guchar *key, const guchar *plaintext, guchar *results) #define ROT(val, n) ( ((val) << (n)) | ((val) >> (32 - (n))) ) static void -md4sum (const unsigned char *in, int nbytes, unsigned char digest[16]) +md4sum (const unsigned char *in, size_t nbytes, unsigned char digest[16]) { unsigned char *M; guint32 A, B, C, D, AA, BB, CC, DD, X[16]; - int pbytes, nbits = nbytes * 8, i, j; + size_t pbytes, nbits = nbytes * 8; + int i, j; /* There is *always* padding of at least one bit. */ pbytes = ((119 - (nbytes % 64)) % 64) + 1; - M = alloca (nbytes + pbytes + 8); + M = g_malloc (nbytes + pbytes + 8); memcpy (M, in, nbytes); memset (M + nbytes, 0, pbytes + 8); M[nbytes] = 0x80; @@ -1212,6 +1223,8 @@ md4sum (const unsigned char *in, int nbytes, unsigned char digest[16]) digest[13] = (D >> 8) & 0xFF; digest[14] = (D >> 16) & 0xFF; digest[15] = (D >> 24) & 0xFF; + + g_free (M); } diff --git a/tests/ntlm-test.c b/tests/ntlm-test.c index e19f5663..c95fcd50 100644 --- a/tests/ntlm-test.c +++ b/tests/ntlm-test.c @@ -740,6 +740,67 @@ do_retrying_test (TestServer *ts, soup_test_session_abort_unref (session); } +static gboolean +long_password_test_authenticate (SoupMessage *msg, + SoupAuth *auth, + gboolean retrying, + gpointer user) +{ + size_t l = 65536; + char *password; + char tmp[10000]; + size_t i; + + password = (char *)g_malloc (l); + + for (i = 0; i < 10000; i++) { + tmp[i] = 'A'; + } + for (i = 0; i < l/10000; i++) { + memcpy (password + i * 10000, tmp, 10000); + } + memcpy (password + l - 1 - 10000, tmp, 10000); + + soup_auth_authenticate (auth, "alice", password); + + g_free (password); + return TRUE; +} + +static void +do_long_password_test (TestServer *ts, + gconstpointer data) +{ + SoupSession *session; + SoupMessage *msg; + GUri *uri; + GBytes *body; + + if (!can_do_ntlm_test ()) { + g_test_skip ("NTLM authentication not available (likely due to FIPS mode)"); + return; + } + + session = soup_test_session_new (NULL); + soup_session_add_feature_by_type (session, SOUP_TYPE_AUTH_NTLM); + soup_session_set_proxy_resolver(session, NULL); + + uri = g_uri_parse_relative (ts->uri, "/alice", SOUP_HTTP_URI_FLAGS, NULL); + msg = soup_message_new_from_uri ("GET", uri); + g_signal_connect (msg, "authenticate", + G_CALLBACK (long_password_test_authenticate), NULL); + g_uri_unref (uri); + + body = soup_session_send_and_read (session, msg, NULL, NULL); + + soup_test_assert_message_status (msg, SOUP_STATUS_UNAUTHORIZED); + + g_bytes_unref (body); + g_object_unref (msg); + + soup_test_session_abort_unref (session); +} + int main (int argc, char **argv) { @@ -763,6 +824,9 @@ main (int argc, char **argv) g_test_add ("/ntlm/retry", TestServer, NULL, setup_server, do_retrying_test, teardown_server); + g_test_add ("/ntlm/long-password", TestServer, NULL, + setup_server, do_long_password_test, teardown_server); + ret = g_test_run (); test_cleanup (); -- 2.52.0