✓ProveIt
Examples / Report #3158093 Saved analysis

Memory Leak in libcurl via Location Header Handling (CWE-770)

curl · 2025-05-2222.9s analysis · $0.025 in model calls

What the report claims

Memory leak in libcurl when processing HTTP 3xx redirect responses with a Location header, causing gradual memory consumption increase and potential denial of service due to resource exhaustion.

Compare with curl's recorded outcome

Labeled AI slop

curl's maintainer lists this report as AI slop

Shown for comparison after analysis. The pipeline did not see this label.

ProveIt's finding

Needs human review

The available evidence does not settle the report. A maintainer should investigate.

Why this finding?
  • Curl_req_hard_reset and Curl_req_free both set req->location = NULL without freeing the prior allocation — confirmed in both master and release curl-8_13_0.
  • Curl_copy_header_value allocates via malloc (via Curl_memdup0) — confirmed.
  • The allocation site (http_header function parsing Location: header into req->location) is not in the provided code snippets, so the end-to-end leak path cannot be fully verified from the given evidence.
  • If the allocation occurs as described, this would be a repeated leak on each redirect (large leak), which the project policy treats as a potential security issue (DoS risk).
What still needs checking
  • The implementation of http_header() in lib/http.c (where Location header is parsed and stored to req->location).
How this analysis ran 5 claims · 8 citations verified

Checked against curl master on 2025-05-22 (203b4349af3e). Test evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.

1 · Grounding
grounded
no model · 16 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
5 claims · 2874 tokens · $0.0003
3 · Evidence
9 code snippets
real curl source at 203b4349af3e
4 · Judgment
Nemotron-3-Ultra-550b-a55b
14911 tokens · 15.3s · $0.0245
5 · Receipts
8 of 8 citations verified
every citation must point at code the judge was shown
Review a draft reply to the reporter

Edit and verify before sending.

Thank you for the detailed report. The provided code confirms that Curl_req_hard_reset and Curl_req_free do not free req->location, and that Curl_copy_header_value allocates via malloc. However, the http_header() function implementation (where the Location header would be parsed and stored into req->location) was not included in the source snippets we reviewed. Could you point to the exact lines in http_header that assign to req->location, or provide that code section? This will let us confirm the full allocation→leak path.

Inspect all claims and source code 5 claims · 9 code excerpts

Report claims

C1flaw not verifiable

Memory allocated for the Location header's value is not properly deallocated when the Curl_easy handle is reused for subsequent requests

Read the reasoning

The provided code confirms that Curl_req_hard_reset and Curl_req_free do not free req->location, and that Curl_copy_header_value allocates memory via malloc. However, the implementation of http_header() where the Location header is allegedly parsed and stored into req->location is not included in the provided source snippets (only its declaration at lib/http.c:106 is shown). Without seeing the allocation site, the full claim cannot be verified from the provided code alone.

C2location not verifiable

The leak occurs in the handling of HTTP 3xx redirect responses containing a Location header

Read the reasoning

The claim describes the leak occurring in HTTP 3xx redirect handling with Location headers. The http_header function implementation (where header parsing occurs) is not in the provided code, so the trigger location cannot be verified.

C3trigger not verifiable

A malicious server can trigger the leak by sending HTTP 3xx responses with long Location headers that cause repeated allocation without deallocation

Read the reasoning

The trigger claim depends on http_header() allocating and storing the Location header value into req->location. Since http_header's implementation is not provided, this attack path cannot be verified from the given code.

C4impact not verifiable

The impact is gradual memory consumption increase leading to potential denial of service due to resource exhaustion

Read the reasoning

Impact claim depends on C1-C3 being verified. Since the allocation path is not confirmed in the provided code, the DoS impact cannot be assessed from the provided evidence alone.

C5reproduction not verifiable

The reproduction involves using Valgrind to monitor memory allocations while following redirects with a malicious server that provides long Location headers

Read the reasoning

This describes reproduction steps, not a code claim. Valgrind output and server logs are not provided in the evidence.

Read the original report (excerpt)

