Skip to content

Fix seven memory-safety bugs reachable from network traffic (DREAM report) - #1330

Open
eaescob wants to merge 1 commit into
masterfrom
security/dream-2026-hardening
Open

eaescob wants to merge 1 commit into
masterfrom
security/dream-2026-hardening

Conversation

@eaescob

@eaescob eaescob commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

Fixes seven memory-safety bugs that a malicious network peer can reach while ettercap is monitoring traffic. All seven were reported by the DREAM Security Research Team against v0.8.4.1 (3548c10) and validated against master.

Vulnerability discovered by: Adiel Sol, Arad Inbar, Erez Cohen, Nir Somech, Ben Grinberg, Daniel Lubel, Shir Sadon (DREAM Security Research Team)

Each has a draft advisory and the GHSA id is cited in a comment at every fix site. CVE ids have been requested from GitHub and are not assigned yet — I'll amend this description once they land.

Advisory CVE Bug File
GHSA-f3j5-rhhr-85j5 pending Use-after-free in func_pcre src/ec_filter.c
GHSA-vrjv-f8gv-2h35 pending Heap overflow in gg_get_version src/dissectors/ec_gg.c
GHSA-v48w-7cvh-c7f4 pending SNI heap over-read src/ec_sslwrap.c
GHSA-hq2w-pg4m-6w63 pending OOB read + size_t underflow in sslw_remove_sts src/ec_sslwrap.c
GHSA-j37f-7jx3-rf8j pending size_t underflow in ccmp_decrypt src/ec_encryption_ccmp.c
GHSA-jw9q-j2rm-94j2 pending NTLM heap over-read src/dissectors/ec_http.c
GHSA-w5ch-7c4x-crcc pending Telnet stack underflow src/dissectors/ec_telnet.c

What each fix does

func_pcre use-after-free — pcre2_match_data_free() ran at ec_filter.c:593, then pcre2_get_ovector_pointer() read the freed block at line 625 and the resulting pointer drove the SAFE_CALLOC size, the marker indices, both memcpy offsets into the pcap buffer, and po->DATA.len. The SAFE_CALLOC on the very next line can recycle that block. The offsets are now snapshotted before the match data is released, mirroring how the pcre1 path already used a stack ovec[]. Also frees the match data on the no-match paths of both level 5 and level 6, which leaked. Note this is the default build path wherever pcre2 is installed (EttercapLibCheck.cmake:269).

gg_get_version heap overflow — sprintf(str,"unknown (0x%X)",version) plus both conditional strcats produced 44 bytes into a 30-byte allocation. Both helpers now compose into a private buffer and hand back only a bounded copy. While here: user[10] was one byte short for a u_int32 uin and pass[40] was one short for five %X, and the short-packet guards returned without freeing the three scratch buffers.

SNI over-read — val_len came off the wire and went straight into strndup(). The client_hello callback runs before OpenSSL validates extension bodies, so nothing upstream constrained it; it read up to 64KB past the buffer and the result was handed to SSL_set_tlsext_host_name() for the upstream connection.

sslw_remove_sts — two defects. The second memmem used the length of the whole packet from an already-advanced pointer, so it scanned past the buffer end; and its result was never NULL-checked, so h_end became 0x2, header_length underflowed, and memcpy got a near-SIZE_MAX length. len itself deliberately still describes the whole packet, since the reconstruction below is sized from it.

ccmp_decrypt — len -= WPA_CCMP_TRAILER wrapped to ~SIZE_MAX for frames under 8 bytes and the loop then wrote AES blocks up the stack. Guarded in both the outer and inner function, and the UINT16_MAX check moved above the VLA it was supposed to bound.

NTLM parser — lmResponse.offset, ntResponse.offset and uUser.offset were unchecked 32-bit values added to the decoded message base, so a peer could make us read 24 bytes from anywhere in a 4GB window and hex-encode it into the password we print and log. There was no minimum-size validation at all: base64decode() sizes its output from the input string, so even msgType at offset 8 was out of bounds for a short blob.

Telnet — backspace handling decremented p with no lower bound. The isprint() check only guards the first saved character and is bypassed after one round trip, because the loop stored the un-erased string back into s->data. Bounding p by str also fixes that, so backspaces are no longer carried forward.

A note on the two reported double-frees

The submission described findings 3 and 4 as double-frees. I could not reproduce that. ae->hostname is freed exactly once and SAFE_FREE() NULLs as it frees. The real defects are the over-read (3) and the OOB read + NULL-deref + size_t underflow (4), and that is what the advisories and these fixes describe.

Per maintainer request the SNI path still got a defensive guard: the callback fires twice per handshake and used to overwrite ae->hostname without freeing, so it now releases any previous value before reassigning. That closes the leak and makes a double free unreachable on every path that reaches it.

