CVE-2021-22945: UAF and double-free in MQTT sending
Medium
C
curl
Submitted None
Actions:
Reported by
z2_
Vulnerability Details
Technical details and impact analysis
# 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