CVE-2019-5481: krb5: double-free in read_data() after realloc() fail
What the report claims
Double-free of 'buf->data' occurs in lib/security.c's teardown path when Curl_saferealloc() fails, potentially exploitable via remote trigger of realloc() failure with len=0x7fffffff.
Compare with curl's recorded outcome
Known vulnerability
A real vulnerability: CVE-2019-5481 FTP-KRB double free
Shown for comparison after analysis. The pipeline did not see this label.
ProveIt's finding
The code supports the reported flaw. A maintainer should review its impact and decide what to do next.
Why this finding?
- Curl_saferealloc frees the original pointer on realloc failure (lib/strdup.c:96-98)
- read_data returns error without setting buf->data = NULL (lib/security.c:206-207)
- len is read from attacker-controlled network socket (lib/security.c:197)
- 0x7fffffff is a plausible trigger on 32-bit systems causing allocation failure
- Double-free on teardown is a security-relevant heap corruption vulnerability
How this analysis ran 5 claims · 11 citations verified
Checked against curl master on 2019-09-03 (5050edb12484). Test evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.
Review a draft reply to the reporter
Edit and verify before sending.
Thank you for the report. The double-free in read_data() is confirmed: when Curl_saferealloc fails, it frees buf->data but read_data returns without nulling the pointer, leaving a dangling reference that will be freed again on teardown. The len value is read from the network, so an attacker can trigger this on 32-bit systems by sending 0x7fffffff. We will fix this by ensuring buf->data is set to NULL on allocation failure.
Inspect all claims and source code 5 claims · 2 code excerpts
Report claims
double-free of the reference 'buf->data' on the teardown path if 'Curl_saferealloc()' fails
Read the reasoning
When Curl_saferealloc fails with non-zero size, it frees the original pointer (buf->data) and returns NULL. read_data then returns an error code without nulling buf->data. The caller's teardown path will subsequently free buf->data a second time, causing a double-free.
in 'lib/security.c'
Read the reasoning
The read_data function is located in lib/security.c as shown in the provided source.
sender might be able to remotely trigger a realloc() failure ... by sending the value 0x7fffffff
Read the reasoning
The len value is read directly from the network socket, converted from network byte order, and passed to Curl_saferealloc. An attacker controlling the network stream can send 0x7fffffff (2,147,483,647), which on a 32-bit system will likely cause realloc to fail due to address space exhaustion.
double-free after a 'realloc()' failure, which could be triggered remotely
Read the reasoning
Combines C1 and C3: attacker-controlled len can trigger realloc failure, leading to double-free on teardown.
realloc failure with particular 'len' value can be reproduced ... by sending the value 0x7fffffff
Read the reasoning
The reporter's test case demonstrates that realloc(0x7fffffff) fails on a 32-bit system. Since len comes from the network and is passed directly to realloc via Curl_saferealloc, the same failure can be triggered in the actual code path.
Read the original report
Source code
| 189 | static CURLcode read_data(struct connectdata *conn, |
| C2: read_data function defined in lib/security.c | |
| 190 | curl_socket_t fd, |
| 191 | struct krb5buffer *buf) |
| 192 | { |
| 193 | int len; |
| 194 | void *tmp = NULL; |
| 195 | CURLcode result; |
| 196 | |
| 197 | result = socket_read(fd, &len, sizeof(len)); |
| C3: len read from socket via socket_read(fd, &len, sizeof(len)) | |
| C4: Attacker controls len via network input | |
| 198 | if(result) |
| 199 | return result; |
| 200 | |
| 201 | if(len) { |
| 202 | /* only realloc if there was a length */ |
| 203 | len = ntohl(len); |
| C3: len = ntohl(len) converts network byte order to host order | |
| C5: len = ntohl(len) - attacker-controlled value reaches realloc | |
| 204 | tmp = Curl_saferealloc(buf->data, len); |
| C1: Curl_saferealloc(buf->data, len) called; on failure it frees buf->data and returns NULL | |
| C3: len passed to Curl_saferealloc as size argument | |
| 205 | } |
| 206 | if(tmp == NULL) |
| C1: read_data returns CURLE_OUT_OF_MEMORY without setting buf->data = NULL, leaving dangling pointer | |
| C4: buf->data not nulled before error return | |
| 207 | return CURLE_OUT_OF_MEMORY; |
| 208 | |
| 209 | buf->data = tmp; |
| 210 | result = socket_read(fd, buf->data, len); |
| 211 | if(result) |
| 212 | return result; |
| 213 | buf->size = conn->mech->decode(conn->app_data, buf->data, len, |
| 214 | conn->data_prot, conn); |
| 215 | buf->index = 0; |
| 216 | return CURLE_OK; |
| 217 | } |
| 93 | void *Curl_saferealloc(void *ptr, size_t size) |
| 94 | { |
| 95 | void *datap = realloc(ptr, size); |
| 96 | if(size && !datap) |
| C1: Curl_saferealloc frees ptr when size != 0 and realloc fails | |
| C4: Curl_saferealloc frees original buffer on allocation failure | |
| 97 | /* only free 'ptr' if size was non-zero */ |
| 98 | free(ptr); |
| 99 | return datap; |
| 100 | } |