Verification

  • End-to-end ASan repro of the func_pcre UAF. With a pcre_regex(DATA.data, "(hello) (world)", "$2-$1") filter and a crafted pcap, stock master gives a trace matching the reporter's byte for byte:
    ERROR: AddressSanitizer: heap-use-after-free
    READ of size 8 at ... thread T2
      #0 func_pcre       src/ec_filter.c:627
      #1 execute_func    src/ec_filter.c:206
      #2 filter_engine   src/ec_filter.c:125
      #3 filter_packet   src/ec_filter.c:181
      #4 decode_data     src/ec_decode.c:316
      #5 decode_tcp      src/protocols/ec_tcp.c:295
    
    After the fix the filter still fires and substitutes, with no ASan report.
  • Extracted ASan harnesses confirm the gg (WRITE of size 13 past a 30-byte region), ccmp (len=3 → 0xFFFFFFFFFFFFFFFB) and telnet (dynamic-stack-buffer-overflow) bugs before, and clean runs after.
  • Existing ctest suite passes; no new compiler warnings.

Not addressed here

Two things surfaced during triage that are out of scope for a security fix:

  1. ef_syntax.l:44 defines FUNCTION [a-z_]+\([^)]*\), which stops at the first ). Any regex containing a paren fails to compile — including the substitution example we ship in share/etter.filter.pcre. I relaxed it locally to build the test filter above and reverted it; worth a separate PR.
  2. wpa_ccmp_decrypt() reads the MIC from data + len, i.e. past the length it was given. That is the existing caller contract and I left it alone, but it deserves a look.

🤖 Generated with Claude Code

https://claude.ai/code/session_01U7DPkxps63uUMetZEJSTLr

All seven were reported by the DREAM Security Research Team against v0.8.4.1
(3548c10) and validated against master.

Vulnerability discovered by: Adiel Sol, Arad Inbar, Erez Cohen, Nir Somech,
Ben Grinberg, Daniel Lubel, Shir Sadon (DREAM Security Research Team)

Each has a draft advisory; the GHSA id is cited in the comment at every fix
site. CVE ids have been requested and are not assigned yet.

GHSA-f3j5-rhhr-85j5 - use-after-free in func_pcre (src/ec_filter.c)
  The PCRE2 match data was freed before pcre2_get_ovector_pointer() was
  called on it, and the resulting pointer then drove an allocation size,
  two memcpy offsets into the pcap buffer, and the packet length delta.
  The offsets are now copied out of the match data before it is released,
  which also plugs the leaks on the no-match paths of both level 5 and 6.
  This is the default build path wherever pcre2 is installed.

GHSA-vrjv-f8gv-2h35 - heap overflow in gg_get_version (ec_gg.c)
  The helper composed up to 44 bytes into a 30-byte allocation via
  sprintf()/strcat(). Both helpers now build into a private buffer and hand
  back only a bounded copy. Also fixes user[10]/pass[40], which were one
  byte short for a u_int32 uin and 41 bytes short for five %X, and the
  scratch buffers leaked by the short-packet guards.

GHSA-v48w-7cvh-c7f4 - SNI heap over-read (ec_sslwrap.c)
  val_len was read from the ClientHello and passed to strndup() without
  being checked against the extension length, so it read up to 64KB past
  the buffer and shipped the result to the upstream server. The reported
  double free was not reproducible - ae->hostname is freed once and
  SAFE_FREE() NULLs - but the callback fires twice and leaked the first
  allocation, so it now releases any previous value before reassigning.

GHSA-hq2w-pg4m-6w63 - out-of-bounds read in sslw_remove_sts (ec_sslwrap.c)
  The search for the end of the STS header used the length of the whole
  packet from an already-advanced pointer, reading past the buffer, and did
  not check memmem() for NULL - h_end became 0x2, header_length underflowed
  and memcpy() got a near-SIZE_MAX length. Bounded and NULL-checked; len
  still describes the whole packet, as the rebuild below is sized from it.

GHSA-j37f-7jx3-rf8j - size_t underflow in ccmp_decrypt (ec_encryption_ccmp.c)
  len -= WPA_CCMP_TRAILER wrapped for frames shorter than 8 bytes and the
  decrypt loop then ran ~2^60 times up the stack. Both the outer and inner
  function now reject undersized frames, and the UINT16_MAX check moved
  above the VLA it was meant to bound.

GHSA-jw9q-j2rm-94j2 - heap over-read in the NTLM parser (ec_http.c)
  lmResponse/ntResponse/uUser offsets were used as displacements from the
  decoded message with no bounds checking, letting a peer read 24 bytes
  from anywhere in a 4GB window and have us log the result. The decoded
  length is now kept and every offset/length pair validated against it;
  short blobs are rejected before msgType is read at offset 8.

GHSA-w5ch-7c4x-crcc - stack underflow in the telnet dissector (ec_telnet.c)
  Backspace handling decremented the write pointer with no lower bound, so
  leading backspaces walked it below the VLA and later characters wrote
  attacker-chosen bytes underneath it. Bounded by str.

Verified with an AddressSanitizer build: the func_pcre use-after-free
reproduces end to end via etterfilter + a crafted pcap and is gone after
the fix, and extracted harnesses confirm the gg, ccmp and telnet fixes.
Existing test suite passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U7DPkxps63uUMetZEJSTLr
@eaescob
eaescob force-pushed the security/dream-2026-hardening branch from 3416996 to a8c1a50 Compare September 8, 2026 17:44

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant