✓ProveIt
Examples / Report #1555441 Saved analysis

CVE-2022-27781: CERTINFO never-ending busy-loop

curl · 2022-04-3015.0s analysis · $0.013 in model calls

What the report claims

Curl is prone to a DoS attack in case the NSS TLS library is used and the CERTINFO option is enabled. Using maliciously crafted certificates on a server, an attacker can make curl run into an endless loop when connecting to the server.

Compare with curl's recorded outcome

Known vulnerability

A real vulnerability: CVE-2022-27781 CERTINFO never-ending busy-loop

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

ProveIt's finding

Code supports the claim

The code supports the reported flaw. A maintainer should review its impact and decide what to do next.

Why this finding?
  • The while loop at lib/vtls/nss.c:1019-1028 traverses the certificate issuer chain without any cycle detection.
  • If a server presents a certificate chain with a loop (mutual issuers), CERT_FindCertIssuer will keep returning certificates indefinitely.
  • The only loop exit conditions are cert2 becoming NULL or cert2->isRoot being true; neither occurs in a cyclic chain.
  • The vulnerability is reachable when CERTINFO is enabled (line 1009) and the NSS backend is used.
  • An attacker controlling a TLS server can trigger this DoS against any libcurl application built with NSS and CERTINFO enabled.
How this analysis ran 6 claims · 14 citations verified

Checked against curl master on 2022-04-30 (6d8619337769). 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
partially grounded
no model · 9 references checked out, 1 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
6 claims · 1897 tokens · $0.0002
3 · Evidence
3 code snippets
real curl source at 6d8619337769 · 3 docs via Tavily
4 · Judgment
Nemotron-3-Ultra-550b-a55b
7842 tokens · 9.1s · $0.0127
5 · Receipts
14 of 14 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 and PoC. The infinite loop in display_conn_info() when processing a cyclic certificate chain is confirmed in lib/vtls/nss.c lines 1019-1028. The while loop lacks cycle detection, so a malicious server presenting mutually-issued CA certificates causes a DoS. We will add a visited-certificate check to bound the traversal.

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

Report claims

C1flaw supported

display_conn_info() runs into an endless loop when the received certificate chain contains a loop

Read the reasoning

The while loop at lines 1019-1028 traverses the issuer chain using CERT_FindCertIssuer but maintains no visited-certificate set. If the chain contains a cycle (e.g., CA1 issued by CA2, CA2 issued by CA1), cert2 never becomes NULL and cert2->isRoot is never true, so the loop never terminates.

C2location supported

display_conn_info() starts with the leaf certificate and attempts to find its issuer certificate in the chain

Read the reasoning

The function starts with the peer (leaf) certificate at line 1005 and immediately attempts to find its issuer at line 1018.

C3trigger supported

certificate chain contains a loop

Read the reasoning

The loop condition at line 1019 and the repeated issuer lookup at line 1025 will follow a cyclic chain indefinitely because there is no cycle detection.

C4impact supported

application runs into an infinite loop and consumes nearly 100% CPU

Read the reasoning

An infinite loop with no blocking calls (only CERT_FindCertIssuer and CERT_DestroyCertificate) will consume 100% CPU.

C5reproduction supported

modify URL in certinfo example to point to https://localhost:4443/ and execute certinfo

Read the reasoning

The example enables CERTINFO and sets a URL; changing the URL to the malicious server reproduces the issue.

C6reproduction supported

build curl with NSS TLS library and with examples

Read the reasoning

The vulnerable code is in the NSS-specific TLS backend (lib/vtls/nss.c), so building with --with-nss is required.

Deterministic checks that failed

  • file lib/vtls/nss.c existed in curl's history but not at bc677a8f16a5
Read the original report (excerpt)

Full report on HackerOne ↗

