Title: Use-After-Free in cURL Test Suite via Improper Cleanup of Global Handle
What the report claims
The test suite contains a use-after-free bug in the cleanup of a global CURL handle due to missing NULL assignment after curl_easy_cleanup.
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
The available evidence does not settle the report. A maintainer should investigate.
See the withdrawn model reasoning
The following reasoning failed the citation check and does not support the final finding.
- The test file lib1555.c and its contents (test_lib1555 function, t1555_curl global, test_cleanup label) do not exist in curl at the checked revision (master 68a44edd500a on 2025-12-05) or in any release since 7.50, per deterministic checks.
- Deterministic checks show 0/4 quoted lines from the report appear anywhere in curl at the checked revision.
- The report describes a vulnerability in code that is not part of the curl codebase.
How this analysis ran 5 claims · 0 citations verified
Evidence check: judge said likely_invalid without a code citation contradicting a central claim
Checked against curl master on 2025-12-05 (68a44edd500a). Dev evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.
Inspect the withdrawn draft reply
This draft belongs to the withdrawn judgment. It may contain unsupported claims.
The test file lib1555.c and the function test_lib1555 with global t1555_curl do not exist in curl's repository at the revision you reference (master 68a44edd500a, 2025-12-05) or in any release since 7.50. Deterministic symbol checks confirm 0/4 quoted lines from your report appear in curl. Please verify the file path and revision, or provide a patch against the actual curl source tree.
Inspect all claims and source code 5 claims · 10 code excerpts
This model judgment was withdrawn. The claim assessments below are preserved for inspection and may be wrong.
Report claims
curl_easy_cleanup(t1555_curl) is called without checking if t1555_curl is NULL or valid, leading to use-after-free or double-free
Read the reasoning
The report describes a test file lib1555.c with function test_lib1555, global t1555_curl, and test_cleanup label. Deterministic checks confirm these symbols are ungrounded — they do not exist in curl at the checked revision (master 68a44edd500a on 2025-12-05) or in any release since 7.50. The quoted code from the report does not appear in the curl codebase.
test_cleanup label in test_lib1555 function at line 83 in lib1555.c
Read the reasoning
The location referenced (test_cleanup label in test_lib1555 at line 83 of lib1555.c) does not exist in the curl repository at the revision checked. The deterministic checks show 0/4 quoted lines from the report appear anywhere in curl.
Repeated calls to test_lib1555 or test_cleanup causing the dangling pointer to be reused
Read the reasoning
The trigger described (repeated calls to test_lib1555 or test_cleanup) cannot occur because these functions do not exist in the curl codebase at the checked revision.
Memory corruption, potential arbitrary code execution, or denial of service in test environments
Read the reasoning
The claimed impact (memory corruption, arbitrary code execution, denial of service) is predicated on the existence of the vulnerable test code, which deterministic checks confirm is not present in curl.
Call test_lib1555 multiple times without reinitializing t1555_curl to demonstrate use-after-free
Read the reasoning
The reproduction steps require calling test_lib1555 multiple times, but this function does not exist in curl at the checked revision.
Deterministic checks that failed
- symbol progressCallback() does not exist in curl at 68a44edd500a or in any release since 7.50
- symbol easy_init exists in other curl releases but not at 68a44edd500a
- symbol easy_setopt exists in other curl releases but not at 68a44edd500a
- symbol test_cleanup() does not exist in curl at 68a44edd500a or in any release since 7.50
- quote only 0/4 quoted lines appear anywhere in curl at 68a44edd500a
- quote only 0/2 quoted lines appear anywhere in curl at 68a44edd500a
Read the original report (excerpt)
Source code
| 855 | void curl_easy_cleanup(CURL *ptr) |
| 856 | { |
| 857 | struct Curl_easy *data = ptr; |
| 858 | if(GOOD_EASY_HANDLE(data)) { |
| 859 | SIGPIPE_VARIABLE(pipe_st); |
| 860 | sigpipe_ignore(data, &pipe_st); |
| 861 | Curl_close(&data); |
| 862 | sigpipe_restore(&pipe_st); |
| 863 | } |
| 864 | } |
| 1 | <!-- |
| 2 | Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. |
| 3 | |
| 4 | SPDX-License-Identifier: curl |
| 5 | --> |
| 6 | |
| 7 | # include |
| 8 | |
| 9 | Public include files for libcurl, external users. |
| 10 | |
| 11 | They are all placed in the curl subdirectory here for better fit in any kind of |
| 12 | environment. You must include files from here using... |
| 13 | |
| 14 | #include <curl/curl.h> |
| 15 | |
| 16 | ... style and point the compiler's include path to the directory holding the |
| 17 | curl subdirectory. It makes it more likely to survive future modifications. |
| 18 | |
| 19 | The public curl include files can be shared freely between different platforms |
| 20 | and different architectures. |
Show 4 lines (not cited by the judge)
| 835 | CURLcode curl_easy_perform(CURL *data) |
| 836 | { |
| 837 | return easy_perform(data, FALSE); |
| 838 | } |
Show 23 lines (not cited by the judge)
| 1230 | CURLcode curl_easy_recv(CURL *d, void *buffer, size_t buflen, size_t *n) |
| 1231 | { |
| 1232 | CURLcode result; |
| 1233 | struct connectdata *c; |
| 1234 | struct Curl_easy *data = d; |
| 1235 | |
| 1236 | if(!GOOD_EASY_HANDLE(data)) |
| 1237 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 1238 | if(Curl_is_in_callback(data)) |
| 1239 | return CURLE_RECURSIVE_API_CALL; |
| 1240 | |
| 1241 | result = easy_connection(data, &c); |
| 1242 | if(result) |
| 1243 | return result; |
| 1244 | |
| 1245 | if(!data->conn) |
| 1246 | /* on first invoke, the transfer has been detached from the connection and |
| 1247 | needs to be reattached */ |
| 1248 | Curl_attach_connection(data, c); |
| 1249 | |
| 1250 | *n = 0; |
| 1251 | return Curl_conn_recv(data, FIRSTSOCKET, buffer, buflen, n); |
| 1252 | } |
Show 14 lines (not cited by the judge)
| 1308 | CURLcode curl_easy_send(CURL *d, const void *buffer, size_t buflen, size_t *n) |
| 1309 | { |
| 1310 | size_t written = 0; |
| 1311 | CURLcode result; |
| 1312 | struct Curl_easy *data = d; |
| 1313 | if(!GOOD_EASY_HANDLE(data)) |
| 1314 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 1315 | if(Curl_is_in_callback(data)) |
| 1316 | return CURLE_RECURSIVE_API_CALL; |
| 1317 | |
| 1318 | result = Curl_senddata(data, buffer, buflen, &written); |
| 1319 | *n = written; |
| 1320 | return result; |
| 1321 | } |
Show 33 lines (not cited by the judge)
| 274 | void curl_global_cleanup(void) |
| 275 | { |
| 276 | global_init_lock(); |
| 277 | |
| 278 | if(!initialized) { |
| 279 | global_init_unlock(); |
| 280 | return; |
| 281 | } |
| 282 | |
| 283 | if(--initialized) { |
| 284 | global_init_unlock(); |
| 285 | return; |
| 286 | } |
| 287 | |
| 288 | Curl_ssl_cleanup(); |
| 289 | Curl_async_global_cleanup(); |
| 290 | |
| 291 | #ifdef _WIN32 |
| 292 | Curl_win32_cleanup(easy_init_flags); |
| 293 | #endif |
| 294 | |
| 295 | Curl_amiga_cleanup(); |
| 296 | |
| 297 | Curl_ssh_cleanup(); |
| 298 | |
| 299 | #ifdef DEBUGBUILD |
| 300 | curlx_free(leakpointer); |
| 301 | #endif |
| 302 | |
| 303 | easy_init_flags = 0; |
| 304 | |
| 305 | global_init_unlock(); |
| 306 | } |
Show 33 lines (not cited by the judge)
| 276 | void curl_global_cleanup(void) |
| 277 | { |
| 278 | global_init_lock(); |
| 279 | |
| 280 | if(!initialized) { |
| 281 | global_init_unlock(); |
| 282 | return; |
| 283 | } |
| 284 | |
| 285 | if(--initialized) { |
| 286 | global_init_unlock(); |
| 287 | return; |
| 288 | } |
| 289 | |
| 290 | Curl_ssl_cleanup(); |
| 291 | Curl_async_global_cleanup(); |
| 292 | |
| 293 | #ifdef _WIN32 |
| 294 | Curl_win32_cleanup(easy_init_flags); |
| 295 | #endif |
| 296 | |
| 297 | Curl_amiga_cleanup(); |
| 298 | |
| 299 | Curl_ssh_cleanup(); |
| 300 | |
| 301 | #ifdef DEBUGBUILD |
| 302 | free(leakpointer); |
| 303 | #endif |
| 304 | |
| 305 | easy_init_flags = 0; |
| 306 | |
| 307 | global_init_unlock(); |
| 308 | } |
Show 9 lines (not cited by the judge)
| 1222 | int curl_mprintf(const char *format, ...) |
| 1223 | { |
| 1224 | int retcode; |
| 1225 | va_list ap_save; /* argument pointer */ |
| 1226 | va_start(ap_save, format); |
| 1227 | retcode = formatf(stdout, fputc_wrapper, format, ap_save); |
| 1228 | va_end(ap_save); |
| 1229 | return retcode; |
| 1230 | } |
Show 68 lines (not cited by the judge)
| 143 | static CURLcode global_init(long flags, bool memoryfuncs) |
| 144 | { |
| 145 | if(initialized++) |
| 146 | return CURLE_OK; |
| 147 | |
| 148 | if(memoryfuncs) { |
| 149 | /* Setup the default memory functions here (again) */ |
| 150 | Curl_cmalloc = (curl_malloc_callback)malloc; |
| 151 | Curl_cfree = (curl_free_callback)free; |
| 152 | Curl_crealloc = (curl_realloc_callback)realloc; |
| 153 | Curl_cstrdup = (curl_strdup_callback)system_strdup; |
| 154 | Curl_ccalloc = (curl_calloc_callback)calloc; |
| 155 | } |
| 156 | |
| 157 | if(Curl_trc_init()) { |
| 158 | DEBUGF(curl_mfprintf(stderr, "Error: Curl_trc_init failed\n")); |
| 159 | goto fail; |
| 160 | } |
| 161 | |
| 162 | if(!Curl_ssl_init()) { |
| 163 | DEBUGF(curl_mfprintf(stderr, "Error: Curl_ssl_init failed\n")); |
| 164 | goto fail; |
| 165 | } |
| 166 | |
| 167 | if(!Curl_vquic_init()) { |
| 168 | DEBUGF(curl_mfprintf(stderr, "Error: Curl_vquic_init failed\n")); |
| 169 | goto fail; |
| 170 | } |
| 171 | |
| 172 | if(Curl_win32_init(flags)) { |
| 173 | DEBUGF(curl_mfprintf(stderr, "Error: win32_init failed\n")); |
| 174 | goto fail; |
| 175 | } |
| 176 | |
| 177 | if(Curl_amiga_init()) { |
| 178 | DEBUGF(curl_mfprintf(stderr, "Error: Curl_amiga_init failed\n")); |
| 179 | goto fail; |
| 180 | } |
| 181 | |
| 182 | if(Curl_macos_init()) { |
| 183 | DEBUGF(curl_mfprintf(stderr, "Error: Curl_macos_init failed\n")); |
| 184 | goto fail; |
| 185 | } |
| 186 | |
| 187 | if(Curl_async_global_init()) { |
| 188 | DEBUGF(curl_mfprintf(stderr, "Error: resolver_global_init failed\n")); |
| 189 | goto fail; |
| 190 | } |
| 191 | |
| 192 | if(Curl_ssh_init()) { |
| 193 | DEBUGF(curl_mfprintf(stderr, "Error: Curl_ssh_init failed\n")); |
| 194 | goto fail; |
| 195 | } |
| 196 | |
| 197 | easy_init_flags = flags; |
| 198 | |
| 199 | #ifdef DEBUGBUILD |
| 200 | if(getenv("CURL_GLOBAL_INIT")) |
| 201 | /* alloc data that will leak if *cleanup() is not called! */ |
| 202 | leakpointer = curlx_malloc(1); |
| 203 | #endif |
| 204 | |
| 205 | return CURLE_OK; |
| 206 | |
| 207 | fail: |
| 208 | initialized--; /* undo the increase */ |
| 209 | return CURLE_FAILED_INIT; |
| 210 | } |
Show 68 lines (not cited by the judge)
| 144 | static CURLcode global_init(long flags, bool memoryfuncs) |
| 145 | { |
| 146 | if(initialized++) |
| 147 | return CURLE_OK; |
| 148 | |
| 149 | if(memoryfuncs) { |
| 150 | /* Setup the default memory functions here (again) */ |
| 151 | Curl_cmalloc = (curl_malloc_callback)malloc; |
| 152 | Curl_cfree = (curl_free_callback)free; |
| 153 | Curl_crealloc = (curl_realloc_callback)realloc; |
| 154 | Curl_cstrdup = (curl_strdup_callback)system_strdup; |
| 155 | Curl_ccalloc = (curl_calloc_callback)calloc; |
| 156 | } |
| 157 | |
| 158 | if(Curl_trc_init()) { |
| 159 | DEBUGF(curl_mfprintf(stderr, "Error: Curl_trc_init failed\n")); |
| 160 | goto fail; |
| 161 | } |
| 162 | |
| 163 | if(!Curl_ssl_init()) { |
| 164 | DEBUGF(curl_mfprintf(stderr, "Error: Curl_ssl_init failed\n")); |
| 165 | goto fail; |
| 166 | } |
| 167 | |
| 168 | if(!Curl_vquic_init()) { |
| 169 | DEBUGF(curl_mfprintf(stderr, "Error: Curl_vquic_init failed\n")); |
| 170 | goto fail; |
| 171 | } |
| 172 | |
| 173 | if(Curl_win32_init(flags)) { |
| 174 | DEBUGF(curl_mfprintf(stderr, "Error: win32_init failed\n")); |
| 175 | goto fail; |
| 176 | } |
| 177 | |
| 178 | if(Curl_amiga_init()) { |
| 179 | DEBUGF(curl_mfprintf(stderr, "Error: Curl_amiga_init failed\n")); |
| 180 | goto fail; |
| 181 | } |
| 182 | |
| 183 | if(Curl_macos_init()) { |
| 184 | DEBUGF(curl_mfprintf(stderr, "Error: Curl_macos_init failed\n")); |
| 185 | goto fail; |
| 186 | } |
| 187 | |
| 188 | if(Curl_async_global_init()) { |
| 189 | DEBUGF(curl_mfprintf(stderr, "Error: resolver_global_init failed\n")); |
| 190 | goto fail; |
| 191 | } |
| 192 | |
| 193 | if(Curl_ssh_init()) { |
| 194 | DEBUGF(curl_mfprintf(stderr, "Error: Curl_ssh_init failed\n")); |
| 195 | goto fail; |
| 196 | } |
| 197 | |
| 198 | easy_init_flags = flags; |
| 199 | |
| 200 | #ifdef DEBUGBUILD |
| 201 | if(getenv("CURL_GLOBAL_INIT")) |
| 202 | /* alloc data that will leak if *cleanup() is not called! */ |
| 203 | leakpointer = malloc(1); |
| 204 | #endif |
| 205 | |
| 206 | return CURLE_OK; |
| 207 | |
| 208 | fail: |
| 209 | initialized--; /* undo the increase */ |
| 210 | return CURLE_FAILED_INIT; |
| 211 | } |