CVE-2024-7264: ASN.1 date parser overread
What the report claims
A specially-crafted certificate causes a stack-buffer-overflow in curl's ASN.1 date parser when using gnutls, leading to out-of-bounds reads during certificate parsing.
Compare with curl's recorded outcome
Known vulnerability
A real vulnerability: CVE-2024-7264 ASN.1 date parser overread
Shown for comparison after analysis. The pipeline did not see this label.
ProveIt's finding
The code does what the report says, but curl's own policy may exclude it. A maintainer decides.
curl policy: NULL dereferences and crashesSee the withdrawn model reasoning
The following reasoning failed the citation check and does not support the final finding.
- The code confirms a real buffer over-read in GTime2str when a certificate ends with a fractional indicator ('.' or ',') and no digits: fracl becomes SIZE_MAX, casts to -1 for %.*s, causing strlen(fracp) to read past the buffer.
- However, curl's documented policy explicitly states: 'If a malicious server can trigger a NULL dereference in curl or otherwise cause curl to crash (and nothing worse), chances are big that we do not consider that a security problem.' This is a crash (DoS) from a malicious server certificate with no demonstrated data leak or code execution.
- The report itself notes: 'I couldn't see a way where the remote attacker can actually get the content of the over-read memory bytes.'
How this analysis ran 5 claims · 17 citations verified
Evidence check: the behavior is real; the project's policy section 'NULL dereferences and crashes' may exclude it
Checked against curl tested commit 2a59c8d4cebf (2a59c8d4cebf). Test 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.
Thank you for the detailed report and reproduction case. The code confirms the buffer over-read in GTime2str when a certificate ends with '.' or ',' and no fractional digits: fracl wraps to SIZE_MAX, casts to -1 for the %.*s format, and strlen reads past the buffer. However, per curl's vulnerability disclosure policy (section 'NULL dereferences and crashes'), a crash triggered by a malicious server certificate—without data leakage or code execution—is not treated as a security vulnerability. We would still fix the bug as a robustness issue, but it does not qualify for a CVE or security advisory. If you can demonstrate a path to information disclosure or code execution, please share those details.
Inspect all claims and source code 5 claims · 3 code excerpts
This model judgment was withdrawn. The claim assessments below are preserved for inspection and may be wrong.
Report claims
The loop in GTime2str may set fracl to -1 when tzp == fracp, causing Curl_dyn_addf to treat it as 'no length given' and run strlen(tzp) beyond the buffer
Read the reasoning
When a certificate ends with '.' or ',' (fractional indicator) and no digits follow, tzp==fracp at line 525, making fracl = SIZE_MAX. Cast to int for %.*s gives -1, which formatf treats as 'no precision' and calls strlen(fracp). Since fracp==end (just past the certificate buffer), strlen reads out of bounds. The loop at line 525 also reads fracp[SIZE_MAX-1], but the observed crash is in strlen from the format call.
The flaw occurs in lib/vtls/x509asn1.c at line 525 in the loop initializing fracl
Read the reasoning
The loop at line 525 is where fracl becomes SIZE_MAX when tzp==fracp. This line is correctly identified as the origin of the oversized fracl value.
A specially-crafted certificate causes tzp to equal fracp, setting fracl to -1
Read the reasoning
A certificate where the fractional indicator ('.' or ',') is the last byte causes fracp to advance to end, tzp to advance to end (one increment), making tzp==fracp==end at line 525, so fracl = SIZE_MAX.
The out-of-bounds strlen(tzp) read overflows the certificate buffer, causing a stack-buffer-overflow
Read the reasoning
The crash is in strlen(fracp) (not strlen(tzp) as the report states) when %.*s receives -1 precision. The AddressSanitizer stack trace confirms strlen reading 4471 bytes starting just past the stack buffer (offset 8224), matching fracp==end.
Compile curl with gnutls and AddressSanitizer, then run ./poc bad_cert_1.bin to trigger the overflow
Read the reasoning
Reproduction steps involve external files (poc.c, bad_cert_1.bin) not provided in the source code. The deterministic checks confirm the relevant functions exist at the tested revision, but the PoC cannot be verified from the given material.
Deterministic checks that failed
- quote only 0/2 quoted lines appear anywhere in curl at 2a59c8d4cebf
Read the original report (excerpt)
Source code
| 223 | CURLcode Curl_dyn_addf(struct dynbuf *s, const char *fmt, ...) |
| 224 | { |
| 225 | CURLcode result; |
| 226 | va_list ap; |
| 227 | DEBUGASSERT(s); |
| 228 | DEBUGASSERT(s->init == DYNINIT); |
| 229 | DEBUGASSERT(!s->leng || s->bufr); |
| 230 | va_start(ap, fmt); |
| 231 | result = Curl_dyn_vaddf(s, fmt, ap); |
| C1: Curl_dyn_addf calls Curl_dyn_vaddf which leads to formatf/strlen | |
| C4: Curl_dyn_addf -> Curl_dyn_vaddf -> formatf -> strlen | |
| 232 | va_end(ap); |
| 233 | return result; |
| 234 | } |
| 484 | static CURLcode GTime2str(struct dynbuf *store, |
| 485 | const char *beg, const char *end) |
| 486 | { |
| 487 | const char *tzp; |
| 488 | const char *fracp; |
| 489 | char sec1, sec2; |
| 490 | size_t fracl; |
| C1: fracl declared as size_t (unsigned) | |
| 491 | size_t tzl; |
| 492 | const char *sep = ""; |
| 493 | |
| 494 | /* Convert an ASN.1 Generalized time to a printable string. |
| 495 | Return the dynamically allocated string, or NULL if an error occurs. */ |
| 496 | |
| 497 | for(fracp = beg; fracp < end && *fracp >= '0' && *fracp <= '9'; fracp++) |
| 498 | ; |
| 499 | |
| 500 | /* Get seconds digits. */ |
| 501 | sec1 = '0'; |
| 502 | switch(fracp - beg - 12) { |
| 503 | case 0: |
| 504 | sec2 = '0'; |
| 505 | break; |
| 506 | case 2: |
| 507 | sec1 = fracp[-2]; |
| 508 | FALLTHROUGH(); |
| 509 | case 1: |
| 510 | sec2 = fracp[-1]; |
| 511 | break; |
| 512 | default: |
| 513 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 514 | } |
| 515 | |
| 516 | /* Scan for timezone, measure fractional seconds. */ |
| 517 | tzp = fracp; |
| C1: tzp = fracp; fracl = 0; | |
| C3: tzp = fracp initially | |
| 518 | fracl = 0; |
| 519 | if(fracp < end && (*fracp == '.' || *fracp == ',')) { |
| C1: if block for fractional part entered when '.' or ',' present | |
| C3: if block entered for '.' or ',' | |
| 520 | fracp++; |
| C1: fracp++ advances past '.' or ',' | |
| C3: fracp++ | |
| 521 | do |
| C1: do-while advances tzp at least once | |
| C3: do tzp++ while loop | |
| 522 | tzp++; |
| 523 | while(tzp < end && *tzp >= '0' && *tzp <= '9'); |
| 524 | /* Strip leading zeroes in fractional seconds. */ |
| 525 | for(fracl = tzp - fracp - 1; fracl && fracp[fracl - 1] == '0'; fracl--) |
| C1: fracl = tzp - fracp - 1 becomes SIZE_MAX when tzp==fracp (no digits after indicator) | |
| C2: for(fracl = tzp - fracp - 1; fracl && fracp[fracl - 1] == '0'; fracl--) | |
| C3: fracl = tzp - fracp - 1 | |
| 526 | ; |
| 527 | } |
| 528 | |
| 529 | /* Process timezone. */ |
| 530 | if(tzp >= end) |
| 531 | ; /* Nothing to do. */ |
| 532 | else if(*tzp == 'Z') { |
| 533 | tzp = " GMT"; |
| 534 | end = tzp + 4; |
| 535 | } |
| 536 | else { |
| 537 | sep = " "; |
| 538 | tzp++; |
| 539 | } |
| 540 | |
| 541 | tzl = end - tzp; |
| C4: tzl = end - tzp (not the source of the crash) | |
| 542 | return Curl_dyn_addf(store, |
| C1: Curl_dyn_addf called with (int)fracl for %.*s precision | |
| C4: Curl_dyn_addf with %.*s using (int)fracl and fracp | |
| 543 | "%.4s-%.2s-%.2s %.2s:%.2s:%c%c%s%.*s%s%.*s", |
| 544 | beg, beg + 4, beg + 6, |
| 545 | beg + 8, beg + 10, sec1, sec2, |
| 546 | fracl? ".": "", (int)fracl, fracp, |
| 547 | sep, (int)tzl, tzp); |
| 548 | } |
Show 161 lines (not cited by the judge)
| 1056 | CURLcode Curl_extract_certinfo(struct Curl_easy *data, |
| 1057 | int certnum, |
| 1058 | const char *beg, |
| 1059 | const char *end) |
| 1060 | { |
| 1061 | struct Curl_X509certificate cert; |
| 1062 | struct Curl_asn1Element param; |
| 1063 | char *certptr; |
| 1064 | size_t clen; |
| 1065 | struct dynbuf out; |
| 1066 | CURLcode result = CURLE_OK; |
| 1067 | unsigned int version; |
| 1068 | const char *ptr; |
| 1069 | int rc; |
| 1070 | |
| 1071 | if(!data->set.ssl.certinfo) |
| 1072 | if(certnum) |
| 1073 | return CURLE_OK; |
| 1074 | |
| 1075 | Curl_dyn_init(&out, MAX_X509_STR); |
| 1076 | /* Prepare the certificate information for curl_easy_getinfo(). */ |
| 1077 | |
| 1078 | /* Extract the certificate ASN.1 elements. */ |
| 1079 | if(Curl_parseX509(&cert, beg, end)) |
| 1080 | return CURLE_PEER_FAILED_VERIFICATION; |
| 1081 | |
| 1082 | /* Subject. */ |
| 1083 | result = DNtostr(&out, &cert.subject); |
| 1084 | if(result) |
| 1085 | goto done; |
| 1086 | if(data->set.ssl.certinfo) { |
| 1087 | result = ssl_push_certinfo_dyn(data, certnum, "Subject", &out); |
| 1088 | if(result) |
| 1089 | goto done; |
| 1090 | } |
| 1091 | Curl_dyn_reset(&out); |
| 1092 | |
| 1093 | /* Issuer. */ |
| 1094 | result = DNtostr(&out, &cert.issuer); |
| 1095 | if(result) |
| 1096 | goto done; |
| 1097 | if(data->set.ssl.certinfo) { |
| 1098 | result = ssl_push_certinfo_dyn(data, certnum, "Issuer", &out); |
| 1099 | if(result) |
| 1100 | goto done; |
| 1101 | } |
| 1102 | Curl_dyn_reset(&out); |
| 1103 | |
| 1104 | /* Version (always fits in less than 32 bits). */ |
| 1105 | version = 0; |
| 1106 | for(ptr = cert.version.beg; ptr < cert.version.end; ptr++) |
| 1107 | version = (version << 8) | *(const unsigned char *) ptr; |
| 1108 | if(data->set.ssl.certinfo) { |
| 1109 | result = Curl_dyn_addf(&out, "%x", version); |
| 1110 | if(result) |
| 1111 | goto done; |
| 1112 | result = ssl_push_certinfo_dyn(data, certnum, "Version", &out); |
| 1113 | if(result) |
| 1114 | goto done; |
| 1115 | Curl_dyn_reset(&out); |
| 1116 | } |
| 1117 | |
| 1118 | /* Serial number. */ |
| 1119 | result = ASN1tostr(&out, &cert.serialNumber, 0); |
| 1120 | if(result) |
| 1121 | goto done; |
| 1122 | if(data->set.ssl.certinfo) { |
| 1123 | result = ssl_push_certinfo_dyn(data, certnum, "Serial Number", &out); |
| 1124 | if(result) |
| 1125 | goto done; |
| 1126 | } |
| 1127 | Curl_dyn_reset(&out); |
| 1128 | |
| 1129 | /* Signature algorithm .*/ |
| 1130 | result = dumpAlgo(&out, ¶m, cert.signatureAlgorithm.beg, |
| 1131 | cert.signatureAlgorithm.end); |
| 1132 | if(result) |
| 1133 | goto done; |
| 1134 | if(data->set.ssl.certinfo) { |
| 1135 | result = ssl_push_certinfo_dyn(data, certnum, "Signature Algorithm", |
| 1136 | &out); |
| 1137 | if(result) |
| 1138 | goto done; |
| 1139 | } |
| 1140 | Curl_dyn_reset(&out); |
| 1141 | |
| 1142 | /* Start Date. */ |
| 1143 | result = ASN1tostr(&out, &cert.notBefore, 0); |
| 1144 | if(result) |
| 1145 | goto done; |
| 1146 | if(data->set.ssl.certinfo) { |
| 1147 | result = ssl_push_certinfo_dyn(data, certnum, "Start Date", &out); |
| 1148 | if(result) |
| 1149 | goto done; |
| 1150 | } |
| 1151 | Curl_dyn_reset(&out); |
| 1152 | |
| 1153 | /* Expire Date. */ |
| 1154 | result = ASN1tostr(&out, &cert.notAfter, 0); |
| 1155 | if(result) |
| 1156 | goto done; |
| 1157 | if(data->set.ssl.certinfo) { |
| 1158 | result = ssl_push_certinfo_dyn(data, certnum, "Expire Date", &out); |
| 1159 | if(result) |
| 1160 | goto done; |
| 1161 | } |
| 1162 | Curl_dyn_reset(&out); |
| 1163 | |
| 1164 | /* Public Key Algorithm. */ |
| 1165 | result = dumpAlgo(&out, ¶m, cert.subjectPublicKeyAlgorithm.beg, |
| 1166 | cert.subjectPublicKeyAlgorithm.end); |
| 1167 | if(result) |
| 1168 | goto done; |
| 1169 | if(data->set.ssl.certinfo) { |
| 1170 | result = ssl_push_certinfo_dyn(data, certnum, "Public Key Algorithm", |
| 1171 | &out); |
| 1172 | if(result) |
| 1173 | goto done; |
| 1174 | } |
| 1175 | |
| 1176 | rc = do_pubkey(data, certnum, Curl_dyn_ptr(&out), |
| 1177 | ¶m, &cert.subjectPublicKey); |
| 1178 | if(rc) { |
| 1179 | result = CURLE_OUT_OF_MEMORY; /* the most likely error */ |
| 1180 | goto done; |
| 1181 | } |
| 1182 | Curl_dyn_reset(&out); |
| 1183 | |
| 1184 | /* Signature. */ |
| 1185 | result = ASN1tostr(&out, &cert.signature, 0); |
| 1186 | if(result) |
| 1187 | goto done; |
| 1188 | if(data->set.ssl.certinfo) { |
| 1189 | result = ssl_push_certinfo_dyn(data, certnum, "Signature", &out); |
| 1190 | if(result) |
| 1191 | goto done; |
| 1192 | } |
| 1193 | Curl_dyn_reset(&out); |
| 1194 | |
| 1195 | /* Generate PEM certificate. */ |
| 1196 | result = Curl_base64_encode(cert.certificate.beg, |
| 1197 | cert.certificate.end - cert.certificate.beg, |
| 1198 | &certptr, &clen); |
| 1199 | if(result) |
| 1200 | goto done; |
| 1201 | |
| 1202 | /* Generate the final output certificate string. Format is: |
| 1203 | -----BEGIN CERTIFICATE-----\n |
| 1204 | <max 64 base64 characters>\n |
| 1205 | . |
| 1206 | . |
| 1207 | . |
| 1208 | -----END CERTIFICATE-----\n |
| 1209 | */ |
| 1210 | |
| 1211 | Curl_dyn_reset(&out); |
| 1212 | |
| 1213 | /* Build the certificate string. */ |
| 1214 | result = Curl_dyn_add(&out, "-----BEGIN CERTIFICATE-----\n"); |
| 1215 | if(!result) { |
| 1216 | size_t j = 0; |