✓ProveIt
Examples / Report #1590071 Saved analysis

CVE-2022-32208: FTP-KRB bad message verification

curl · 2022-06-0213.6s analysis · $0.016 in model calls

What the report claims

libcurl handles gss_unwrap GSS_S_BAD_SIG error incorrectly, allowing FTP response injection and heap memory leak.

Compare with curl's recorded outcome

Known vulnerability

A real vulnerability: CVE-2022-32208 FTP-KRB bad message verification

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

ProveIt's finding

Needs human review

The available evidence does not settle the report. A maintainer should investigate.

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.

  • krb5_decode returns -1 on gss_unwrap failure (including GSS_S_BAD_SIG) without fully clearing buffer
  • read_data assigns signed int return to unsigned size_t buf->size causing integer overflow to SIZE_MAX
  • buffer_read uses unvalidated buf->size leading to heap overflow read
  • Attack path: malicious FTP server -> malformed GSS token -> gss_unwrap error -> -1 -> size_t overflow -> heap read -> data leak/injection
  • All cited functions and code paths exist at the checked revision
How this analysis ran 6 claims · 16 citations verified

Evidence check: judge quoted an external source that does not say that (https://datatracker.ietf.org/doc/html/draft-ietf-cat-ftpdsaauth-00)

Checked against curl master on 2022-06-02 (07a9b89fedae). 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 · 17 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
6 claims · 2063 tokens · $0.0002
3 · Evidence
9 code snippets
real curl source at 07a9b89fedae · 6 docs via Tavily
4 · Judgment
Nemotron-3-Ultra-550b-a55b
9756 tokens · 11.8s · $0.0153
5 · Receipts
16 of 16 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 detailed report. The code confirms the flaw: krb5_decode returns -1 on gss_unwrap failure (line 146), read_data assigns this to size_t buf->size causing overflow (line 525-526), and buffer_read reads past the buffer end (line 534-536). This allows heap memory disclosure and potential FTP response injection via the "599 " prefix. We will address this.

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

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

Report claims

C1flaw supported

krb5_decode function does not handle GSS_S_BAD_SIG error properly and returns -1 without clearing buffer

Read the reasoning

The code confirms krb5_decode returns -1 on any gss_unwrap error (including GSS_S_BAD_SIG) and only prefixes the buffer with "599 " rather than clearing it entirely.

C2location supported

defective krb5_decode function in libcurl FTP code

Read the reasoning

The function exists in the FTP/Kerberos code at the cited location.

C3trigger supported

gss_unwrap returns GSS_S_BAD_SIG causing -1 return from krb5_decode

Read the reasoning

gss_unwrap returning GSS_S_BAD_SIG (or any non-GSS_S_COMPLETE status) triggers the error path that returns -1.

C4impact supported

arbitrary FTP server responses can be injected and heap memory may be consumed as response

Read the reasoning

When krb5_decode returns -1, it becomes SIZE_MAX when assigned to size_t buf->size. buffer_read then reads far past the allocated buffer. The "599 " prefix could be interpreted as an FTP response, allowing injection.

C5reproduction supported

attacker sends crafted FTP data that triggers GSS_S_BAD_SIG error and causes -1 size handling

Read the reasoning

Attacker controls FTP server data stream. Malformed GSSAPI token triggers gss_unwrap -> GSS_S_BAD_SIG -> -1 return -> size_t overflow.

C6impact supported

heap buffer read may leak confidential information via unallocated memory consumption

Read the reasoning

With buf->size = SIZE_MAX (from -1 conversion), buffer_read reads arbitrary heap memory past the buffer end, potentially leaking secrets via FTP responses or error messages.

What the report says about other projects

confirmed FTP control channel responses must be authenticated via GSSAPI

“This document defines a method to secure file transfers using the FTP specification RFC 959 mechanisms may be” source

confirmed GSSAPI must reject malformed tokens with GSS_S_BAD_SIG

“#define GSS_S_BAD_SIG (6ul << GSS_C_ROUTINE_ERROR_OFFSET)” source

Read the original report (excerpt)

Full report on HackerOne ↗

## Summary: libcurl handles `gss_unwrap` `GSS_S_BAD_SIG` error incorrectly. This enables malicious attacker to inject arbitrary FTP server responses to GSSAPI protected FTP control connection and/or make the client consume unrelated heap memory as a FTP command response. The defective `krb5_decode` function is as follows: ``` static int krb5_decode(void *app_data, void *buf, int len, int level UNUSED_PARAM, struct connectdata *conn UNUSED_PARAM) { gss_ctx_id_t *context = app_data; OM_uint32 maj, min; gss_buffer_desc enc, dec; (void)level; (void)conn; enc.value = buf; enc.length = len; maj = gss_unwrap(&min, *context, &enc, &dec, NULL, NULL); if(maj != GSS_S_COMPLETE) { if(len >= 4) strcpy(buf, "599 "); return -1; } memcpy(buf, dec.value, dec.length); len = curlx_uztosi(dec.length); gss_release_buffer(&min, &dec); return len; } ``` Note how `read_data` function will set the `buf->size` to result of the decode operation as-is without considering possible `-1` return code and that size `buf->size` is of type `size_t`: ``` /* Types needed for krb5-ftp connections */ struct krb5buffer { void *data; size_t size; size_t index; BIT(eof_flag); }; ``` ``` static CURLcode read_data(struct connectdata *conn, curl_socket_t fd, struct krb5buffer *buf) { int len; CURLcode result; result = socket_read(fd, &len, sizeof(len)); if(result) return result; if(len) { /* only realloc if there was a length */ len = ntohl(len); buf->data = Curl_saferealloc(buf->data, len); } if(!len || !buf->data) return CURLE_OUT_OF_MEMORY; result = socket_read(fd, buf->data, len); if(result) return result; buf->size = conn->mech->decode(conn->app_data, buf->data, len, conn->data_prot, conn); buf->index = 0; return CURLE_OK; } ``` When `gss_unwrap` returns an error the `krb5_decode` code attempts to erase the buffer by prefixing the buffer with `599 \0`. However, this doesn't take into account the case that arbitrary number of bytes can be read by `read_data` function. Hence the buffer may contain multiple lines not just one. The attacker merely needs to find a position in the FTP protocol where ftpcode `599` doesn't lead to connection termination to take over the GSSAPI protected FT …

Source code

lib/krb5.clines 128–154 · definition of krb5_decode
128static int
C2: krb5_decode function defined at lines 128-154 in lib/krb5.c
129krb5_decode(void *app_data, void *buf, int len,
130 int level UNUSED_PARAM,
131 struct connectdata *conn UNUSED_PARAM)
132{
133 gss_ctx_id_t *context = app_data;
134 OM_uint32 maj, min;
135 gss_buffer_desc enc, dec;
136
137 (void)level;
138 (void)conn;
139
140 enc.value = buf;
141 enc.length = len;
142 maj = gss_unwrap(&min, *context, &enc, &dec, NULL, NULL);
C3: gss_unwrap called at line 142, return value stored in maj
143 if(maj != GSS_S_COMPLETE) {
C1: krb5_decode checks if(maj != GSS_S_COMPLETE) at line 143, which catches GSS_S_BAD_SIG among other errors
C3: Check at line 143: if(maj != GSS_S_COMPLETE) catches GSS_S_BAD_SIG
144 if(len >= 4)
C1: Only clears first 4 bytes with strcpy(buf, "599 ") when len >= 4 at line 144-145
145 strcpy(buf, "599 ");
C4: Buffer prefixed with "599 " which is a valid FTP response code
146 return -1;
C1: Returns -1 at line 146 without fully clearing the buffer
C3: Returns -1 at line 146 when gss_unwrap fails
147 }
148
149 memcpy(buf, dec.value, dec.length);
150 len = curlx_uztosi(dec.length);
151 gss_release_buffer(&min, &dec);
152
153 return len;
154}
lib/krb5.clines 503–529 · definition of read_data
503static CURLcode read_data(struct connectdata *conn,
504 curl_socket_t fd,
505 struct krb5buffer *buf)
506{
507 int len;
508 CURLcode result;
509
510 result = socket_read(fd, &len, sizeof(len));
C5: socket_read reads length at line 510
511 if(result)
512 return result;
513
514 if(len) {
515 /* only realloc if there was a length */
516 len = ntohl(len);
517 buf->data = Curl_saferealloc(buf->data, len);
518 }
519 if(!len || !buf->data)
520 return CURLE_OUT_OF_MEMORY;
521
522 result = socket_read(fd, buf->data, len);
C5: socket_read reads encrypted data at line 522
523 if(result)
524 return result;
525 buf->size = conn->mech->decode(conn->app_data, buf->data, len,
C4: read_data assigns decode return value (int) to buf->size (size_t) at line 525-526
C5: decode called at line 525-526
C6: buf->size becomes SIZE_MAX when decode returns -1 at line 525-526
526 conn->data_prot, conn);
527 buf->index = 0;
528 return CURLE_OK;
529}
lib/krb5.clines 531–539 · definition of buffer_read
531static size_t
532buffer_read(struct krb5buffer *buf, void *data, size_t len)
533{
534 if(buf->size - buf->index < len)
C4: buffer_read uses buf->size without validation at line 534-536
C6: buffer_read computes len = buf->size - buf->index at line 534-535
535 len = buf->size - buf->index;
536 memcpy(data, (char *)buf->data + buf->index, len);
C6: memcpy reads from buf->data + buf->index at line 536
537 buf->index += len;
538 return len;
539}
lib/curl_setup_once.hlines 312–312 · definition of BIT
Show 1 lines (not cited by the judge)
312#define BIT(x) bool x
lib/strdup.clines 114–121 · definition of Curl_saferealloc
Show 8 lines (not cited by the judge)
114void *Curl_saferealloc(void *ptr, size_t size)
115{
116 void *datap = realloc(ptr, size);
117 if(size && !datap)
118 /* only free 'ptr' if size was non-zero */
119 free(ptr);
120 return datap;
121}
lib/warnless.clines 122–135 · definition of curlx_uztosi
Show 14 lines (not cited by the judge)
122int curlx_uztosi(size_t uznum)
123{
124#ifdef __INTEL_COMPILER
125# pragma warning(push)
126# pragma warning(disable:810) /* conversion may lose significant bits */
127#endif
128
129 DEBUGASSERT(uznum <= (size_t) CURL_MASK_SINT);
130 return (int)(uznum & (size_t) CURL_MASK_SINT);
131
132#ifdef __INTEL_COMPILER
133# pragma warning(pop)
134#endif
135}
lib/krb5.clines 542–580 · definition of sec_recv
Show 39 lines (not cited by the judge)
542static ssize_t sec_recv(struct Curl_easy *data, int sockindex,
543 char *buffer, size_t len, CURLcode *err)
544{
545 size_t bytes_read;
546 size_t total_read = 0;
547 struct connectdata *conn = data->conn;
548 curl_socket_t fd = conn->sock[sockindex];
549
550 *err = CURLE_OK;
551
552 /* Handle clear text response. */
553 if(conn->sec_complete == 0 || conn->data_prot == PROT_CLEAR)
554 return sread(fd, buffer, len);
555
556 if(conn->in_buffer.eof_flag) {
557 conn->in_buffer.eof_flag = 0;
558 return 0;
559 }
560
561 bytes_read = buffer_read(&conn->in_buffer, buffer, len);
562 len -= bytes_read;
563 total_read += bytes_read;
564 buffer += bytes_read;
565
566 while(len > 0) {
567 if(read_data(conn, fd, &conn->in_buffer))
568 return -1;
569 if(conn->in_buffer.size == 0) {
570 if(bytes_read > 0)
571 conn->in_buffer.eof_flag = 1;
572 return bytes_read;
573 }
574 bytes_read = buffer_read(&conn->in_buffer, buffer, len);
575 len -= bytes_read;
576 total_read += bytes_read;
577 buffer += bytes_read;
578 }
579 return total_read;
580}
lib/hash.hlines 32–57 · definition of size_t
Show 26 lines (not cited by the judge)
32typedef size_t (*hash_function) (void *key,
33 size_t key_length,
34 size_t slots_num);
35
36/*
37 Comparator function prototype. Compares two keys.
38*/
39typedef size_t (*comp_function) (void *key1,
40 size_t key1_len,
41 void *key2,
42 size_t key2_len);
43
44typedef void (*Curl_hash_dtor)(void *);
45
46struct Curl_hash {
47 struct Curl_llist *table;
48
49 /* Hash function to be used for this hash table */
50 hash_function hash_func;
51
52 /* Comparator function to compare keys */
53 comp_function comp_func;
54 Curl_hash_dtor dtor;
55 int slots;
56 size_t size;
57};
lib/krb5.clines 454–474 · definition of socket_read
Show 21 lines (not cited by the judge)
454static CURLcode
455socket_read(curl_socket_t fd, void *to, size_t len)
456{
457 char *to_p = to;
458 CURLcode result;
459 ssize_t nread = 0;
460
461 while(len > 0) {
462 result = Curl_read_plain(fd, to_p, len, &nread);
463 if(!result) {
464 len -= nread;
465 to_p += nread;
466 }
467 else {
468 if(result == CURLE_AGAIN)
469 continue;
470 return result;
471 }
472 }
473 return CURLE_OK;
474}