✓ProveIt
Examples / Report #3466883 Saved analysis

Curl Alt-Svc Parser Stack Buffer Overflow

curl · 2025-12-169.4s analysis · $0.016 in model calls

What the report claims

A critical stack buffer overflow in cURL's Alt-Svc header parser allows remote code execution via oversized Alt-Svc headers.

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

Code contradicts the claim

The code provides evidence against the report's central claim. Review the cited lines before deciding.

Why this finding?
  • The report's central claim about buffer size (101 bytes / MAX_ALTSVC_DATELEN=100) is contradicted by the provided source code which shows MAX_ALTSVC_DATELEN=256 at lib/altsvc.c:46
  • The actual Alt-Svc header parsing code that would contain the alleged memcpy is not included in the provided source snippets, so the claimed flaw cannot be verified
  • The deterministic checks show false positives (e.g., Python's struct.pack reported as a curl symbol), reducing confidence in their grounding
  • The report targets versions 7.64.0-7.89.0 but the checked revision is master at curl-8_0_0 where MAX_ALTSVC_DATELEN=256; the report's buffer size claim doesn't match the provided code at any version shown
What still needs checking
  • The Alt-Svc parsing function (likely in lib/altsvc.c) showing the memcpy and any bounds checks
  • Confirmation of MAX_ALTSVC_DATELEN value in the specific vulnerable versions (7.64.0-7.89.0)
How this analysis ran 6 claims · 1 citations verified

Checked against curl master on 2025-12-16 (7b2783496ce7). Dev 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 · 13 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
6 claims · 4372 tokens · $0.0003
3 · Evidence
6 code snippets
real curl source at 7b2783496ce7
4 · Judgment
Nemotron-3-Ultra-550b-a55b
11558 tokens · 7.3s · $0.0156
5 · Receipts
1 of 1 citations verified
every citation must point at code the judge was shown
Review a draft reply to the reporter

Edit and verify before sending.

The report claims a 101-byte stack buffer (MAX_ALTSVC_DATELEN=100), but the provided source shows MAX_ALTSVC_DATELEN=256 at lib/altsvc.c:46, making the buffer 257 bytes. The actual Alt-Svc parsing code with the alleged memcpy is not in the provided snippets. Please provide the specific parsing function and confirm the MAX_ALTSVC_DATELEN value in the versions you tested (7.64.0-7.89.0). Without the vulnerable code path, the overflow claim cannot be verified.

Inspect all claims and source code 6 claims · 6 code excerpts

Report claims

C1flaw contradicted

The code creates a fixed-size buffer on the stack: char dbuf[MAX_ALTSVC_DATELEN + 1] (101 bytes)

Read the reasoning

The report claims MAX_ALTSVC_DATELEN + 1 = 101 bytes, implying MAX_ALTSVC_DATELEN = 100. The provided source shows MAX_ALTSVC_DATELEN = 256 at lib/altsvc.c:46.

C2flaw not verifiable

It blindly copies user-controlled data into this buffer: memcpy(dbuf, Curl_dyn_ptr(&date), Curl_dyn_len(&date))

Read the reasoning

The report references memcpy with Curl_dyn_ptr and Curl_dyn_len, but the actual Alt-Svc parsing code containing this memcpy is not in the provided source snippets. The deterministic checks only confirm these symbols exist, not how they're used.

C3flaw not verifiable

There's no check to verify if the input data exceeds the buffer size

Read the reasoning

The claim about a missing bounds check depends on the parsing code which is not provided. Cannot verify from given source.

C4flaw not verifiable

An attacker can provide a date string much larger than 101 bytes, overwriting adjacent memory

Read the reasoning

Depends on the parsing code and actual buffer size. The buffer size claim (101 bytes) is contradicted by the provided code (257 bytes). The parsing code is not shown.

C5flaw not verifiable

The vulnerability is a classic stack buffer overflow (CWE-121)

Read the reasoning

Classification as CWE-121 depends on the actual flaw existing. The core buffer size claim is contradicted and the vulnerable code path is not in the provided source.

C6impact not verifiable

It can lead to complete system compromise

Read the reasoning

Impact claim without code references. Depends on the vulnerability being real and exploitable, which is not established by the provided code.

Read the original report (excerpt)

Full report on HackerOne ↗

# cURL Alt-Svc Parser Stack Buffer Overflow Vulnerability Analysis ## In Simple Terms A critical security flaw was discovered in cURL (versions 7.64.0-7.89.0) that allows attackers to run malicious code on your system by exploiting how cURL processes certain HTTP responses. When cURL receives a specially crafted HTTP response with an oversized "Alt-Svc" header, it can overflow a buffer in memory, allowing attackers to execute arbitrary code with your privileges. ## The Technical Problem The vulnerability is a classic stack buffer overflow (CWE-121) in cURL's Alt-Svc header parser: 1. The code creates a fixed-size buffer on the stack: `char dbuf[MAX_ALTSVC_DATELEN + 1]` (101 bytes) 2. It blindly copies user-controlled data into this buffer: `memcpy(dbuf, Curl_dyn_ptr(&date), Curl_dyn_len(&date))` 3. Crucially, there's **no check** to verify if the input data exceeds the buffer size 4. An attacker can provide a date string much larger than 101 bytes, overwriting adjacent memory ## Attack Scenario Here's how an attack works in practice: 1. An attacker sets up a malicious web server 2. When a victim runs `curl http://attacker.com/`, the server responds with: ``` HTTP/1.1 200 OK Alt-Svc: h2="evil.com"; date="[EXTREMELY LONG STRING WITH SHELLCODE]" ``` 3. cURL processes this response, the buffer overflows 4. The attacker's shellcode executes on the victim's machine ## Real-World Impact This vulnerability is serious because: - It affects a widely used library (libcurl) that's integrated into countless applications - It requires minimal user interaction (just making an HTTP request) - It can lead to complete system compromise - Many systems may remain unpatched ## The Fix cURL version 8.0.0 added a simple but effective check to prevent the overflow: ```c if(Curl_dyn_len(&date) > MAX_ALTSVC_DATELEN) { Curl_dyn_free(&date); return CURLE_OUT_OF_MEMORY; } ``` This validates input length before attempting the copy operation. ## Immediate Actions If you're using a vulnerable version of cURL: 1. **Update to cURL 8.0.0 or newer immediately** 2. If you can't update, disable Alt-Svc functionality: `export CURL_DISABLE_ALTSVC=1` 3. Consider implementing network filtering to detect and block suspicious Alt-Svc headers ## Detection You can check if your system is vulnerable with: ```bash curl_version=$(curl --version | head -1 | grep -o '[ …

Source code

lib/altsvc.clines 46–46 · definition of MAX_ALTSVC_DATELEN
46#define MAX_ALTSVC_DATELEN 256
C1: MAX_ALTSVC_DATELEN is defined as 256, not 100. The buffer would be 257 bytes, not 101 bytes as claimed.
lib/curl_setup.hlines 276–276 · definition of CURL_DISABLE_ALTSVC
276# define CURL_DISABLE_ALTSVC 1
src/curlinfo.clines 248–259 · definition of main
Show 12 lines (not cited by the judge)
248int main(int argc, char **argv)
249{
250 size_t i;
251
252 (void)argc;
253 (void)argv;
254
255 for(i = 0; i < CURL_ARRAYSIZE(disabled); i++)
256 puts(disabled[i]);
257
258 return 0;
259}
src/curlinfo.c@curl-8_17_0lines 241–253 · definition of main as in release curl-8_17_0; differs from master; commits that changed it since: 2399ec74c3 tidy-up: replace banned `printf()` with `puts()`
Show 13 lines (not cited by the judge)
241int main(int argc, char **argv)
242{
243 size_t i;
244
245 (void)argc;
246 (void)argv;
247
248 for(i = 0; i < CURL_ARRAYSIZE(disabled); i++)
249 /* !checksrc! disable BANNEDFUNC 1 */
250 printf("%s\n", disabled[i]);
251
252 return 0;
253}
src/tool_getparam.clines 3024–3120 · definition of parse_args
Show 97 lines (not cited by the judge)
3024ParameterError parse_args(int argc, argv_item_t argv[])
3025{
3026 int i;
3027 bool stillflags;
3028 const char *orig_opt = NULL;
3029 ParameterError result = PARAM_OK;
3030 struct OperationConfig *config = global->first;
3031
3032 for(i = 1, stillflags = TRUE; i < argc && !result; i++) {
3033 orig_opt = convert_tchar_to_UTF8(argv[i]);
3034 if(!orig_opt)
3035 return PARAM_NO_MEM;
3036
3037 if(stillflags && ('-' == orig_opt[0])) {
3038 bool passarg;
3039
3040 if(!strcmp("--", orig_opt))
3041 /* This indicates the end of the flags and thus enables the
3042 following (URL) argument to start with -. */
3043 stillflags = FALSE;
3044 else {
3045 const char *nextarg = NULL;
3046 if(i < (argc - 1)) {
3047 nextarg = convert_tchar_to_UTF8(argv[i + 1]);
3048 if(!nextarg) {
3049 unicodefree(CURL_UNCONST(orig_opt));
3050 return PARAM_NO_MEM;
3051 }
3052 }
3053
3054 result = getparameter(orig_opt, nextarg, &passarg, config,
3055 CONFIG_MAX_LEVELS);
3056
3057 unicodefree(CURL_UNCONST(nextarg));
3058 config = global->last;
3059 if(result == PARAM_NEXT_OPERATION) {
3060 /* Reset result as PARAM_NEXT_OPERATION is only used here and not
3061 returned from this function */
3062 result = PARAM_OK;
3063
3064 if(config->url_list && config->url_list->url) {
3065 /* Allocate the next config */
3066 config->next = config_alloc();
3067 if(config->next) {
3068 /* Update the last config pointer */
3069 global->last = config->next;
3070
3071 /* Move onto the new config */
3072 config->next->prev = config;
3073 config = config->next;
3074 }
3075 else
3076 result = PARAM_NO_MEM;
3077 }
3078 else {
3079 errorf("missing URL before --next");
3080 result = PARAM_BAD_USE;
3081 }
3082 }
3083 else if(!result && passarg)
3084 i++; /* we are supposed to skip this */
3085 }
3086 }
3087 else {
3088 bool used;
3089
3090 /* Just add the URL please */
3091 result = getparameter("--url", orig_opt, &used, config, 0);
3092 }
3093
3094 if(!result) {
3095 unicodefree(CURL_UNCONST(orig_opt));
3096 orig_opt = NULL;
3097 }
3098 }
3099
3100 if(!result && config->content_disposition) {
3101 if(config->resume_from_current)
3102 result = PARAM_CONTDISP_RESUME_FROM;
3103 }
3104
3105 if(result && result != PARAM_HELP_REQUESTED &&
3106 result != PARAM_MANUAL_REQUESTED &&
3107 result != PARAM_VERSION_INFO_REQUESTED &&
3108 result != PARAM_ENGINES_REQUESTED &&
3109 result != PARAM_CA_EMBED_REQUESTED) {
3110 const char *reason = param2text(result);
3111
3112 if(orig_opt && strcmp(":", orig_opt))
3113 helpf("option %s: %s", orig_opt, reason);
3114 else
3115 helpf("%s", reason);
3116 }
3117
3118 unicodefree(CURL_UNCONST(orig_opt));
3119 return result;
3120}
src/tool_getparam.c@curl-8_17_0lines 2999–3095 · definition of parse_args as in release curl-8_17_0; differs from master; commits that changed it since: 47b8e1dbd3 tidy-up: move `CURL_UNCONST()` out from macro `curl_unicodefree()`
Show 97 lines (not cited by the judge)
2999ParameterError parse_args(int argc, argv_item_t argv[])
3000{
3001 int i;
3002 bool stillflags;
3003 const char *orig_opt = NULL;
3004 ParameterError result = PARAM_OK;
3005 struct OperationConfig *config = global->first;
3006
3007 for(i = 1, stillflags = TRUE; i < argc && !result; i++) {
3008 orig_opt = convert_tchar_to_UTF8(argv[i]);
3009 if(!orig_opt)
3010 return PARAM_NO_MEM;
3011
3012 if(stillflags && ('-' == orig_opt[0])) {
3013 bool passarg;
3014
3015 if(!strcmp("--", orig_opt))
3016 /* This indicates the end of the flags and thus enables the
3017 following (URL) argument to start with -. */
3018 stillflags = FALSE;
3019 else {
3020 const char *nextarg = NULL;
3021 if(i < (argc - 1)) {
3022 nextarg = convert_tchar_to_UTF8(argv[i + 1]);
3023 if(!nextarg) {
3024 unicodefree(orig_opt);
3025 return PARAM_NO_MEM;
3026 }
3027 }
3028
3029 result = getparameter(orig_opt, nextarg, &passarg, config,
3030 CONFIG_MAX_LEVELS);
3031
3032 unicodefree(nextarg);
3033 config = global->last;
3034 if(result == PARAM_NEXT_OPERATION) {
3035 /* Reset result as PARAM_NEXT_OPERATION is only used here and not
3036 returned from this function */
3037 result = PARAM_OK;
3038
3039 if(config->url_list && config->url_list->url) {
3040 /* Allocate the next config */
3041 config->next = config_alloc();
3042 if(config->next) {
3043 /* Update the last config pointer */
3044 global->last = config->next;
3045
3046 /* Move onto the new config */
3047 config->next->prev = config;
3048 config = config->next;
3049 }
3050 else
3051 result = PARAM_NO_MEM;
3052 }
3053 else {
3054 errorf("missing URL before --next");
3055 result = PARAM_BAD_USE;
3056 }
3057 }
3058 else if(!result && passarg)
3059 i++; /* we are supposed to skip this */
3060 }
3061 }
3062 else {
3063 bool used;
3064
3065 /* Just add the URL please */
3066 result = getparameter("--url", orig_opt, &used, config, 0);
3067 }
3068
3069 if(!result) {
3070 unicodefree(orig_opt);
3071 orig_opt = NULL;
3072 }
3073 }
3074
3075 if(!result && config->content_disposition) {
3076 if(config->resume_from_current)
3077 result = PARAM_CONTDISP_RESUME_FROM;
3078 }
3079
3080 if(result && result != PARAM_HELP_REQUESTED &&
3081 result != PARAM_MANUAL_REQUESTED &&
3082 result != PARAM_VERSION_INFO_REQUESTED &&
3083 result != PARAM_ENGINES_REQUESTED &&
3084 result != PARAM_CA_EMBED_REQUESTED) {
3085 const char *reason = param2text(result);
3086
3087 if(orig_opt && strcmp(":", orig_opt))
3088 helpf("option %s: %s", orig_opt, reason);
3089 else
3090 helpf("%s", reason);
3091 }
3092
3093 unicodefree(orig_opt);
3094 return result;
3095}