## Summary: Curl is prone to a DoS attack in case the NSS TLS library is used and the CERTINFO option is enabled. Using maliciously crafted certificates on a server, an attacker can make curl run into an endless loop when connecting to the server. The bug is located in the following code segment (https://github.com/curl/curl/blob/master/lib/vtls/nss.c#L1014): ``` /* Count certificates in chain. */ int i = 1; now = PR_Now(); if(!cert->isRoot) { cert2 = CERT_FindCertIssuer(cert, now, certUsageSSLCA); while(cert2) { i++; if(cert2->isRoot) { CERT_DestroyCertificate(cert2); break; } cert3 = CERT_FindCertIssuer(cert2, now, certUsageSSLCA); CERT_DestroyCertificate(cert2); cert2 = cert3; } } ``` When CERTINFO is set, display_conn_info() executes the above shown code, which tries to count the certificates in the chain received from servers via TLS. To this end, display_conn_info() starts with the leaf certificate and attempts to find its issuer certificate in the chain. The issuer certificate then becomes the origin for the next iteration. This step is repeated until there either is no issuer certificate or a root (= self-signed) certificate is found. However, if the received certificate chain contains a loop, this exit condition is never reached and display_conn_info() runs into an endless loop. To craft a loop, it is sufficient to have two CA certificates that mutually list each other as issuers (see attached PoC). ## Steps To Reproduce: I have implemented a small PoC where a Webserver uses a maliciously crafted certificate chain that contains a loop. To this end, the end-entity certificate for localhost is issued by CA2, whose certificate is issued by CA1, whose certificate in turn is issued by CA2 (-> loop). The Python script for the Webserver and the certificate chain are attached to this report. To trigger the DoS in curl, the following steps need to be executed: 1. Modify URL in certinfo example (https://github.com/curl/curl/blob/master/docs/examples/certinfo.c#L46) to point to `https://localhost:4443/` instead of `https://www.example.com/` (`url_easy_setopt(curl, CURLOPT_URL, "https://localhost:4443/")`) 1. Build curl with NSS TLS library (./configure --with-nss) and with examples (make examples) 1. Execute python script attached to this report to start the attacker's Webserver 1. Execute certinfo (doc/ex …

Source code

