ANT-2026-WT5AMKP5 · wireshark/wireshark

stack-buffer-overflow medium

CVE-2026-15166 GHSA-h9wc-3j4p-q5g3

Severity Claude high · Security research firm high · Maintainer medium

Discovered by Claude Mythos Preview

REPORT

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

ANT-2026-WT5AMKP5: 802.11 EAPOL key-data decryption stack buffer overflow

In Wireshark's 802.11 decryption code, Dot11DecryptDecryptKeyData() declares a 1024-byte stack array decrypted_data[] and, on the RC4 key-wrap path, calls memcpy(decrypted_data, data, key_bytes_len). key_bytes_len is bounded only by eapol_parsed->len - 95, where eapol_parsed->len is the raw attacker-controlled 16-bit EAPOL length field. The existing DOT11DECRYPT_EAPOL_MAX_LEN checks at dot11decrypt.c:861 and :1618-1620 are on different code paths and do not protect the try_decrypt_keydata → Dot11DecryptDecryptKeyData path. An attacker who injects a spoofed EAPOL-Key message 3 with a ~2000–4000 byte Key Data Length (or supplies a malicious pcap) causes ~900+ attacker-influenced bytes to overwrite the stack frame of try_decrypt_keydata, yielding a crash on canary-protected builds and potential RCE otherwise.

Target

Project: wireshark/wireshark
Location: epan/crypt/dot11decrypt.c:485
Discovery: static analysis — not yet dynamically reproduced

Technical Details

The root cause is a missing bounds check: key_bytes_len is derived from the packet's EAPOL Key Data Length / EAPOL length fields and is never compared against sizeof(decrypted_data) (1024) before the memcpy at dot11decrypt.c:477. The RC4 branch (dot11decrypt.c:458-478) has no integrity check and always returns, so once a valid SA exists the overflowing memcpy is reached unconditionally; the AES-unwrap path uses the same undersized output buffer.

Reproduction

  1. Ensure Wireshark has observed/established an SA for the target BSSID/STA (e.g., deauth the client to force a fresh 4-way handshake).
  2. Craft an EAPOL-Key message 3 with key_version=1 (RC4) and a Key Data Length / EAPOL length of ~2000–4000 bytes, padding the body accordingly.
  3. Transmit the frame (or embed it in a pcap opened by the analyst).
  4. packet-ieee80211.c:43016 calls try_decrypt_keydata → Dot11DecryptDecryptKeyData(), which memcpy()s the oversized key data into the 1024-byte stack array, overwriting saved registers/return address.

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

Suggested Fix

Before any copy or in-place decrypt, bound key_bytes_len by both sizeof(decrypted_data) and the actual captured key-data length, rejecting frames that exceed the buffer.

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-WT5AMKP5.


Reference: ANT-2026-WT5AMKP5
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/epan/crypt/dot11decrypt.c b/epan/crypt/dot11decrypt.c
index 76dbbdd6251..d37c506de38 100644
--- a/epan/crypt/dot11decrypt.c
+++ b/epan/crypt/dot11decrypt.c
@@ -435,6 +435,11 @@ Dot11DecryptDecryptKeyData(PDOT11DECRYPT_CONTEXT ctx,
         }
     }
 
+    if (key_bytes_len > *decrypted_len) {
+        ws_debug("Too large EAPOL key data");
+        return DOT11DECRYPT_RET_UNSUCCESS;
+    }
+
     if ((key_bytes_len < GROUP_KEY_MIN_LEN) ||
         (eapol_parsed->len < EAPOL_RSN_KEY_LEN) ||
         (key_bytes_len > eapol_parsed->len - EAPOL_RSN_KEY_LEN)) {
diff --git a/epan/dissectors/packet-ieee80211.c b/epan/dissectors/packet-ieee80211.c
index e4145dd240f..a34850d4510 100644
--- a/epan/dissectors/packet-ieee80211.c
+++ b/epan/dissectors/packet-ieee80211.c
@@ -42652,29 +42652,38 @@ keydata_padding_len(tvbuff_t *tvb)
   return 0;
 }
 
