ANT-2026-27KVBTTP · wireshark/wireshark

other low

CVE-2026-76891

Severity Claude high · Security research firm high · Maintainer low

Discovered by Claude Mythos Preview

REPORT

Anthropic's analysis, sealed at approval. Disclosure to the maintainer was performed by Ada Logics.

ANT-2026-27KVBTTP: sharkd tap request leaves dangling stack-object listeners

In sharkd_session_process_tap(), a stack-local rtpstream_tapinfo_t is declared (~line 3608) and registered into the global tap listener queue at line 3865. The remove_tap_listener cleanup loop at lines ~4076-4083 only runs on normal function exit, but 14 error branches between lines 3632-4053 return early after registration when a later tapN parameter is invalid. A client sending {"tap0":"rtp-streams","tap1":"stat:bogus"} registers the stack object then bails out, leaving the global listener list pointing into a dead stack frame. Any follow-up request that triggers sharkd_retap() (follow, iograph, download, tap) invokes rtpstream_reset_cb on the stale pointer, reading a function pointer at offset 0 and calling it, then treating stale stack bytes as GHashTable/GList and destroying them — yielding a crash or control-flow hijack in the long-lived, unauthenticated daemon.

Target

Project: wireshark/wireshark
Location: sharkd_session.c:3865
Discovery: static analysis — not yet dynamically reproduced

Technical Details

The root cause is that stack-allocated tap state is registered into a global list but the unregister-cleanup loop is not on every exit path; early return statements in the tap0..tap15 processing loop skip it. Because rtpstream_tapinfo_t's first field (ui/rtp_stream.h:88) is the tap_reset function pointer, rtpstream_reset_cb (ui/tap-rtp-common.c:174-176) loads and calls a function pointer straight from reclaimed stack memory, then rtpstream_reset() (ui/tap-rtp-common.c:149-165) dereferences further stale bytes as glib container pointers.

Reproduction

  1. Connect to the sharkd socket.
  2. Send {"req":"tap","tap0":"rtp-streams","tap1":"stat:bogus"} (or follow:bogus) so tap0 registers the stack-local rtp_tapinfo and tap1 hits an early-return error branch before the cleanup loop.
  3. Send a follow-up request that calls sharkd_retap() — e.g. load a pcap containing RTP, or issue an iograph/follow/download/tap request.
  4. reset_tap_listeners() invokes rtpstream_reset_cb on the dangling stack pointer, reading and calling ti->tap_reset from stale stack memory.

[No reproducer or sanitizer output attached — request from security-cvd@anthropic.com if needed.]

Suggested Fix

Ensure tap listeners are removed on every exit path: route all error branches through a common cleanup label (goto cleanup) that calls remove_tap_listener for each registered tap, or stop registering stack-allocated objects as global listeners (heap-allocate and track them for unconditional teardown).

Acknowledgement

This vulnerability was discovered by Claude, Anthropic's AI assistant, and triaged by the Anthropic security team in collaboration with Anthropic Research. Please direct questions to security-cvd@anthropic.com and reference ANT-2026-27KVBTTP.


Reference: ANT-2026-27KVBTTP
Anthropic CVD Policy: https://www.anthropic.com/coordinated-vulnerability-disclosure

SECURITY RESEARCH FIRM ANALYSIS

Triage and disclosure were performed by Ada Logics.

Verdict
true positive
Severity
high
UPSTREAM FIX

The change that resolved this finding.

diff --git a/sharkd_session.c b/sharkd_session.c
index 0577ff418fb..610967eef7d 100644
--- a/sharkd_session.c
+++ b/sharkd_session.c
@@ -3572,488 +3572,516 @@ sharkd_session_eo_register_tap_listener(register_eo_t *eo, const char *tap_type,
     return register_tap_listener(get_eo_tap_listener_name(eo), eo_object, tap_filter, 0, NULL, get_eo_packet_func(eo), tap_draw, NULL);
 }
 
