✓ProveIt
Examples / Report #1172857 Saved analysis

CVE-2021-22897: schannel cipher selection surprise

curl · 2021-04-2210.5s analysis · $0.010 in model calls

What the report claims

The commit added support for selecting ciphers in schannel but used a static algIds array, causing the last configured cipher list to override previous configurations across multiple curl handles, potentially leading to insecure cipher configurations.

Compare with curl's recorded outcome

Known vulnerability

A real vulnerability: CVE-2021-22897 Schannel cipher selection surprise

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 static ALG_ID algIds[45] array at lib/vtls/schannel.c:335 is shared across all calls to set_ssl_ciphers.
  • Each connection's SCHANNEL_CRED receives a pointer to this single static array (line 353).
  • When multiple curl handles configure different cipher lists via CURLOPT_SSL_CIPHER_LIST, the last write overwrites the array for all connections.
  • This can cause a connection expecting a strong cipher suite to instead use a weaker one configured by another handle, which is a security-relevant misconfiguration.
  • The bug exists in both the master revision (e540b32562a0) and the release curl-7_76_1 (same static array at line 331).
How this analysis ran 5 claims · 10 citations verified

Checked against curl master on 2021-04-22 (e540b32562a0). 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 · 5 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
5 claims · 1402 tokens · $0.0002
3 · Evidence
4 code snippets
real curl source at e540b32562a0
4 · Judgment
Nemotron-3-Ultra-550b-a55b
5563 tokens · 8.4s · $0.0099
5 · Receipts
10 of 10 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 report. The static `algIds` array in `set_ssl_ciphers` (lib/vtls/schannel.c:335) is indeed shared across all connections using the schannel backend. Each connection's credential points to this single array (line 353), so the last handle to set CURLOPT_SSL_CIPHER_LIST overwrites the cipher list for all concurrent connections. This can cause a connection to use a weaker cipher suite than configured. We will fix this by making the cipher list per-connection (e.g., storing it in SCHANNEL_CRED).

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

Report claims

C1flaw supported

The static `algIds` array in `set_ssl_ciphers` causes the last configured cipher list to override configuration used by other connections

Read the reasoning

The static array is global to the function. Every call to set_ssl_ciphers writes into the same array, and each connection's SCHANNEL_CRED ends up pointing to that single array. The last caller's cipher list overwrites the array for all connections.

C2location supported

in `set_ssl_ciphers`

Read the reasoning

The flaw is located in the set_ssl_ciphers function as shown.

C3trigger supported

using multiple curl handles with `CURLOPT_SSL_CIPHER_LIST` set to different cipher lists

Read the reasoning

The code path exists: multiple easy handles can be created and each can set a different cipher list via CURLOPT_SSL_CIPHER_LIST, which will invoke set_ssl_ciphers for the schannel backend.

C4impact supported

potential wrong configuration for connections

Read the reasoning

Because all connections share the static array, a connection may use a cipher list configured by a different handle, leading to wrong (potentially weaker) cipher configuration.

C5reproduction supported

Create two or more separate curl handles with `curl_easy_init`, set different cipher lists with `CURLOPT_SSL_CIPHER_LIST`, and create simultaneous connections

Read the reasoning

The reproduction steps map directly to the code: create multiple handles, set different cipher lists on each, and initiate connections. The static array bug will cause the last configuration to be seen by all.

Read the original report

Full report on HackerOne ↗

## Summary: [Commit "schannel: support selecting ciphers"](https://github.com/curl/curl/commit/9aefbff30d280c60fc9d8cc3e0b2f19fc70a2f28) added support for selecting the ciphers with SCHANNEL. However, due to use of a static `algIds` array for ciphers in `set_ssl_ciphers` the last configured cipher list will override configuration used by other connections, leading to potential wrong configuration for them. This may have security implications if insecure cipher configuration is used where secure cipher configuration is expected. ## Steps To Reproduce: 1.Create two or more separate curl handles with `curl_easy_init` 2. Set different cipher lists with `curl_easy_setopt` `CURLOPT_SSL_CIPHER_LIST` to the curl handles 3. Create simultaneous connections with there the separate curl handles Instead of each connection using the specific cipher list some of them will share the wrong configuration. If/how this happens exactly depends on how the connection setup overlaps. Note that to be vulnerable some existing application using libcurl would needs to use such mixed `CURLOPT_SSL_CIPHER_LIST` configuration with multiple curl handles to begin with. It is not really known how likely this really is, but it seems somewhat rare use case. ## Supporting Material/References: - Commit adding the vulnerable feature: https://github.com/curl/curl/commit/9aefbff30d280c60fc9d8cc3e0b2f19fc70a2f28 ## Impact Potentially wrong cipher configuration used for connections.