Full report on HackerOne ↗

## Summary: This report details a memory leak vulnerability in libcurl that occurs when processing HTTP 3xx redirect responses containing a `Location:` header. Specifically, the memory allocated for the `Location:` header's value is not properly deallocated when the `Curl_easy` handle is reused for subsequent requests (e.g., when following redirects or in long-running applications that frequently reuse handles). This leads to a gradual increase in memory consumption, potentially resulting in a Denial of Service (DoS) due to resource exhaustion. ### Statement clarifying if an AI was used to find the issue or generate the report: This report was generated with the assistance of an AI. The vulnerability was identified through a combination of manual code analysis and AI-assisted debugging and proof-of-concept generation. ## Affected version: curl/libcurl version: **8.14.0-DEV** (Built from source on 2025-05-22) Platform: **Kali Linux (x86_64)** You can obtain your exact version information using: ```bash ./src/curl -V ``` **Steps To Reproduce**: **Set up the testing environment**: **Install necessary dependencies**: ``` sudo apt update sudo apt install git build-essential autoconf automake libtool pkg-config zlib1g-dev libssl-dev libnghttp2-dev libldap2-dev librtmp-dev libssh2-1-dev libpsl-dev libidn2-dev libnghttp3-dev libsqlite3-dev libbrotli-dev valgrind python3 ``` **Clone the curl repository**: ```git clone https://github.com/curl/curl.git cd curl ``` **Build curl with debug symbols**: ``` autoreconf -fi ./configure --with-openssl --with-zlib --with-libssh2 --with-libpsl --with-libidn2 --with-nghttp2 --with-ldap --with-brotli --enable-debug --disable-shared make -j$(nproc) chmod +x curl-config ``` **Prepare the malicious HTTP server (Python PoC server)**: **Create a Python script named leak_server.py in the root of the curl directory**: ``` nano leak_server.py ``` **Paste the following Python code into leak_server.py**: ``` from http.server import BaseHTTPRequestHandler, HTTPServer import sys class LeakHandler(BaseHTTPRequestHandler): def do_GET(self): path = self.path sys.stderr.write(f"Received request for: {path}\n") # Number of redirects to perform before stopping max_redirects = 1000 # Adjust this value as needed for more/less leak try: current_redirect_count = int(path.split('/')[-1]) …

Source code

