✓ProveIt
Examples / Report #3462525 Saved analysis

Buffer Overflow in cURL Internal printf Function

curl · 2025-12-1222.1s analysis · $0.018 in model calls

What the report claims

The report claims a buffer overflow in curl_msprintf() due to lack of bounds checking when writing to a user-provided buffer.

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 a policy review

The code does what the report says, but curl's own policy may exclude it. A maintainer decides.

curl policy: API misuse
Judgment withdrawn. The answer failed the evidence check.
See the withdrawn model reasoning

The following reasoning failed the citation check and does not support the final finding.

  • curl_msprintf is a public API function analogous to sprintf: it takes a buffer pointer but no size parameter, and the caller is responsible for providing a sufficiently large buffer. This is documented API behavior, not a flaw.
  • The project policy explicitly states: 'If a reported issue only triggers by an application using the API in a way that is not documented to work or even documented to not work, it is probably not going to be considered a security problem. We only guarantee secure and proper functionality when the APIs are used as expected and documented.'
  • The PoC demonstrates API misuse (passing a 16-byte buffer with a format string producing >16 bytes), not a vulnerability in curl. The attack vector 'Network' is unsubstantiated—no path from network input to curl_msprintf with attacker-controlled format string and fixed buffer is shown.
  • curl likely provides a bounded variant (curl_msnprintf) for safe use, though not in the provided code excerpt.
What still needs checking
  • Documentation of curl_msprintf's contract (whether it promises bounds checking or requires caller to ensure buffer size)
  • Identification of any internal curl function that calls curl_msprintf with a fixed-size buffer and attacker-controlled format string
How this analysis ran 6 claims · 8 citations verified

Evidence check: the behavior is real; the project's policy section 'API misuse' may exclude it

Checked against curl master on 2025-12-12 (fe8393d7db8c). 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 · 9 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
6 claims · 1779 tokens · $0.0002
3 · Evidence
4 code snippets
real curl source at fe8393d7db8c
4 · Judgment
Nemotron-3-Ultra-550b-a55b
11005 tokens · 20.1s · $0.0178
5 · Receipts
8 of 8 citations verified
every citation must point at code the judge was shown
Inspect the withdrawn draft reply

This draft belongs to the withdrawn judgment. It may contain unsupported claims.

Thank you for the report. curl_msprintf() is a public API function designed like sprintf(): it takes a buffer pointer but no size parameter, and the caller must ensure the buffer is large enough for the formatted output. This is documented API behavior, not a vulnerability. Our policy treats API misuse as a non-security issue. The PoC demonstrates passing a too-small buffer, which is incorrect use of the API. If you have found an internal curl code path where a fixed-size buffer is passed to curl_msprintf with attacker-controlled format data, please share that specific call chain.

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

This model judgment was withdrawn. The claim assessments below are preserved for inspection and may be wrong.

Report claims

C1flaw supported

curl_msprintf() writes formatted output to a user-provided buffer without performing any bounds checking

Read the reasoning

The code confirms curl_msprintf() provides no bounds checking. The storebuffer callback (lines 1195-1201) writes each output character to the user-provided buffer and increments the pointer without any size validation. curl_msprintf (lines 1203-1212) passes the buffer pointer to formatf but never supplies a buffer length.

C2location supported

File lib/mprintf.c, function curl_msprintf()

Read the reasoning

Deterministic checks confirm the file and function exist at the reported revision.

C3trigger not verifiable

Providing a malicious format string to any cURL function that internally uses curl_msprintf()

Read the reasoning

The report claims any cURL function using curl_msprintf internally can be attacked via malicious format strings, but no such internal callers are identified in the provided code. The PoC calls curl_msprintf directly, which is a public API function. Whether network input reaches curl_msprintf with attacker-controlled format strings and fixed buffers is not demonstrated.

C4impact not verifiable

Potential arbitrary code execution

Read the reasoning

Arbitrary code execution is a theoretical consequence of buffer overflow, but exploitability depends on mitigations (stack canaries, ASLR, etc.) not in scope. The report provides no exploit demonstration.

C5reproduction supported

Using a long format string that generates output larger than the target buffer

Read the reasoning

formatf processes format strings and writes output character by character via the stream callback. A format string with many literal characters or %s with a long argument will generate output exceeding any fixed buffer size.