docs/examples/certinfo.clines 21–71 · lines 46-46 cited by the report
21 ***************************************************************************/
22/* <DESC>
23 * Extract lots of TLS certificate info.
24 * </DESC>
25 */
26#include <stdio.h>
27
28#include <curl/curl.h>
29
30static size_t wrfu(void *ptr, size_t size, size_t nmemb, void *stream)
31{
32 (void)stream;
33 (void)ptr;
34 return size * nmemb;
35}
36
37int main(void)
38{
39 CURL *curl;
40 CURLcode res;
41
42 curl_global_init(CURL_GLOBAL_DEFAULT);
43
44 curl = curl_easy_init();
45 if(curl) {
46 curl_easy_setopt(curl, CURLOPT_URL, "https://www.example.com/");
C5: curl_easy_setopt(curl, CURLOPT_URL, "https://www.example.com/"); -- the line to modify
47
48 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, wrfu);
49
50 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
51 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
52
53 curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L);
54 curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L);
C5: curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L); enables CERTINFO
55
56 res = curl_easy_perform(curl);
57
58 if(!res) {
59 struct curl_certinfo *certinfo;
60
61 res = curl_easy_getinfo(curl, CURLINFO_CERTINFO, &certinfo);
62
63 if(!res && certinfo) {
64 int i;
65
66 printf("%d certs!\n", certinfo->num_of_certs);
67
68 for(i = 0; i < certinfo->num_of_certs; i++) {
69 struct curl_slist *slist;
70
71 for(slist = certinfo->certinfo[i]; slist; slist = slist->next)
lib/vtls/nss.clines 986–1053 · definition of display_conn_info
986static CURLcode display_conn_info(struct Curl_easy *data, PRFileDesc *sock)
C6: display_conn_info is compiled only when NSS backend is used
987{
988 CURLcode result = CURLE_OK;
989 SSLChannelInfo channel;
990 SSLCipherSuiteInfo suite;
991 CERTCertificate *cert;
992 CERTCertificate *cert2;
993 CERTCertificate *cert3;
994 PRTime now;
995
996 if(SSL_GetChannelInfo(sock, &channel, sizeof(channel)) ==
997 SECSuccess && channel.length == sizeof(channel) &&
998 channel.cipherSuite) {
999 if(SSL_GetCipherSuiteInfo(channel.cipherSuite,
1000 &suite, sizeof(suite)) == SECSuccess) {
1001 infof(data, "SSL connection using %s", suite.cipherSuiteName);
1002 }
1003 }
1004
1005 cert = SSL_PeerCertificate(sock);
C2: cert = SSL_PeerCertificate(sock) obtains the leaf certificate
1006 if(cert) {
1007 infof(data, "Server certificate:");
1008
1009 if(!data->set.ssl.certinfo) {
1010 display_cert_info(data, cert);
1011 CERT_DestroyCertificate(cert);
1012 }
1013 else {
1014 /* Count certificates in chain. */
1015 int i = 1;
1016 now = PR_Now();
1017 if(!cert->isRoot) {
1018 cert2 = CERT_FindCertIssuer(cert, now, certUsageSSLCA);
C2: cert2 = CERT_FindCertIssuer(cert, now, certUsageSSLCA) finds the leaf's issuer
1019 while(cert2) {
C1: while(cert2) loop with no cycle detection; if certificate chain has a loop, cert2 never becomes NULL and cert2->isRoot never true
C3: while(cert2) continues as long as an issuer is found
C4: infinite while loop consumes CPU
1020 i++;
C1: i++ increments on each iteration
C4: i++ executes on every iteration with no bound
1021 if(cert2->isRoot) {
C1: only exit condition is cert2->isRoot, which is never true in a mutual-issuer loop
1022 CERT_DestroyCertificate(cert2);
1023 break;
1024 }
1025 cert3 = CERT_FindCertIssuer(cert2, now, certUsageSSLCA);
C1: cert3 = CERT_FindCertIssuer(cert2, ...) continues the chain traversal
C3: CERT_FindCertIssuer called repeatedly on each successive certificate
1026 CERT_DestroyCertificate(cert2);
1027 cert2 = cert3;
C1: cert2 = cert3 advances to next issuer without visited-set tracking
1028 }
1029 }
1030
1031 result = Curl_ssl_init_certinfo(data, i);
1032 if(!result) {
1033 for(i = 0; cert; cert = cert2) {
1034 result = Curl_extract_certinfo(data, i++, (char *)cert->derCert.data,
1035 (char *)cert->derCert.data +
1036 cert->derCert.len);
1037 if(result)
1038 break;
1039
1040 if(cert->isRoot) {
1041 CERT_DestroyCertificate(cert);
1042 break;
1043 }
1044
1045 cert2 = CERT_FindCertIssuer(cert, now, certUsageSSLCA);
1046 CERT_DestroyCertificate(cert);
1047 }
1048 }
1049 }
1050 }
1051
1052 return result;
1053}
lib/optiontable.pllines 38–81 · definition of while
Show 44 lines (not cited by the judge)
38while(<STDIN>) {
39 if(/^ *CURLOPT\(([^,]*), ([^,]*), (\d+)\)/) {
40 my($opt, $type, $num)=($1,$2,$3);
41 my $name;
42 my $ext = $type;
43
44 if($opt =~ /OBSOLETE/) {
45 # skip obsolete options
46 next;
47 }
48
49 if($opt =~ /^CURLOPT_(.*)/) {
50 $name=$1;
51 }
52 $ext =~ s/CURLOPTTYPE_//;
53 $ext =~ s/CBPOINT/CBPTR/;
54 $ext =~ s/POINT\z//;
55 $type = "CURLOT_$ext";
56
57 $opt{$name} = $opt;
58 $type{$name} = $type;
59 push @names, $name;
60 if($num < $lastnum) {
61 print STDERR "ERROR: $opt has bad number\n";
62 exit 2;
63 }
64 else {
65 $lastnum = $num;
66 }
67 }
68
69 # alias for an older option
70 # old = new
71 if(/^#define (CURLOPT_[^ ]*) *(CURLOPT_\S*)/) {
72 my ($o, $n)=($1, $2);
73 # skip obsolete ones
74 if($n !~ /OBSOLETE/) {
75 $o =~ s/^CURLOPT_//;
76 $n =~ s/^CURLOPT_//;
77 $alias{$o} = $n;
78 push @names, $o,
79 }
80 }
81}