Skip to content

fix: two memory leaks on malformed-packet exception paths (#884, #885) - #886

Merged
mrjimenez merged 2 commits into
amule-project:masterfrom
got3nks:fix/exception-safety-leaks
Jun 6, 2026
Merged

fix: two memory leaks on malformed-packet exception paths (#884, #885)#886
mrjimenez merged 2 commits into
amule-project:masterfrom
got3nks:fix/exception-safety-leaks

Conversation

@got3nks

@got3nks got3nks commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Two independent CWE-401 leaks, both triggered by CEOFException thrown from a truncated wire payload while raw-pointer state is in flight, both filed today by @ngosang. One commit per fix.

Commits

# Issue What it does Files
13ff9c1ac #884 Wraps the AND/OR/NOT bodies in CreateSearchExpressionTree with try/catch. On exception the static Free() helper is invoked on the partial subtree (it's null-safe and recursively releases left/right), then the exception re-raises. Restores the leak-free behaviour of the success / NULL-return paths. The leaf branches (String / MetaTag / Numeric) don't need a guard because the throwing reads in each happen before any node is allocated — those nodes are only at risk when they are the left child of a boolean whose right recursion throws, which the new boolean-branch guards now handle. KademliaUDPListener.cpp
c98fd28a7 #885 Replaces the heap-allocated new CMemFile(packet, size) in the OP_SERVERLIST handler with a stack object. Destruction now happens on every exit path, including the throw from ReadUInt8/ReadUInt32/ReadUInt16 on an empty or truncated server-supplied payload. Drops the now-redundant explicit delete servers. ServerSocket.cpp

A small note on the #884 issue text: it states that CKademlia::ProcessPacket "logs and continues" — actually that dispatcher logs and re-throws in both catch arms (Kademlia.cpp:309 & 312); the final swallower is ClientUDPSocket.cpp:129-135. The leak is real either way — it happens during stack unwinding through CreateSearchExpressionTree, not at the eventual catch site — so the fix location is unchanged.

Test plan

Built clean on macOS local (Apple Silicon, Homebrew) at each commit; daemon links and runs.

#885 — runtime-verified on Ubuntu ARM64 via synthetic harness

The fix is reachable only when amuled is connected to a server that sends a malformed OP_SERVERLIST. Public eD2k servers don't reliably send OP_SERVERLIST at all (most never reply to OP_GETSERVERLIST), so a synthetic harness was built to drive the path end-to-end against amuled #886:

  • Tiny Python TCP "server" listening on a non-public LAN address. After completing just enough of the eD2k handshake (OP_LOGINREQUEST in, OP_SERVERMESSAGE + OP_IDCHANGE + OP_SERVERSTATUS out) to push amuled to CS_CONNECTED, it injects:
    1. A well-formed OP_SERVERLIST carrying two entries (8.8.8.8:4444, 1.1.1.1:5555).
    2. A truncated OP_SERVERLIST with a zero-byte payload — forces ReadUInt8() to throw CEOFException at the first read inside the handler.
  • amuled is steered at the harness via amulecmd add ed2k://|server|<IP>|14661|/ + connect <IP>:14661. (Requires FilterLanIPs=0 and IpFilterServers=0 temporarily — restored afterwards.)
  • Observed in ~/.aMule/logfile:
    • Well-formed packet: Servers: Server: OP_SERVERLISTReceived 2 new serversSaving of server-list completed. Then ~20s later: ServerUDP: >> Sending OP__GlobServStatReq ... to server 8.8.8.8:4444 — confirms the two injected entries actually landed in the live server list.
    • Truncated packet: Server: Packet Received: Prot e3, Opcode 32, Length 0Servers: Server: OP_SERVERLISTBogus packet received from server: SafeIO::EOF: Attempt to read past end of file. Stack unwinding propagates from ReadUInt8 out through the case body to the function-level catch — with the stack-allocated CMemFile from this PR, the wrapper is destroyed during unwind (the leak the PR closes).
  • amuled survived both packets, reconnected to its prior server immediately after the bogus-packet disconnect, no follow-on issues.

#884 — code review only

CreateSearchExpressionTree is reached when amuled receives a KADEMLIA2_SEARCH_KEY_REQ from another Kad peer. The Kad node on the test VM is firewalled, so peers don't issue search-key-reqs at it, and a synthetic harness for that path would require eMule-encrypted UDP + a working DH key agreement — disproportionate effort for a leak fix this surgical. The diff is three mechanical try/catch wrappers around AND/OR/NOT bodies with no success-path semantics change; the success path is byte-for-byte identical to before.

got3nks added 2 commits June 6, 2026 18:22
CreateSearchExpressionTree builds an SSearchTerm tree from a
KADEMLIA2_SEARCH_KEY_REQ payload via recursive descent over a
CMemFile. The three boolean branches (AND/OR/NOT) allocate the
parent node with a bare new, then make a first recursive call for
the left child and a second for the right. The recursive reads
(bio.ReadUInt8, ReadString, ReadUInt32, ReadUInt64) throw
CEOFException on a truncated payload. With no try/catch in the
parser, no handler in Process2SearchKeyRequest, and ~SSearchTerm
only freeing astr/tag (not recursing into left/right -- that lives
exclusively in the manual Free walk), the parent node and any
already-built left subtree leak through stack unwinding all the way
up to CClientUDPSocket::OnPacketReceived's catch block.

KADEMLIA2_SEARCH_KEY_REQ is wire-reachable pre-auth on the Kad UDP
listener (PacketTracking rate-limits per-IP, but does not eliminate
slow-burn or multi-source attacks), so the leak is remotely
triggerable resource exhaustion.

Wrap each boolean-node body in a try/catch that calls the static
Free() helper on the partial subtree before re-raising. Free is
null-safe and recursively releases the partially built left
subtree along with the parent node, restoring the leak-free
behaviour of the success/NULL-return paths. The leaf branches
(String/MetaTag/Numeric) don't need a guard because the throwing
reads in each happen before any node is allocated; their nodes
are only at risk when they are the already-built left child of a
boolean whose right recursion throws, which the new boolean-branch
guards now handle.

Fixes amule-project#884.
The OP_SERVERLIST handler heap-allocated its CMemFile wrapper with
a bare new and only freed it via an unguarded delete at the end of
the case body. The very first ReadUInt8 on an empty payload, and
the ReadUInt32/ReadUInt16 calls inside the entry loop on a
truncated payload, throw CEOFException; the function-level catch
at the end of ProcessPacket logs the bogus packet but does not
reach the still-owning local pointer. The CMemFile attach
constructor sets m_delete = false, so the underlying wire buffer
isn't lost -- only the small wrapper -- but a malicious server can
repeat the malformed packet indefinitely.

Switch to a stack-allocated CMemFile, which is destroyed on every
exit path including the throw, and drop the now-redundant explicit
delete.

Fixes amule-project#885.
@mrjimenez
mrjimenez merged commit 51d0331 into amule-project:master Jun 6, 2026
7 checks passed
got3nks added a commit to got3nks/amule that referenced this pull request Jun 7, 2026
…ule-project#912)

Extends existing categories (preferring extensions over new lines):
- Performance/Upload: amule-project#898 SlotAllocation default raised.
- Networking & Discovery: wire-parser hardening list extended with
  amule-project#879/amule-project#882/amule-project#890/amule-project#886; new amuleweb security hardening bullet
  consolidating ngosang's amule-project#869-amule-project#874 triage (all landed in amule-project#875);
  amulegui list extended with amule-project#857; shared-folder watcher extended
  with amule-project#858.
- Packaging: Windows installer i18n line extended with amule-project#899.
- Internals & Refactoring: new docs-polish + code-quality bullets
  covering amule-project#851/amule-project#855/amule-project#862/amule-project#888/amule-project#900/amule-project#866/amule-project#867/amule-project#895 and amule-project#909/amule-project#910/amule-project#912.
- Translations: new pre-release final-wave bullet covering amule-project#847/amule-project#856/
  amule-project#891/amule-project#908/amule-project#860/amule-project#904/amule-project#859/amule-project#863/amule-project#861/amule-project#880/amule-project#911/amule-project#901/amule-project#902/amule-project#889/amule-project#868/amule-project#853.
- Bug Fixes & Stability: amule-project#850/amule-project#854/amule-project#878/amule-project#906.
- CI: ccache wiring (amule-project#892, amule-project#903) + CodeQL binutils-dev (amule-project#907).

Contributors footer gains mifritscher and nguyenhoangminhhieu2004-gif
(both first-time contributors).

PR index extended through amule-project#912.
mrjimenez pushed a commit that referenced this pull request Jun 8, 2026
Extends existing categories (preferring extensions over new lines):
- Performance/Upload: #898 SlotAllocation default raised.
- Networking & Discovery: wire-parser hardening list extended with
  #879/#882/#890/#886; new amuleweb security hardening bullet
  consolidating ngosang's #869-#874 triage (all landed in #875);
  amulegui list extended with #857; shared-folder watcher extended
  with #858.
- Packaging: Windows installer i18n line extended with #899.
- Internals & Refactoring: new docs-polish + code-quality bullets
  covering #851/#855/#862/#888/#900/#866/#867/#895 and #909/#910/#912.
- Translations: new pre-release final-wave bullet covering #847/#856/
  #891/#908/#860/#904/#859/#863/#861/#880/#911/#901/#902/#889/#868/#853.
- Bug Fixes & Stability: #850/#854/#878/#906.
- CI: ccache wiring (#892, #903) + CodeQL binutils-dev (#907).

Contributors footer gains mifritscher and nguyenhoangminhhieu2004-gif
(both first-time contributors).

PR index extended through #912.
@got3nks
got3nks deleted the fix/exception-safety-leaks branch June 8, 2026 10:33
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.

2 participants