From bff7f3b9851d8f6684d0ca274a915f1a179b1ce0 Mon Sep 17 00:00:00 2001
From: John Thacker <johnthacker@gmail.com>
Date: Mon, 13 Apr 2026 16:37:58 -0400
Subject: [PATCH] WebSocket: Put some bounds on zlib decompression

WebSocket by default saves the state of the decompression sliding
window/dictionary, which is why the common functions from tvbuff_zlib
aren't used. Put similar bounds on the decompression length. Note
that if the message is truncated, zlib probably doesn't update the
sliding window when skipping to the end, so subsequent messages probably
fail too.

This is the largest maximum we can possibly support. We could lower it
more, if we feel that it is necessary to avoid DoS.

Fix #21190

AI-Assisted: no


(cherry picked from commit d655b4ebc78088a73eb0706b75cc93fe045602bc)

Co-authored-by: John Thacker <johnthacker@gmail.com>
---
 epan/dissectors/packet-websocket.c | 33 ++++++++++++++++++++++++++++--
 1 file changed, 31 insertions(+), 2 deletions(-)

Index: wireshark-3.6.24/epan/dissectors/packet-websocket.c
===================================================================
--- wireshark-3.6.24.orig/epan/dissectors/packet-websocket.c
+++ wireshark-3.6.24/epan/dissectors/packet-websocket.c
@@ -25,6 +25,7 @@
 #include "packet-tcp.h"
 
 #ifdef HAVE_ZLIB
+#define ZLIB_PREFIX(x) x
 #define ZLIB_CONST
 #include <zlib.h>
 #endif
@@ -50,6 +51,8 @@ static dissector_handle_t sip_handle;
 
 #define OPCODE_KEY 0
 
+#define ckd_mul(res, x, y) __builtin_mul_overflow((x), (y), (res))
+
 static gint  pref_text_type             = WEBSOCKET_NONE;
 static gboolean pref_decompress         = TRUE;
 
@@ -251,6 +254,15 @@ websocket_init_z_stream_context(gint8 wb
   return z_strm;
 }
 
+/* Because the stream, or at least the sliding window, has to be reused
+ * between messages, this doesn't call the functions in tvbuff_zlib.
+ * XXX - Would it make sense to provide a tvbuff_zlib API call that
+ * takes either a zlib stream pointer or a sliding window/dictionary,
+ * in order to consolidate some code? */
+/* Same constants as tvbuff_zlib.c */
+#define TVB_Z_MIN_BUFSIZ 32768
+#define TVB_Z_MAX_BUFSIZ 1048576 * 10
+
 /*
  * Decompress the given buffer using the given zlib context. On success, the
  * (possibly empty) buffer is stored as "proto data" and TRUE is returned.
@@ -260,7 +272,7 @@ static gboolean
 websocket_uncompress(tvbuff_t *tvb, packet_info *pinfo, z_streamp z_strm, tvbuff_t **uncompressed_tvb, guint32 key)
 {
   /*
-   * Decompression a message: append "0x00 0x00 0xff 0xff" to the end of
+   * Decompressing a message: append "0x00 0x00 0xff 0xff" to the end of
    * message, then apply DEFLATE to the result.
    * https://tools.ietf.org/html/rfc7692#section-7.2.2
    */
@@ -275,8 +287,11 @@ websocket_uncompress(tvbuff_t *tvb, pack
   tvb_memcpy(tvb, compr_payload, 0, compr_len-4);
   compr_payload[compr_len-4] = compr_payload[compr_len-3] = 0x00;
   compr_payload[compr_len-2] = compr_payload[compr_len-1] = 0xff;
-  decompr_buf_len = 2*compr_len;
-  decompr_buf = (guint8 *)wmem_alloc(pinfo->pool, decompr_buf_len);
+  if (ckd_mul(&decompr_buf_len, compr_len, 2)) {
+    decompr_buf_len = TVB_Z_MAX_BUFSIZ;
+  } else {
+    decompr_buf_len = CLAMP(decompr_buf_len, TVB_Z_MIN_BUFSIZ, TVB_Z_MAX_BUFSIZ);
+  }  decompr_buf = (guint8 *)wmem_alloc(pinfo->pool, decompr_buf_len);
 
   z_strm->next_in = compr_payload;
   z_strm->avail_in = compr_len;
@@ -289,6 +304,19 @@ websocket_uncompress(tvbuff_t *tvb, pack
 
     if (err == Z_OK || err == Z_STREAM_END || err == Z_BUF_ERROR) {
       guint avail_bytes = decompr_buf_len - z_strm->avail_out;
+      /* Note z_strm, and thus z_strm->total_out, does not necessarily get reset
+       * between messages because the same sliding window may be used. */
+      if (decompr_len + avail_bytes > INT_MAX) {
+        /* Out of room (various tvb and Qt API functions will fail on anything
+         * bigger than a signed int. We could lower this more. (Because the
+         * size of the decompression buffer is clamped, this can't overflow.) */
+        err = ZLIB_PREFIX(inflateSync)(z_strm);
+        /* This should succeed and find the 00 00 FF FF at the end, but if the
+         * flush point is a sync flush point and not a full flush point, i.e.
+         * the siding window needs to be used for the next message, the stream
+         * probably gets left in a bad state.  */
+        continue;
+      }
       if (avail_bytes) {
         decompr_payload = (guint8 *)wmem_realloc(wmem_file_scope(), decompr_payload,
                                                  decompr_len + avail_bytes);
