Skip to content

fix: prevent UAF after disconnectClient in mqtt event loop#2

Closed
goyalpalak18 wants to merge 1 commit intohoneynet:mainfrom
goyalpalak18:fix/mqtt-uaf-disconnect
Closed

fix: prevent UAF after disconnectClient in mqtt event loop#2
goyalpalak18 wants to merge 1 commit intohoneynet:mainfrom
goyalpalak18:fix/mqtt-uaf-disconnect

Conversation

@goyalpalak18
Copy link
Copy Markdown

Description

While reviewing the main MQTT event loop (servers/mqtt_pit.c), I found and fixed a guaranteed Heap Use-After-Free (UAF) bug.

During the connection teardown, I noticed that disconnectClient() completely frees the mqttClient struct. The core problem is that it gets called from inside a switch statement. When it hits break, it only escapes the switch block, but the outer packet-processing for loop keeps right on running.

Because of this, right after the client is freed, the code blindly marches forward and tries to update client->bytesWrittenToBuffer and run a memmove on the now-freed pointer.

Since normal botnets and standard tools (like mosquitto_pub) cleanly send a DISCONNECT packet before dropping, this UAF gets triggered reliably during normal honeypot operation. Under load, this causes silent heap corruption or eventual SIGSEGV crashes, which kills the threat intel collection.

How I fixed it

I kept the patch straightforward without messing with the core tarpit logic or protocol behavior:

  • Added a simple bool clientDisconnected = false; flag scoped to the epoll event.
  • Flagged it to true right before any disconnectClient() call inside the switch.
  • Added an if (clientDisconnected) break; after the switch to bail out of the packet loop early.
  • Wrapped the leftover buffer compaction (memmove) in an if (!clientDisconnected) check to ensure I only touch memory for live clients.

How to test

It's super easy to reproduce the before and after:

  1. Spin up the honeypot (default port 1883).
  2. Connect and immediately disconnect using a standard client: mosquitto_pub -t test -m "hello" (this sends a clean DISCONNECT on exit). Alternatively, just send the raw bytes 0xE0 0x00.
  3. Before my changes: The process will either SIGSEGV (usually in memmove or HASH_DEL) or silently corrupt the heap if you hit it with concurrent connections.
  4. After my changes: The connection tears down cleanly, memory is freed safely, and the event loop moves on without touching the dangling pointer.

Guard all post-disconnect code paths with a clientDisconnected flag
so freed client memory is never accessed after teardown.

Signed-off-by: goyalpalak18 <goyalpalak1806@gmail.com>
@goyalpalak18
Copy link
Copy Markdown
Author

Hi @regulartim , please have a look at this.

@Hackpaul
Copy link
Copy Markdown

Hackpaul commented Feb 28, 2026

I have see the #5 pr replicated the corrected version of this pr , so i appreciate this work and requested you to just close this pr to prevent misconception
.

@goyalpalak18
Copy link
Copy Markdown
Author

Got it, no problem. Closing this to avoid any confusion.

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