C6impact supported

Buffer overflow occurs - writes beyond 16-byte buffer

Read the reasoning

The PoC demonstrates the overflow: a 16-byte buffer receives more than 16 bytes of formatted output because storebuffer performs no bounds check.

Read the original report (excerpt)

Full report on HackerOne ↗

A critical buffer overflow vulnerability exists in the `curl_msprintf()` function in cURL's internal printf implementation. The function writes formatted output to a user-provided buffer without performing any bounds checking, allowing attackers to overflow arbitrary memory and potentially achieve arbitrary code execution. ## Affected Version Current master branch (commit 141ce4be64) and potentially earlier versions ## CVSS Score Breakdown **Base Score: 9.8 (Critical)** - **Attack Vector (AV):** Network (N) - **Attack Complexity (AC):** Low (L) - **Privileges Required (PR):** None (N) - **User Interaction (UI):** None (N) - **Scope (S):** Unchanged (U) - **Confidentiality (C):** High (H) - **Integrity (I):** High (H) - **Availability (A):** High (H) ## Vulnerability Details ### Location - **File:** `lib/mprintf.c` - **Function:** `curl_msprintf()` - **Lines:** 1203-1211 ### Root Cause The vulnerability occurs because `curl_msprintf()` calls `formatf()` with the `storebuffer()` callback function, which writes directly to the provided buffer without any size validation: ```c int curl_msprintf(char *buffer, const char *format, ...) { va_list ap_save; /* argument pointer */ int retcode; va_start(ap_save, format); retcode = formatf(&buffer, storebuffer, format, ap_save); va_end(ap_save); *buffer = 0; /* we terminate this with a zero byte */ return retcode; } static int storebuffer(unsigned char outc, void *f) { char **buffer = f; **buffer = (char)outc; // VULNERABILITY: No bounds checking (*buffer)++; return 0; } ``` ### Attack Vector An attacker can trigger this vulnerability by: 1. Providing a malicious format string to any cURL function that internally uses `curl_msprintf()` 2. Supplying format specifiers that generate output larger than the target buffer 3. Causing stack corruption and potentially hijacking control flow ## Proof of Concept ### Test Code ```python import ctypes # Load libcurl libcurl = ctypes.CDLL("libcurl.so.4") curl_msprintf = libcurl.curl_msprintf # Create small buffer (16 bytes) buffer = ctypes.create_string_buffer(16) # Trigger overflow with long format string long_format = b"A" * 100 + b"%s" + b"B" * 50 result = curl_msprintf(buffer, long_format) print(f"Result: {result}") print(f"Buffer content: {buffer.value}") # Buffer overflow occurs - writes beyond 16-byte buffer ``` ## Impact ### Business Imp …

Source code