lib/request.clines 75–167 · lines 100-100 cited by the report; definition of Curl_req_hard_reset
75 if(result)
76 return result;
77
78 if(!req->sendbuf_init) {
79 Curl_bufq_init2(&req->sendbuf, data->set.upload_buffer_size, 1,
80 BUFQ_OPT_SOFT_LIMIT);
81 req->sendbuf_init = TRUE;
82 }
83 else {
84 Curl_bufq_reset(&req->sendbuf);
85 if(data->set.upload_buffer_size != req->sendbuf.chunk_size) {
86 Curl_bufq_free(&req->sendbuf);
87 Curl_bufq_init2(&req->sendbuf, data->set.upload_buffer_size, 1,
88 BUFQ_OPT_SOFT_LIMIT);
89 }
90 }
91
92 return CURLE_OK;
93}
94
95CURLcode Curl_req_start(struct SingleRequest *req,
96 struct Curl_easy *data)
97{
98 req->start = curlx_now();
99 return Curl_req_soft_reset(req, data);
100}
101
102static CURLcode req_flush(struct Curl_easy *data);
103
104CURLcode Curl_req_done(struct SingleRequest *req,
105 struct Curl_easy *data, bool aborted)
106{
107 (void)req;
108 if(!aborted)
109 (void)req_flush(data);
110 Curl_client_reset(data);
111#ifndef CURL_DISABLE_DOH
112 Curl_doh_close(data);
113#endif
114 return CURLE_OK;
115}
116
117void Curl_req_hard_reset(struct SingleRequest *req, struct Curl_easy *data)
118{
119 struct curltime t0 = {0, 0};
120
121 Curl_safefree(req->newurl);
122 Curl_client_reset(data);
123 if(req->sendbuf_init)
124 Curl_bufq_reset(&req->sendbuf);
125
126#ifndef CURL_DISABLE_DOH
127 Curl_doh_close(data);
128#endif
129 /* Can no longer memset() this struct as we need to keep some state */
130 req->size = -1;
131 req->maxdownload = -1;
132 req->bytecount = 0;
133 req->writebytecount = 0;
134 req->start = t0;
135 req->headerbytecount = 0;
136 req->allheadercount = 0;
137 req->deductheadercount = 0;
138 req->headerline = 0;
139 req->offset = 0;
140 req->httpcode = 0;
141 req->keepon = 0;
142 req->upgr101 = UPGR101_INIT;
143 req->sendbuf_hds_len = 0;
144 req->timeofdoc = 0;
145 req->location = NULL;
C1: Curl_req_hard_reset sets req->location = NULL without freeing (master)
146 req->newurl = NULL;
147#ifndef CURL_DISABLE_COOKIES
148 req->setcookies = 0;
149#endif
150 req->header = FALSE;
151 req->content_range = FALSE;
152 req->download_done = FALSE;
153 req->eos_written = FALSE;
154 req->eos_read = FALSE;
155 req->eos_sent = FALSE;
156 req->upload_done = FALSE;
157 req->upload_aborted = FALSE;
158 req->ignorebody = FALSE;
159 req->http_bodyless = FALSE;
160 req->chunk = FALSE;
161 req->ignore_cl = FALSE;
162 req->upload_chunky = FALSE;
163 req->getheader = FALSE;
164 req->no_body = data->set.opt_no_body;
165 req->authneg = FALSE;
166 req->shutdown = FALSE;
167}
lib/http.c@curl-8_13_0lines 283–297 · definition of Curl_copy_header_value as in release curl-8_13_0; differs from master; commits that changed it since: 255aac56f9 curlx: move into to curlx/
283char *Curl_copy_header_value(const char *header)
284{
285 struct Curl_str out;
286
287 /* find the end of the header name */
288 if(!Curl_str_cspn(&header, &out, ";:") &&
289 (!Curl_str_single(&header, ':') || !Curl_str_single(&header, ';'))) {
290 Curl_str_untilnl(&header, &out, MAX_HTTP_RESP_HEADER_SIZE);
291 Curl_str_trimblanks(&out);
292
293 return Curl_memdup0(Curl_str(&out), Curl_strlen(&out));
C1: Curl_copy_header_value allocates via Curl_memdup0 (malloc)
294 }
295 /* bad input */
296 return NULL;
297}
lib/strdup.clines 112–123 · definition of Curl_memdup0
112void *Curl_memdup0(const char *src, size_t length)
113{
114 char *buf = malloc(length + 1);
C1: Curl_memdup0 allocates with malloc(length + 1)
115 if(!buf)
116 return NULL;
117 if(length) {
118 DEBUGASSERT(src); /* must never be NULL */
119 memcpy(buf, src, length);
120 }
121 buf[length] = 0;
122 return buf;
123}
lib/request.clines 169–175 · definition of Curl_req_free
169void Curl_req_free(struct SingleRequest *req, struct Curl_easy *data)
170{
171 Curl_safefree(req->newurl);
C1: Curl_req_free frees req->newurl and req->p.ftp but not req->location (master)
172 if(req->sendbuf_init)
173 Curl_bufq_free(&req->sendbuf);
174 Curl_client_cleanup(data);
175}
lib/request.c@curl-8_13_0lines 172–185 · definition of Curl_req_free as in release curl-8_13_0; differs from master; commits that changed it since: 27bc798369 file: use easy handle meta for protocol struct | a2d90d4ba5 ftp: use easy handle and connectin meta data for protocol structs | 1ebd92d0fd async: DoH improvements
172void Curl_req_free(struct SingleRequest *req, struct Curl_easy *data)
173{
174 /* This is a bit ugly. `req->p` is a union and we assume we can
175 * free this safely without leaks. */
176 Curl_safefree(req->p.ftp);
177 Curl_safefree(req->newurl);
C1: Curl_req_free frees req->newurl and req->p.ftp but not req->location (release)
178 if(req->sendbuf_init)
179 Curl_bufq_free(&req->sendbuf);
180 Curl_client_cleanup(data);
181
182#ifndef CURL_DISABLE_DOH
183 Curl_doh_cleanup(data);
184#endif
185}
lib/request.c@curl-8_13_0lines 117–170 · definition of Curl_req_hard_reset as in release curl-8_13_0; differs from master; commits that changed it since: 27bc798369 file: use easy handle meta for protocol struct | a2d90d4ba5 ftp: use easy handle and connectin meta data for protocol structs
117void Curl_req_hard_reset(struct SingleRequest *req, struct Curl_easy *data)
118{
119 struct curltime t0 = {0, 0};
120
121 /* This is a bit ugly. `req->p` is a union and we assume we can
122 * free this safely without leaks. */
123 Curl_safefree(req->p.ftp);
124 Curl_safefree(req->newurl);
125 Curl_client_reset(data);
126 if(req->sendbuf_init)
127 Curl_bufq_reset(&req->sendbuf);
128
129#ifndef CURL_DISABLE_DOH
130 Curl_doh_close(data);
131#endif
132 /* Can no longer memset() this struct as we need to keep some state */
133 req->size = -1;
134 req->maxdownload = -1;
135 req->bytecount = 0;
136 req->writebytecount = 0;
137 req->start = t0;
138 req->headerbytecount = 0;
139 req->allheadercount = 0;
140 req->deductheadercount = 0;
141 req->headerline = 0;
142 req->offset = 0;
143 req->httpcode = 0;
144 req->keepon = 0;
145 req->upgr101 = UPGR101_INIT;
146 req->sendbuf_hds_len = 0;
147 req->timeofdoc = 0;
148 req->location = NULL;
C1: Curl_req_hard_reset sets req->location = NULL without freeing (release curl-8_13_0)
149 req->newurl = NULL;
150#ifndef CURL_DISABLE_COOKIES
151 req->setcookies = 0;
152#endif
153 req->header = FALSE;
154 req->content_range = FALSE;
155 req->download_done = FALSE;
156 req->eos_written = FALSE;
157 req->eos_read = FALSE;
158 req->eos_sent = FALSE;
159 req->upload_done = FALSE;
160 req->upload_aborted = FALSE;
161 req->ignorebody = FALSE;
162 req->http_bodyless = FALSE;
163 req->chunk = FALSE;
164 req->ignore_cl = FALSE;
165 req->upload_chunky = FALSE;
166 req->getheader = FALSE;
167 req->no_body = data->set.opt_no_body;
168 req->authneg = FALSE;
169 req->shutdown = FALSE;
170}
lib/http.clines 106–155 · definition of http_header
106static CURLcode http_header(struct Curl_easy *data,
C2: Only declaration of http_header shown; implementation not provided
C3: http_header implementation not provided
107 const char *hd, size_t hdlen);
108static CURLcode http_host(struct Curl_easy *data, struct connectdata *conn);
109static CURLcode http_range(struct Curl_easy *data,
110 Curl_HttpReq httpreq);
111static CURLcode http_req_complete(struct Curl_easy *data,
112 struct dynbuf *r, int httpversion,
113 Curl_HttpReq httpreq);
114static CURLcode http_req_set_reader(struct Curl_easy *data,
115 Curl_HttpReq httpreq, int httpversion,
116 const char **tep);
117static CURLcode http_size(struct Curl_easy *data);
118static CURLcode http_statusline(struct Curl_easy *data,
119 struct connectdata *conn);
120static CURLcode http_target(struct Curl_easy *data, struct connectdata *conn,
121 struct dynbuf *req);
122static CURLcode http_useragent(struct Curl_easy *data);
123#ifdef HAVE_LIBZ
124static CURLcode http_transferencode(struct Curl_easy *data);
125#endif
126
127
128/*
129 * HTTP handler interface.
130 */
131const struct Curl_handler Curl_handler_http = {
132 "http", /* scheme */
133 Curl_http_setup_conn, /* setup_connection */
134 Curl_http, /* do_it */
135 Curl_http_done, /* done */
136 ZERO_NULL, /* do_more */
137 Curl_http_connect, /* connect_it */
138 ZERO_NULL, /* connecting */
139 ZERO_NULL, /* doing */
140 ZERO_NULL, /* proto_getsock */
141 Curl_http_getsock_do, /* doing_getsock */
142 ZERO_NULL, /* domore_getsock */
143 ZERO_NULL, /* perform_getsock */
144 ZERO_NULL, /* disconnect */
145 Curl_http_write_resp, /* write_resp */
146 Curl_http_write_resp_hd, /* write_resp_hd */
147 ZERO_NULL, /* connection_check */
148 ZERO_NULL, /* attach connection */
149 Curl_http_follow, /* follow */
150 PORT_HTTP, /* defport */
151 CURLPROTO_HTTP, /* protocol */
152 CURLPROTO_HTTP, /* family */
153 PROTOPT_CREDSPERREQUEST | /* flags */
154 PROTOPT_USERPWDCTRL
155};
lib/http.clines 2317–2367 · lines 2342-2342 cited by the report
Show 51 lines (not cited by the judge)
2317 *announced_exp100 = TRUE;
2318 }
2319 }
2320 return CURLE_OK;
2321}
2322
2323static CURLcode http_req_complete(struct Curl_easy *data,
2324 struct dynbuf *r, int httpversion,
2325 Curl_HttpReq httpreq)
2326{
2327 CURLcode result = CURLE_OK;
2328 curl_off_t req_clen;
2329 bool announced_exp100 = FALSE;
2330
2331 DEBUGASSERT(data->conn);
2332 if(data->req.upload_chunky) {
2333 result = Curl_httpchunk_add_reader(data);
2334 if(result)
2335 return result;
2336 }
2337
2338 /* Get the request body length that has been set up */
2339 req_clen = Curl_creader_total_length(data);
2340 switch(httpreq) {
2341 case HTTPREQ_PUT:
2342 case HTTPREQ_POST:
2343#if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API)
2344 case HTTPREQ_POST_FORM:
2345 case HTTPREQ_POST_MIME:
2346#endif
2347 /* We only set Content-Length and allow a custom Content-Length if
2348 we do not upload data chunked, as RFC2616 forbids us to set both
2349 kinds of headers (Transfer-Encoding: chunked and Content-Length).
2350 We do not override a custom "Content-Length" header, but during
2351 authentication negotiation that header is suppressed.
2352 */
2353 if(req_clen >= 0 && !data->req.upload_chunky &&
2354 (data->req.authneg ||
2355 !Curl_checkheaders(data, STRCONST("Content-Length")))) {
2356 /* we allow replacing this header if not during auth negotiation,
2357 although it is not very wise to actually set your own */
2358 result = curlx_dyn_addf(r, "Content-Length: %" FMT_OFF_T "\r\n",
2359 req_clen);
2360 }
2361 if(result)
2362 goto out;
2363
2364#ifndef CURL_DISABLE_MIME
2365 /* Output mime-generated headers. */
2366 if(data->state.mimepost &&
2367 ((httpreq == HTTPREQ_POST_FORM) || (httpreq == HTTPREQ_POST_MIME))) {
lib/http.clines 283–297 · definition of Curl_copy_header_value
Show 15 lines (not cited by the judge)
283char *Curl_copy_header_value(const char *header)
284{
285 struct Curl_str out;
286
287 /* find the end of the header name */
288 if(!curlx_str_cspn(&header, &out, ";:") &&
289 (!curlx_str_single(&header, ':') || !curlx_str_single(&header, ';'))) {
290 curlx_str_untilnl(&header, &out, MAX_HTTP_RESP_HEADER_SIZE);
291 curlx_str_trimblanks(&out);
292
293 return Curl_memdup0(curlx_str(&out), curlx_strlen(&out));
294 }
295 /* bad input */
296 return NULL;
297}