From db0beded03eea4a71425996e40f9d641305b09ad Mon Sep 17 00:00:00 2001
From: John Thacker <johnthacker@gmail.com>
Date: Mon, 16 Mar 2026 22:17:58 -0400
Subject: [PATCH] epan: Fix potential overflows in zlib decompression

Fix a couple of places that might overflow. (In one place, it
could lead to using an unusually small buffer.)

Also fix a potential overflow in format_text_internal when
expanding the buffer size.

Fix #21097, #21098. Cf. #13779, which is worse on processing time
even while using less memory, because of the use of x-www-form-urlencoded.

We could lower the limit more (and also in some of the other
compressors; zlib is unusual in having a pathological case of
a compression ratio over 1030:1 with, e.g., a buffer of only
zeroes.)

AI-Assisted: no
---
 epan/tvbuff_zlib.c | 25 ++++++++++++++++++++++---
 wsutil/str_util.c  |  6 +++++-
 2 files changed, 27 insertions(+), 4 deletions(-)

Index: wireshark-3.6.24/epan/tvbuff_zlib.c
===================================================================
--- wireshark-3.6.24.orig/epan/tvbuff_zlib.c
+++ wireshark-3.6.24/epan/tvbuff_zlib.c
@@ -19,12 +19,15 @@
 
 #ifdef HAVE_ZLIB
 #define ZLIB_CONST
+#define ZLIB_PREFIX(x) x
 #include <zlib.h>
 #endif
 
 #include "tvbuff.h"
 #include <wsutil/wslog.h>
 
+#define ckd_mul(res, x, y) __builtin_mul_overflow((x), (y), (res))
+
 #ifdef HAVE_ZLIB
 /*
  * Uncompresses a zlib compressed packet inside a message of tvb at offset with
@@ -38,6 +41,8 @@ tvbuff_t *
 tvb_uncompress(tvbuff_t *tvb, const int offset, int comprlen)
 {
 	gint       err;
+	/* bytes_out is an unsigned because tvb_new_real_data does not accept
+	 * more than an unsigned. */
 	guint      bytes_out      = 0;
 	guint8    *compr;
 	guint8    *uncompr        = NULL;
@@ -64,8 +69,11 @@ tvb_uncompress(tvbuff_t *tvb, const int
 	 * Assume that the uncompressed data is at least twice as big as
 	 * the compressed size.
 	 */
-	bufsiz = tvb_captured_length_remaining(tvb, offset) * 2;
-	bufsiz = CLAMP(bufsiz, TVB_Z_MIN_BUFSIZ, TVB_Z_MAX_BUFSIZ);
+	if (ckd_mul(&bufsiz, bytes_in, 2)) {
+		bufsiz = TVB_Z_MAX_BUFSIZ;
+	} else {
+		bufsiz = CLAMP(bufsiz, TVB_Z_MIN_BUFSIZ, TVB_Z_MAX_BUFSIZ);
+	}
 
 	ws_debug("bufsiz: %u bytes\n", bufsiz);
 
@@ -99,6 +107,19 @@ tvb_uncompress(tvbuff_t *tvb, const int
 		if (err == Z_OK || err == Z_STREAM_END) {
 			guint bytes_pass = bufsiz - strm->avail_out;
 
+			// This is an unsigned long, but won't overflow even
+			// on platforms where that is 32-bit, because bufsize
+			// is clamped, if we break when it reaches INT_MAX.
+			if (strm->total_out > INT_MAX) {
+				// Qt uses signed ints in various classes, so
+				// the GUI can't really handle anything
+				ws_debug("overflow, returning what we have");
+				ZLIB_PREFIX(inflateEnd)(strm);
+				g_free(strm);
+				g_free(strmbuf);
+				break;
+			}
+
 			++inflate_passes;
 
 			if (uncompr == NULL) {
@@ -124,6 +145,7 @@ tvb_uncompress(tvbuff_t *tvb, const int
 			}
 
 			bytes_out += bytes_pass;
+			g_assert(bytes_out == strm->total_out);
 
 			if (err == Z_STREAM_END) {
 				inflateEnd(strm);
@@ -300,7 +322,7 @@ tvb_uncompress(tvbuff_t *tvb, const int
 	ws_debug("bytes  in: %u\nbytes out: %u\n\n", bytes_in, bytes_out);
 
 	if (uncompr != NULL) {
-		uncompr_tvb =  tvb_new_real_data(uncompr, bytes_out, bytes_out);
+		uncompr_tvb = tvb_new_real_data(uncompr, bytes_out, bytes_out);
 		tvb_set_free_cb(uncompr_tvb, g_free);
 	}
 	wmem_free(NULL, compr);