lib/mprintf.clines 1195–1201 · definition of storebuffer
1195static int storebuffer(unsigned char outc, void *f)
1196{
1197 char **buffer = f;
1198 **buffer = (char)outc;
C1: storebuffer writes each character to the buffer without any bounds check
C6: storebuffer writes unconditionally to buffer
1199 (*buffer)++;
C1: Pointer incremented without validating remaining buffer space
C6: Pointer advanced unconditionally
1200 return 0;
1201}
lib/mprintf.clines 1203–1212 · definition of curl_msprintf
1203int curl_msprintf(char *buffer, const char *format, ...)
C2: curl_msprintf function definition at line 1203 in lib/mprintf.c
1204{
1205 va_list ap_save; /* argument pointer */
1206 int retcode;
1207 va_start(ap_save, format);
1208 retcode = formatf(&buffer, storebuffer, format, ap_save);
C1: curl_msprintf calls formatf with storebuffer callback, passing buffer pointer but no size
1209 va_end(ap_save);
1210 *buffer = 0; /* we terminate this with a zero byte */
1211 return retcode;
1212}
lib/mprintf.clines 968–1090 · definition of formatf
968static int formatf(void *userp, /* untouched by format(), just sent to the
969 stream() function in the second argument */
970 /* function pointer called for each output character */
971 int (*stream)(unsigned char, void *),
972 const char *format, /* %-formatted string */
973 va_list ap_save) /* list of parameters */
974{
975 int done = 0; /* number of characters written */
976 int i;
977 int ocount = 0; /* number of output segments */
978 int icount = 0; /* number of input arguments */
979
980 struct outsegment output[MAX_SEGMENTS];
981 struct va_input input[MAX_PARAMETERS];
982 char work[BUFFSIZE + 2];
983
984 /* Parse the format string */
985 if(parsefmt(format, output, input, &ocount, &icount, ap_save))
986 return 0;
987
988 for(i = 0; i < ocount; i++) {
989 struct outsegment *optr = &output[i];
990 struct va_input *iptr = &input[optr->input];
991 struct mproperty p;
992 size_t outlen = optr->outlen;
993
994 if(outlen) {
995 const char *str = optr->start;
996 for(; outlen && *str; outlen--) {
C5: formatf loops writing each character via stream callback (storebuffer) for literal format string content
997 if(stream(*str++, userp))
998 return done;
999 done++;
1000 }
1001 if(optr->flags & FLAGS_SUBSTR)
1002 /* this is just a substring */
1003 continue;
1004 }
1005
1006 p.flags = optr->flags;
1007
1008 /* pick up the specified width */
1009 if(p.flags & FLAGS_WIDTHPARAM) {
1010 p.width = (int)input[optr->width].val.nums;
1011 if(p.width < 0) {
1012 /* "A negative field width is taken as a '-' flag followed by a
1013 positive field width." */
1014 if(p.width == INT_MIN)
1015 p.width = INT_MAX;
1016 else
1017 p.width = -p.width;
1018 p.flags |= FLAGS_LEFT;
1019 p.flags &= ~(unsigned int)FLAGS_PAD_NIL;
1020 }
1021 }
1022 else
1023 p.width = optr->width;
1024
1025 /* pick up the specified precision */
1026 if(p.flags & FLAGS_PRECPARAM) {
1027 p.prec = (int)input[optr->precision].val.nums;
1028 if(p.prec < 0)
1029 /* "A negative precision is taken as if the precision were
1030 omitted." */
1031 p.prec = -1;
1032 }
1033 else if(p.flags & FLAGS_PREC)
1034 p.prec = optr->precision;
1035 else
1036 p.prec = -1;
1037
1038 switch(iptr->type) {
1039 case FORMAT_INTU:
1040 case FORMAT_LONGU:
1041 case FORMAT_LONGLONGU:
1042 p.flags |= FLAGS_UNSIGNED;
1043 if(out_number(userp, stream, &p, iptr->val.numu, 0, work, &done))
1044 return done;
1045 break;
1046
1047 case FORMAT_INT:
1048 case FORMAT_LONG:
1049 case FORMAT_LONGLONG:
1050 if(out_number(userp, stream, &p, iptr->val.numu,
1051 iptr->val.nums, work, &done))
1052 return done;
1053 break;
1054
C5: out_string called for %s format specifier, can write arbitrary-length strings
1055 case FORMAT_STRING:
1056 if(out_string(userp, stream, &p, iptr->val.str, &done))
1057 return done;
1058 break;
1059
1060 case FORMAT_PTR:
1061 if(out_pointer(userp, stream, &p, iptr->val.ptr, work, &done))
1062 return done;
1063 break;
1064
1065 case FORMAT_DOUBLE:
1066 if(out_double(userp, stream, &p, iptr->val.dnum, work, &done))
1067 return done;
1068 break;
1069
1070 case FORMAT_INTPTR:
1071 /* Answer the count of characters written. */
1072#ifdef HAVE_LONG_LONG_TYPE
1073 if(p.flags & FLAGS_LONGLONG)
1074 *(LONG_LONG_TYPE *)iptr->val.ptr = (LONG_LONG_TYPE)done;
1075 else
1076#endif
1077 if(p.flags & FLAGS_LONG)
1078 *(long *)iptr->val.ptr = (long)done;
1079 else if(!(p.flags & FLAGS_SHORT))
1080 *(int *)iptr->val.ptr = (int)done;
1081 else
1082 *(short *)iptr->val.ptr = (short)done;
1083 break;
1084
1085 default:
1086 break;
1087 }
1088 }
1089 return done;
1090}
lib/mprintf.c@curl-8_17_0lines 965–1088 · definition of formatf as in release curl-8_17_0; differs from master; commits that changed it since: c3b030b860 lib: fix formatting nits (part 3)
Show 124 lines (not cited by the judge)
965static int formatf(
966 void *userp, /* untouched by format(), just sent to the stream() function in
967 the second argument */
968 /* function pointer called for each output character */
969 int (*stream)(unsigned char, void *),
970 const char *format, /* %-formatted string */
971 va_list ap_save) /* list of parameters */
972{
973 int done = 0; /* number of characters written */
974 int i;
975 int ocount = 0; /* number of output segments */
976 int icount = 0; /* number of input arguments */
977
978 struct outsegment output[MAX_SEGMENTS];
979 struct va_input input[MAX_PARAMETERS];
980 char work[BUFFSIZE + 2];
981
982 /* Parse the format string */
983 if(parsefmt(format, output, input, &ocount, &icount, ap_save))
984 return 0;
985
986 for(i = 0; i < ocount; i++) {
987 struct outsegment *optr = &output[i];
988 struct va_input *iptr = &input[optr->input];
989 struct mproperty p;
990 size_t outlen = optr->outlen;
991
992 if(outlen) {
993 const char *str = optr->start;
994 for(; outlen && *str; outlen--) {
995 if(stream(*str++, userp))
996 return done;
997 done++;
998 }
999 if(optr->flags & FLAGS_SUBSTR)
1000 /* this is just a substring */
1001 continue;
1002 }
1003
1004 p.flags = optr->flags;
1005
1006 /* pick up the specified width */
1007 if(p.flags & FLAGS_WIDTHPARAM) {
1008 p.width = (int)input[optr->width].val.nums;
1009 if(p.width < 0) {
1010 /* "A negative field width is taken as a '-' flag followed by a
1011 positive field width." */
1012 if(p.width == INT_MIN)
1013 p.width = INT_MAX;
1014 else
1015 p.width = -p.width;
1016 p.flags |= FLAGS_LEFT;
1017 p.flags &= ~(unsigned int)FLAGS_PAD_NIL;
1018 }
1019 }
1020 else
1021 p.width = optr->width;
1022
1023 /* pick up the specified precision */
1024 if(p.flags & FLAGS_PRECPARAM) {
1025 p.prec = (int)input[optr->precision].val.nums;
1026 if(p.prec < 0)
1027 /* "A negative precision is taken as if the precision were
1028 omitted." */
1029 p.prec = -1;
1030 }
1031 else if(p.flags & FLAGS_PREC)
1032 p.prec = optr->precision;
1033 else
1034 p.prec = -1;
1035
1036 switch(iptr->type) {
1037 case FORMAT_INTU:
1038 case FORMAT_LONGU:
1039 case FORMAT_LONGLONGU:
1040 p.flags |= FLAGS_UNSIGNED;
1041 if(out_number(userp, stream, &p, iptr->val.numu, 0, work, &done))
1042 return done;
1043 break;
1044
1045 case FORMAT_INT:
1046 case FORMAT_LONG:
1047 case FORMAT_LONGLONG:
1048 if(out_number(userp, stream, &p, iptr->val.numu,
1049 iptr->val.nums, work, &done))
1050 return done;
1051 break;
1052
1053 case FORMAT_STRING:
1054 if(out_string(userp, stream, &p, iptr->val.str, &done))
1055 return done;
1056 break;
1057
1058 case FORMAT_PTR:
1059 if(out_pointer(userp, stream, &p, iptr->val.ptr, work, &done))
1060 return done;
1061 break;
1062
1063 case FORMAT_DOUBLE:
1064 if(out_double(userp, stream, &p, iptr->val.dnum, work, &done))
1065 return done;
1066 break;
1067
1068 case FORMAT_INTPTR:
1069 /* Answer the count of characters written. */
1070#ifdef HAVE_LONG_LONG_TYPE
1071 if(p.flags & FLAGS_LONGLONG)
1072 *(LONG_LONG_TYPE *) iptr->val.ptr = (LONG_LONG_TYPE)done;
1073 else
1074#endif
1075 if(p.flags & FLAGS_LONG)
1076 *(long *) iptr->val.ptr = (long)done;
1077 else if(!(p.flags & FLAGS_SHORT))
1078 *(int *) iptr->val.ptr = (int)done;
1079 else
1080 *(short *) iptr->val.ptr = (short)done;
1081 break;
1082
1083 default:
1084 break;
1085 }
1086 }
1087 return done;
1088}