Source code

lib/vtls/schannel.clines 330–356 · definition of set_ssl_ciphers
330static CURLcode
C2: function definition: static CURLcode set_ssl_ciphers(SCHANNEL_CRED *schannel_cred, char *ciphers)
C5: set_ssl_ciphers processes the cipher list
331set_ssl_ciphers(SCHANNEL_CRED *schannel_cred, char *ciphers)
332{
333 char *startCur = ciphers;
334 int algCount = 0;
335 static ALG_ID algIds[45]; /*There are 45 listed in the MS headers*/
C1: static ALG_ID algIds[45]; declares a single static array shared by all calls
C4: static array shared across all connections
336 while(startCur && (0 != *startCur) && (algCount < 45)) {
337 long alg = strtol(startCur, 0, 0);
338 if(!alg)
339 alg = get_alg_id_by_name(startCur);
340 if(alg)
341 algIds[algCount++] = alg;
342 else if(!strncmp(startCur, "USE_STRONG_CRYPTO",
343 sizeof("USE_STRONG_CRYPTO") - 1) ||
344 !strncmp(startCur, "SCH_USE_STRONG_CRYPTO",
345 sizeof("SCH_USE_STRONG_CRYPTO") - 1))
346 schannel_cred->dwFlags |= SCH_USE_STRONG_CRYPTO;
347 else
348 return CURLE_SSL_CIPHER;
349 startCur = strchr(startCur, ':');
350 if(startCur)
351 startCur++;
352 }
353 schannel_cred->palgSupportedAlgs = algIds;
C1: schannel_cred->palgSupportedAlgs = algIds; assigns the static array's address to each connection's credential
C4: each connection's credential points to the same array
354 schannel_cred->cSupportedAlgs = algCount;
355 return CURLE_OK;
356}
lib/easy.clines 289–312 · definition of curl_easy_init
289struct Curl_easy *curl_easy_init(void)
C3: curl_easy_init creates separate handles
C5: curl_easy_init creates handles
290{
291 CURLcode result;
292 struct Curl_easy *data;
293
294 /* Make sure we inited the global SSL stuff */
295 if(!initialized) {
296 result = curl_global_init(CURL_GLOBAL_DEFAULT);
297 if(result) {
298 /* something in the global init failed, return nothing */
299 DEBUGF(fprintf(stderr, "Error: curl_global_init failed\n"));
300 return NULL;
301 }
302 }
303
304 /* We use curl_open() with undefined URL so far */
305 result = Curl_open(&data);
306 if(result) {
307 DEBUGF(fprintf(stderr, "Error: Curl_open failed\n"));
308 return NULL;
309 }
310
311 return data;
312}
lib/setopt.clines 3000–3014 · definition of curl_easy_setopt
3000CURLcode curl_easy_setopt(struct Curl_easy *data, CURLoption tag, ...)
C3: curl_easy_setopt handles CURLOPT_SSL_CIPHER_LIST which would call into schannel's set_ssl_ciphers
C5: curl_easy_setopt sets options including CURLOPT_SSL_CIPHER_LIST
3001{
3002 va_list arg;
3003 CURLcode result;
3004
3005 if(!data)
3006 return CURLE_BAD_FUNCTION_ARGUMENT;
3007
3008 va_start(arg, tag);
3009
3010 result = Curl_vsetopt(data, tag, arg);
3011
3012 va_end(arg);
3013 return result;
3014}
lib/vtls/schannel.c@curl-7_76_1lines 326–347 · definition of set_ssl_ciphers as in release curl-7_76_1; differs from master; commits that changed it since: 67d3afa73f schannel: Support strong crypto option
Show 22 lines (not cited by the judge)
326static CURLcode
327set_ssl_ciphers(SCHANNEL_CRED *schannel_cred, char *ciphers)
328{
329 char *startCur = ciphers;
330 int algCount = 0;
331 static ALG_ID algIds[45]; /*There are 45 listed in the MS headers*/
332 while(startCur && (0 != *startCur) && (algCount < 45)) {
333 long alg = strtol(startCur, 0, 0);
334 if(!alg)
335 alg = get_alg_id_by_name(startCur);
336 if(alg)
337 algIds[algCount++] = alg;
338 else
339 return CURLE_SSL_CIPHER;
340 startCur = strchr(startCur, ':');
341 if(startCur)
342 startCur++;
343 }
344 schannel_cred->palgSupportedAlgs = algIds;
345 schannel_cred->cSupportedAlgs = algCount;
346 return CURLE_OK;
347}