Loading HuntDB...

CVE-2021-22945: UAF and double-free in MQTT sending

Medium
C
curl
Submitted None
Reported by z2_

Vulnerability Details

Technical details and impact analysis

Double Free
# Vulnerability Description libcurl version 7.77.0 has a [Use-After-Free](https://github.com/curl/curl/blob/curl-7_77_0/lib/mqtt.c#L559) and a [Double-Free](https://github.com/curl/curl/blob/curl-7_77_0/lib/mqtt.c#L560) in `lib/mqtt.c` in the function `mqtt_doing` on [lines 556 - 563](https://github.com/curl/curl/blob/curl-7_77_0/lib/mqtt.c#L556): ```c if(mq->nsend) { /* send the remainder of an outgoing packet */ char *ptr = mq->sendleftovers; result = mqtt_send(data, mq->sendleftovers, mq->nsend); free(ptr); if(result) return result; } ``` As can be seen in the code above `mq->sendleftovers` gets freed in line 560 but not set to `NULL`. If `mqtt_doing` gets called repeatedly and the values of `mq->nsend` and `mq->sendleftovers` don't change this can result in 1. Sending the metadata of the freed chunk over the network via `mqtt_send` 2. Freeing `mq->sendleftovers` multiple times `mq->nsend` and `mq->sendleftovers` get set in the function `mqtt_send` if `Curl_write` cannot send all bytes in the write-buffer at once. This can e.g. happen if `write()` returns `EAGAIN` or `EWOULDBLOCK`. Then `Curl_write` sets the number of written bytes to `0` and returns `CURLE_OK`. This can trigger the vulnerabilities as follows: 1. Supply an `mqtt://` URL to curl 2. Have some successfull transmissions with `mqtt_send` 3. At some point have an unsuccessfull transmission such that not all bytes of the write-buffer can be sent. This causes `mq->sendleftovers` and `mq->nsend` to be set. 4. Have another invocation of `mqtt_doing`. The code mentioned above gets executed. `mq->sendleftovers` gets freed. If `mqtt_send` could send all remaining bytes successfully `mq->sendleftovers` and `mq->nsend` don't get reset. 5. Have another invocation of `mqtt_doing`. Since `mq->nsend` didn't change curl tries to send the leftover bytes again, triggering the vulnerabilities # How to reproduce the bug 1. Checkout tag `curl-7_77_0` in the curl repository 2. Apply the following patch that artificially creates a scenario as described above: ``` diff --git a/lib/sendf.c b/lib/sendf.c index e41bb805f..773d4b5b6 100644 --- a/lib/sendf.c +++ b/lib/sendf.c @@ -294,6 +294,7 @@ void Curl_failf(struct Curl_easy *data, const char *fmt, ...) * If the write would block (CURLE_AGAIN), we return CURLE_OK and * (*written == 0). Otherwise we return regular CURLcode value. */ +static int CUSTOM_blocked = 0; CURLcode Curl_write(struct Curl_easy *data, curl_socket_t sockfd, const void *mem, @@ -322,8 +323,13 @@ CURLcode Curl_write(struct Curl_easy *data, } #endif bytes_written = conn->send[num](data, num, mem, len, &result); + if(!CUSTOM_blocked) { + bytes_written = 0; + CUSTOM_blocked = 1; + } *written = bytes_written; + if(bytes_written >= 0) /* we completely ignore the curlcode value when subzero is not returned */ return CURLE_OK; ``` 3. Rebuild curl 4. Start a simple netcat session with: `nc -lp 5678` 5. Invoke curl with: `curl mqtt://127.0.0.1:5678/` The output: ``` free(): double free detected in tcache 2 [1] 199104 abort (core dumped) ./curl mqtt://127.0.0.1:5678 ``` And in the terminal where netcat was launched it can be seen that the content of the freed heap chunk was sent. ## Impact Since double frees of tcache chunks are not detected until glibc version 2.29 this vulnerability is perfectly exploitable for operationg systems using an older glibc. Causing `write()` to return `EAGAIN` is more difficult but not impossible to manage, e.g. this can always be the case if the peer is not reading as fast as the curl client is writing ([source](https://stackoverflow.com/questions/36539580/write-to-tcp-socket-keeps-returning-eagain/36539632#36539632)). At minimum this can be used to leak heap metadata which can help in exploitation.

Report Details

Additional information and metadata

State

Closed

Substate

Resolved

Submitted

Weakness

Double Free