-/**
- * sharkd_session_process_tap()
- *
- * Process tap request
- *
- * Input:
- *   (m) tap0         - First tap request
- *   (o) tap1...tap15 - Other tap requests
- *
- * Output object with attributes:
- *   (m) taps  - array of object with attributes:
- *                  (m) tap  - tap name
- *                  (m) type - tap output type
- *                  ...
- *                  for type:stats see sharkd_session_process_tap_stats_cb()
- *                  for type:nstat see sharkd_session_process_tap_nstat_cb()
- *                  for type:conv see sharkd_session_process_tap_conv_cb()
- *                  for type:host see sharkd_session_process_tap_conv_cb()
- *                  for type:rtp-streams see sharkd_session_process_tap_rtp_cb()
- *                  for type:rtp-analyse see sharkd_session_process_tap_rtp_analyse_cb()
- *                  for type:eo see sharkd_session_process_tap_eo_cb()
- *                  for type:expert see sharkd_session_process_tap_expert_cb()
- *                  for type:rtd see sharkd_session_process_tap_rtd_cb()
- *                  for type:srt see sharkd_session_process_tap_srt_cb()
- *                  for type:flow see sharkd_session_process_tap_flow_cb()
- *
- *   (m) err   - error code
- */
 static void
-sharkd_session_process_tap(char *buf, const jsmntok_t *tokens, int count)
+rtpstream_free_cb(void *data)
 {
-    void *taps_data[16];
-    GFreeFunc taps_free[16];
-    int taps_count = 0;
-    int i;
-    const char *tap_filter = json_find_attr(buf, tokens, count, "filter");
-
-    rtpstream_tapinfo_t rtp_tapinfo =
-    { NULL, NULL, NULL, NULL, 0, NULL, NULL, 0, TAP_ANALYSE, NULL, NULL, NULL, false, false};
+    rtpstream_tapinfo_t *rtp_tapinfo = (rtpstream_tapinfo_t*)data;
 
-    for (i = 0; i < 16; i++)
-    {
-        char tapbuf[32];
-        const char *tok_tap;
+    rtpstream_reset(rtp_tapinfo);
+    g_free(rtp_tapinfo);
+}
 
-        void *tap_data = NULL;
-        GFreeFunc tap_free = NULL;
-        GString *tap_error = NULL;
+static bool
+sharkd_session_register_tap(const char *tok_tap, const char *tap_filter, void **tap_datap, GFreeFunc *tap_freep)
+{
+    void *tap_data = NULL;
+    GFreeFunc tap_free = NULL;
+    GString *tap_error = NULL;
 
-        snprintf(tapbuf, sizeof(tapbuf), "tap%d", i);
-        tok_tap = json_find_attr(buf, tokens, count, tapbuf);
-        if (!tok_tap)
-            break;
+    if (!strncmp(tok_tap, "stat:", 5))
+    {
+        stats_tree_cfg *cfg = stats_tree_get_cfg_by_abbr(tok_tap + 5);
+        stats_tree *st;
 
-        if (!strncmp(tok_tap, "stat:", 5))
+        if (!cfg)
         {
-            stats_tree_cfg *cfg = stats_tree_get_cfg_by_abbr(tok_tap + 5);
-            stats_tree *st;
+            sharkd_json_error(
+                    rpcid, -11001, NULL,
+                    "sharkd_session_process_tap() stat %s not found", tok_tap + 5
+                    );
+            return false;
+        }
 
-            if (!cfg)
-            {
-                sharkd_json_error(
-                        rpcid, -11001, NULL,
-                        "sharkd_session_process_tap() stat %s not found", tok_tap + 5
-                        );
-                return;
-            }
+        st = stats_tree_new(cfg, NULL, tap_filter);
 
-            st = stats_tree_new(cfg, NULL, tap_filter);
+        tap_error = register_tap_listener(st->cfg->tapname, st, st->filter, st->cfg->flags, stats_tree_reset, stats_tree_packet, sharkd_session_process_tap_stats_cb, NULL);
 
-            tap_error = register_tap_listener(st->cfg->tapname, st, st->filter, st->cfg->flags, stats_tree_reset, stats_tree_packet, sharkd_session_process_tap_stats_cb, NULL);
+        if (!tap_error && cfg->init)
+            cfg->init(st);
 
-            if (!tap_error && cfg->init)
-                cfg->init(st);
+        tap_data = st;
+        tap_free = sharkd_session_free_tap_stats_cb;
+    }
+    else if (!strcmp(tok_tap, "expert"))
+    {
+        struct sharkd_expert_tap *expert_tap;
 
-            tap_data = st;
-            tap_free = sharkd_session_free_tap_stats_cb;
-        }
-        else if (!strcmp(tok_tap, "expert"))
-        {
-            struct sharkd_expert_tap *expert_tap;
+        expert_tap = g_new0(struct sharkd_expert_tap, 1);
+        expert_tap->text = g_string_chunk_new(100);
 
-            expert_tap = g_new0(struct sharkd_expert_tap, 1);
-            expert_tap->text = g_string_chunk_new(100);
+        tap_error = register_tap_listener("expert", expert_tap, tap_filter, 0, NULL, sharkd_session_packet_tap_expert_cb, sharkd_session_process_tap_expert_cb, NULL);
 
-            tap_error = register_tap_listener("expert", expert_tap, tap_filter, 0, NULL, sharkd_session_packet_tap_expert_cb, sharkd_session_process_tap_expert_cb, NULL);
+        tap_data = expert_tap;
+        tap_free = sharkd_session_free_tap_expert_cb;
+    }
+    else if (!strncmp(tok_tap, "seqa:", 5))
+    {
+        seq_analysis_info_t *graph_analysis;
+        register_analysis_t *analysis;
+        const char *tap_name;
+        tap_packet_cb tap_func;
+        unsigned tap_flags;
 
-            tap_data = expert_tap;
-            tap_free = sharkd_session_free_tap_expert_cb;
-        }
-        else if (!strncmp(tok_tap, "seqa:", 5))
+        analysis = sequence_analysis_find_by_name(tok_tap + 5);
+        if (!analysis)
         {
-            seq_analysis_info_t *graph_analysis;
-            register_analysis_t *analysis;
-            const char *tap_name;
-            tap_packet_cb tap_func;
-            unsigned tap_flags;
-
-            analysis = sequence_analysis_find_by_name(tok_tap + 5);
-            if (!analysis)
-            {
-                sharkd_json_error(
-                        rpcid, -11002, NULL,
-                        "sharkd_session_process_tap() seq analysis %s not found", tok_tap + 5
-                        );
-                return;
-            }
+            sharkd_json_error(
+                    rpcid, -11002, NULL,
+                    "sharkd_session_process_tap() seq analysis %s not found", tok_tap + 5
+                    );
+            return false;
+        }
 
-            graph_analysis = sequence_analysis_info_new();
-            graph_analysis->name = tok_tap + 5;
-            /* TODO, make configurable */
-            graph_analysis->any_addr = false;
+        graph_analysis = sequence_analysis_info_new();
+        graph_analysis->name = tok_tap + 5;
+        /* TODO, make configurable */
+        graph_analysis->any_addr = false;
 
-            tap_name  = sequence_analysis_get_tap_listener_name(analysis);
-            tap_flags = sequence_analysis_get_tap_flags(analysis);
-            tap_func  = sequence_analysis_get_packet_func(analysis);
+        tap_name  = sequence_analysis_get_tap_listener_name(analysis);
+        tap_flags = sequence_analysis_get_tap_flags(analysis);
+        tap_func  = sequence_analysis_get_packet_func(analysis);
 
-            tap_error = register_tap_listener(tap_name, graph_analysis, tap_filter, tap_flags, NULL, tap_func, sharkd_session_process_tap_flow_cb, NULL);
+        tap_error = register_tap_listener(tap_name, graph_analysis, tap_filter, tap_flags, NULL, tap_func, sharkd_session_process_tap_flow_cb, NULL);
 
-            tap_data = graph_analysis;
-            tap_free = sharkd_session_free_tap_flow_cb;
-        }
-        else if (!strncmp(tok_tap, "conv:", 5) || !strncmp(tok_tap, "endpt:", 6))
+        tap_data = graph_analysis;
+        tap_free = sharkd_session_free_tap_flow_cb;
+    }
+    else if (!strncmp(tok_tap, "conv:", 5) || !strncmp(tok_tap, "endpt:", 6))
+    {
+        struct register_ct *ct = NULL;
+        const char *ct_tapname;
+        struct sharkd_conv_tap_data *ct_data;
+        tap_packet_cb tap_func = 
… (truncated)

https://github.com/wireshark/wireshark/commit/178939f126d5eeb442d96e10b0ea4c1cfc6920e9

TIMELINE

Recorded dates, in order.

  1. 2026-04-02 Discovered or logged
  2. 2026-07-06 Sent to maintainer
  3. 2026-08-12 Maintainer acknowledged
  4. 2026-08-12 Patch released
  5. 2026-09-28 Publicly revealed
PROVENANCE

SHA-3-512 hash:

6ed8764aad5fa25e5525477fd786fbf8d794d34771bc0f74ac4138bd7696d6df8346a2f218b9a8cecbfbf97e2505eb75392ac5b08d3c66090de68db8b96a70c7

Committed 2026-07-22 07:34 UTC

Revealed 2026-09-28 20:36 UTC

Verify (download preimage.json)

Show preimage JSON
{
  "ant_id": "ANT-2026-27KVBTTP",
  "bug_class": "Use-After-Return / Memory Corruption",
  "claude_severity": "high",
  "commit_sha": null,
  "created_at": "2026-04-16T14:11:16+00:00",
  "description": "In sharkd_session_process_tap(), a stack-local rtpstream_tapinfo_t is declared (~line 3608) and registered into the global tap listener queue at line 3865. The remove_tap_listener cleanup loop at lines ~4076-4083 only runs on normal function exit, but 14 error branches between lines 3632-4053 return early after registration when a later tapN parameter is invalid. A client sending {\"tap0\":\"rtp-streams\",\"tap1\":\"stat:bogus\"} registers the stack object then bails out, leaving the global listener list pointing into a dead stack frame. Any follow-up request that triggers sharkd_retap() (follow, iograph, download, tap) invokes rtpstream_reset_cb on the stale pointer, reading a function pointer at offset 0 and calling it, then treating stale stack bytes as GHashTable*/GList* and destroying them — yielding a crash or control-flow hijack in the long-lived, unauthenticated daemon.",
  "discovered_at": "2026-04-02T00:00:00+00:00",
  "location": "sharkd_session.c:3865",
  "poc_sha256": null,
  "preimage_version": 1,
  "project": "wireshark/wireshark",
  "reproduction": [
    "1. Connect to the sharkd socket.",
    "2. Send {\"req\":\"tap\",\"tap0\":\"rtp-streams\",\"tap1\":\"stat:bogus\"} (or follow:bogus) so tap0 registers the stack-local rtp_tapinfo and tap1 hits an early-return error branch before the cleanup loop.",
    "3. Send a follow-up request that calls sharkd_retap() — e.g. load a pcap containing RTP, or issue an iograph/follow/download/tap request.",
    "4. reset_tap_listeners() invokes rtpstream_reset_cb on the dangling stack pointer, reading and calling ti->tap_reset from stale stack memory."
  ],
  "technical_details": "The root cause is that stack-allocated tap state is registered into a global list but the unregister-cleanup loop is not on every exit path; early `return` statements in the tap0..tap15 processing loop skip it. Because rtpstream_tapinfo_t's first field (ui/rtp_stream.h:88) is the tap_reset function pointer, rtpstream_reset_cb (ui/tap-rtp-common.c:174-176) loads and calls a function pointer straight from reclaimed stack memory, then rtpstream_reset() (ui/tap-rtp-common.c:149-165) dereferences further stale bytes as glib container pointers.",
  "title": "sharkd tap request leaves dangling stack-object listeners",
  "vendor_severity": "high"
}