-static void
+static bool
 get_eapol_parsed(packet_info *pinfo, PDOT11DECRYPT_EAPOL_PARSED eapol_parsed)
 {
   if (!eapol_parsed) {
-    return;
+    return false;
   }
 
   proto_eapol_key_frame_t *eapol_key =
     (proto_eapol_key_frame_t *)p_get_proto_data(pinfo->pool, pinfo, proto_eapol,
                                                 EAPOL_KEY_FRAME_KEY);
   if (!eapol_key) {
-    return;
+    return false;
   }
   eapol_parsed->len = eapol_key->len;
+  if (eapol_parsed->len > DOT11DECRYPT_EAPOL_MAX_LEN) {
+    return false;
+  }
   eapol_parsed->key_type = eapol_key->type;
   eapol_parsed->key_version = (uint8_t)
     GPOINTER_TO_UINT(p_get_proto_data(pinfo->pool, pinfo, proto_wlan, KEY_VERSION_KEY));
   eapol_parsed->key_len = (uint16_t)
     GPOINTER_TO_UINT(p_get_proto_data(pinfo->pool, pinfo, proto_wlan, KEY_LEN_KEY));
+  if (eapol_parsed->key_len > DOT11DECRYPT_EAPOL_MAX_LEN) {
+    return false;
+  }
   eapol_parsed->key_iv = (uint8_t *)p_get_proto_data(pinfo->pool, pinfo, proto_wlan, KEY_IV_KEY);
   eapol_parsed->key_data = (uint8_t *)p_get_proto_data(pinfo->pool, pinfo, proto_wlan, KEY_DATA_KEY);
   eapol_parsed->key_data_len = (uint16_t)
     GPOINTER_TO_UINT(p_get_proto_data(pinfo->pool, pinfo, proto_wlan, KEY_DATA_LEN_KEY));
+  if (eapol_parsed->key_data_len > DOT11DECRYPT_EAPOL_MAX_LEN) {
+    return false;
+  }
   eapol_parsed->nonce = (uint8_t *)p_get_proto_data(pinfo->pool, pinfo, proto_wlan, NONCE_KEY);
   eapol_parsed->group_cipher = (uint8_t)
     GPOINTER_TO_UINT(p_get_proto_data(pinfo->pool, pinfo, proto_wlan, GROUP_CIPHER_KEY));
@@ -42713,6 +42722,7 @@ get_eapol_parsed(packet_info *pinfo, PDOT11DECRYPT_EAPOL_PARSED eapol_parsed)
     (uint8_t *)p_get_proto_data(pinfo->pool, pinfo, proto_wlan, FTE_R1KH_ID_KEY);
   eapol_parsed->fte.r1kh_id_len = (uint8_t)
     GPOINTER_TO_UINT(p_get_proto_data(pinfo->pool, pinfo, proto_wlan, FTE_R1KH_ID_LEN_KEY));
+  return true;
 }
 
 static void
@@ -42764,7 +42774,7 @@ get_assoc_parsed(packet_info *pinfo, PDOT11DECRYPT_ASSOC_PARSED assoc_parsed)
 static void
 try_decrypt_keydata(packet_info *pinfo)
 {
-  uint32_t dec_caplen;
+  uint32_t dec_caplen = DOT11DECRYPT_EAPOL_MAX_LEN;
   unsigned char dec_data[DOT11DECRYPT_EAPOL_MAX_LEN];
   DOT11DECRYPT_EAPOL_PARSED eapol_parsed;
   DOT11DECRYPT_KEY_ITEM used_key;
@@ -42780,7 +42790,8 @@ try_decrypt_keydata(packet_info *pinfo)
   }
 
   memset(&eapol_parsed, 0, sizeof(eapol_parsed));
