Skip to content

fix(Proxy): bound SendTo's SOCKS5 UDP relay against oversized payloads - #882

Merged
mrjimenez merged 1 commit into
amule-project:masterfrom
got3nks:fix/socks5-udp-oob-write
Jun 6, 2026
Merged

fix(Proxy): bound SendTo's SOCKS5 UDP relay against oversized payloads#882
mrjimenez merged 1 commit into
amule-project:masterfrom
got3nks:fix/socks5-udp-oob-write

Conversation

@got3nks

@got3nks got3nks commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #881.

CDatagramSocketProxy::SendTo prepended the 10-byte SOCKS5 UDP request header into the fixed 5120-byte m_buffer and then memcpy'd the full payload right after, with no upper-bound check. Any outbound UDP datagram larger than PROXY_BUFFER_SIZE - PROXY_UDP_OVERHEAD_IPV4 (5110 bytes) walked past the end of m_buffer.

The sibling CDatagramSocketProxy::RecvFrom already carried the exact guard that was missing on send — when the payload didn't fit it allocated a per-call buffer and freed it after the copy. The asymmetry was the bug.

Mirror that pattern on send: allocate a dynamic buffer for the oversized case, fall back to m_buffer otherwise, free if it was dynamically allocated. nBytes is outbound-controlled (assembled by aMule, not directly attacker-controlled), so practical reachability today is narrow — but the SendTo interface places no upper bound on its payload, so any future caller that hands it a large datagram would have walked off the end.

Tested: macOS local build (Apple Silicon, Homebrew) — clean.

CDatagramSocketProxy::SendTo prepended a 10-byte SOCKS5 UDP request
header into the fixed 5120-byte m_buffer and then memcpy'd the full
payload right after, with no upper-bound check. Any outbound UDP
datagram larger than PROXY_BUFFER_SIZE - PROXY_UDP_OVERHEAD_IPV4
(5110 bytes) corrupted memory past the end of m_buffer.

The sibling RecvFrom already carried the exact guard that was
missing on send -- when the receive payload didn't fit, it allocated
a per-call buffer and freed it after the copy. The asymmetry was the
bug.

Mirror that pattern on send: allocate a dynamic buffer for the
oversized case, fall back to m_buffer otherwise, free if it was
dynamically allocated. nBytes is outbound-controlled (assembled by
aMule, not directly attacker-controlled), so reachability today
is narrow -- but the SendTo interface places no upper bound on its
payload, so any future caller that hands it a large datagram would
have walked off the end.

Fixes amule-project#881.
@mrjimenez
mrjimenez merged commit 46bbc4d 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/socks5-udp-oob-write branch June 8, 2026 10:33
got3nks added a commit to got3nks/amule that referenced this pull request Aug 10, 2026
m_eventPending was a plain bool, set from the ASIO thread and cleared on
the main thread, tested and set in separate steps. The damaging
interleaving is the ASIO thread seeing the latch set and skipping its
post while the main thread is about to clear it: the event it deferred to
belongs to the previous delivery, so the bytes it just buffered end up
with nothing left to announce them. Captured on a live amulegui/amuled
pair with the connection dead and 4.6 kB still unread in the kernel
(c = buffered, p = read pending, e = event pending):

  handleRead(10)[c=10 p=0 e=1]   bytes land, latch already set
  postSkip(2)   [c=10 p=0 e=1]   post skipped
  block(8)      [c=10 p=0 e=0]   main consumes that event, latch clear

leaving kernel_unread=4669 event_pending=0 read_pending=0 buffered=10,
with no read armed, no event outstanding, and nothing that can recover.

Make it atomic and use an acq_rel exchange to post. The exchange makes
test-and-set a single step, so exactly one caller wins the right to
notify. It also writes unconditionally, so even the skipping path
releases the buffer and the cleared m_readPending; EventProcessed
acquires that same value, without which the reader is still free to
observe a stale read state and block on data that has already arrived.

Check the wrapper before taking the latch rather than after. With no
wrapper nothing delivers a notification, and EventProcessed only runs off
a delivered one, so claiming the latch there would leave it set for the
life of the socket and make every later post skip -- the same wedge from
the other side. HandleRead reaches that state deliberately: it logs
"wrapper gone" and carries on to post. Returning early also avoids the
window a take-then-release would open, where a concurrent post on the
other thread sees the latch set and drops itself.
got3nks added a commit to got3nks/amule that referenced this pull request Aug 10, 2026
Rebased onto master with amule-project#882 in it. The instrumentation now reads the
event latch through the same atomic operation that changes it:
PostReadEvent labels post/postSkip from the exchange's return value, and
EventProcessed counts ev_spurious from the value its exchange replaced.
A separate read would race with the posting side and, worse, is free to
disagree with the decision it claims to describe -- useless for the one
race the trace exists to study.

Also fix the [ecread] dead-socket report, which tested the pending flag
alone and reloaded it after the branch. A completion landing in between
left it clear with the buffer full, so healthy sockets were reported as
dead; those false alarms outnumbered the real ones. Load the flag once,
reuse it, and require both halves -- nothing buffered AND nothing on the
way -- before calling a socket finished.
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.

Heap buffer overflow in CDatagramSocketProxy::SendTo — unbounded memcpy into fixed 5120-byte SOCKS5 proxy buffer

2 participants