ANT-2026-27KVBTTP · wireshark/wireshark
other low
Severity Claude high · Security research firm high · Maintainer low
Discovered by Claude Mythos Preview
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
- Connect to the sharkd socket.
- 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.
- Send a follow-up request that calls sharkd_retap() — e.g. load a pcap containing RTP, or issue an iograph/follow/download/tap request.
- 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
Triage and disclosure were performed by Ada Logics.
- Verdict
- true positive
- Severity
- high
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
Recorded dates, in order.
- 2026-04-02 Discovered or logged
- 2026-07-06 Sent to maintainer
- 2026-08-12 Maintainer acknowledged
- 2026-08-12 Patch released
- 2026-09-28 Publicly revealed
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"
}