-  get_eapol_parsed(pinfo, &eapol_parsed);
+  if (!get_eapol_parsed(pinfo, &eapol_parsed))
+    return;
 
   int ret = Dot11DecryptDecryptKeyData(&dot11decrypt_ctx,
                                         &eapol_parsed,
@@ -42818,7 +42829,8 @@ try_scan_eapol_keys(packet_info *pinfo, DOT11DECRYPT_HS_MSG_TYPE msg_type)
   }
 
   memset(&eapol_parsed, 0, sizeof(eapol_parsed));
-  get_eapol_parsed(pinfo, &eapol_parsed);
+  if (!get_eapol_parsed(pinfo, &eapol_parsed))
+    return;
   eapol_parsed.msg_type = msg_type;
 
   Dot11DecryptScanEapolForKeys(&dot11decrypt_ctx,

https://github.com/wireshark/wireshark/commit/68a91452afc0a68b37314efa969b9232e1ec6711

TIMELINE

Recorded dates, in order.

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

SHA-3-512 hash:

c8ad7e4ebd888e9061b505bb8b0d77ea9cb23d23e64056eb0e4e1dc5e43146305563063b81889c7ad80959944285ef4443214c48485b52aa651cba7249c1eee2

Committed 2026-07-22 07:34 UTC

Revealed 2026-09-28 20:34 UTC

Verify (download preimage.json)

Show preimage JSON
{
  "ant_id": "ANT-2026-WT5AMKP5",
  "bug_class": "Stack Buffer Overflow",
  "claude_severity": "high",
  "commit_sha": null,
  "created_at": "2026-04-16T14:11:10+00:00",
  "description": "In Wireshark's 802.11 decryption code, Dot11DecryptDecryptKeyData() declares a 1024-byte stack array decrypted_data[] and, on the RC4 key-wrap path, calls memcpy(decrypted_data, data, key_bytes_len). key_bytes_len is bounded only by eapol_parsed->len - 95, where eapol_parsed->len is the raw attacker-controlled 16-bit EAPOL length field. The existing DOT11DECRYPT_EAPOL_MAX_LEN checks at dot11decrypt.c:861 and :1618-1620 are on different code paths and do not protect the try_decrypt_keydata → Dot11DecryptDecryptKeyData path. An attacker who injects a spoofed EAPOL-Key message 3 with a ~2000–4000 byte Key Data Length (or supplies a malicious pcap) causes ~900+ attacker-influenced bytes to overwrite the stack frame of try_decrypt_keydata, yielding a crash on canary-protected builds and potential RCE otherwise.",
  "discovered_at": "2026-04-02T00:00:00+00:00",
  "location": "epan/crypt/dot11decrypt.c:485",
  "poc_sha256": null,
  "preimage_version": 1,
  "project": "wireshark/wireshark",
  "reproduction": [
    "1. Ensure Wireshark has observed/established an SA for the target BSSID/STA (e.g., deauth the client to force a fresh 4-way handshake).",
    "2. Craft an EAPOL-Key message 3 with key_version=1 (RC4) and a Key Data Length / EAPOL length of ~2000–4000 bytes, padding the body accordingly.",
    "3. Transmit the frame (or embed it in a pcap opened by the analyst).",
    "4. packet-ieee80211.c:43016 calls try_decrypt_keydata → Dot11DecryptDecryptKeyData(), which memcpy()s the oversized key data into the 1024-byte stack array, overwriting saved registers/return address."
  ],
  "technical_details": "The root cause is a missing bounds check: key_bytes_len is derived from the packet's EAPOL Key Data Length / EAPOL length fields and is never compared against sizeof(decrypted_data) (1024) before the memcpy at dot11decrypt.c:477. The RC4 branch (dot11decrypt.c:458-478) has no integrity check and always returns, so once a valid SA exists the overflowing memcpy is reached unconditionally; the AES-unwrap path uses the same undersized output buffer.",
  "title": "802.11 EAPOL key-data decryption stack buffer overflow",
  "vendor_severity": "high"
}