✓ProveIt
Examples / Report #1721098 Saved analysis

CVE-2022-35260: .netrc parser out-of-bounds access

curl · 2022-10-0314.8s analysis · $0.017 in model calls

What the report claims

Curl expects the .netrc file to have space characters; if it lacks spaces, parsenetrc() performs out-of-bounds reads and a 1-byte out-of-bounds write, potentially causing crashes or other consequences.

Compare with curl's recorded outcome

Known vulnerability

A real vulnerability: CVE-2022-35260 .netrc parser out-of-bounds access

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(!ISSPACE(*tok_end)) loop at lib/netrc.c:111 lacks a bounds check against the netrcbuffer end.
  • fgets guarantees null-termination within the 4096-byte buffer, but ISSPACE('\0') is false, so the loop continues past the buffer.
  • The subsequent *tok_end = 0 at line 113 writes a null byte out-of-bounds.
  • An attacker controlling the .netrc file content (via --netrc-file) can trigger this reliably with a file containing no whitespace characters.
How this analysis ran 5 claims · 9 citations verified

Checked against curl release curl-7_85_0 (fits the report's line numbers and quotes better than master on 2022-10-03) (93d092867f0f). Test 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 · 6 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
5 claims · 1535 tokens · $0.0002
3 · Evidence
5 code snippets
real curl source at 93d092867f0f
4 · Judgment
Nemotron-3-Ultra-550b-a55b
8777 tokens · 12.1s · $0.0165
5 · Receipts
9 of 9 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. The vulnerability is confirmed: in lib/netrc.c, the loop at line 111 (while(!ISSPACE(*tok_end)) tok_end++;) lacks a bounds check, allowing tok_end to run past the 4096-byte netrcbuffer when the .netrc file contains no whitespace. The write at line 113 (*tok_end = 0;) then writes a null byte out-of-bounds. A 4095-byte file of 'a' with no newline reliably triggers this. We will prepare a fix adding a buffer-end check to the loop condition.

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

Report claims

C1flaw supported

parsenetrc() performs an out-of-bounds read when encountering non-space characters in the .netrc file.

Read the reasoning

The loop at line 111 increments tok_end while the character is not a space. With a .netrc file containing 4095 'a' bytes and no newline, fgets fills netrcbuffer[0..4094] with 'a' and places '\0' at netrcbuffer[4095]. Since ISSPACE('\0') is false (Curl_isspace(0) returns 0), the loop reads netrcbuffer[4095] (valid), increments tok_end to 4096, then reads netrcbuffer[4096] (out-of-bounds) for the next iteration's condition check.

C2flaw supported

parsenetrc() performs a 1-byte out-of-bounds write at line 27.

Read the reasoning

After the while(!ISSPACE(*tok_end)) loop exits (when it randomly encounters a byte that ISSPACE considers a space), line 113 writes a null terminator at that out-of-bounds tok_end position. This is a 1-byte out-of-bounds write.

C3trigger supported

A .netrc file consisting of 4095 bytes of the character 'a' (no space or newline) can trigger the flaw.

Read the reasoning

A 4095-byte file of 'a' with no newline causes fgets to fill the entire buffer (indices 0-4094 = 'a', index 4095 = '\0'). Since 'a' and '\0' are both non-space, the tok_end loop runs past the buffer end. Smaller files can also trigger it if memory after the buffer lacks space characters.

C4impact supported

Application crash plus other as yet undetermined consequences.

Read the reasoning

Buffer overrun reads and writes are undefined behavior in C and can cause application crashes, memory corruption, or potential code execution depending on heap layout.

C5reproduction supported

Running `curl --netrc-file .netrc test.local` with a .netrc file of 4095 'a' bytes reproduces the issue.

Read the reasoning

The --netrc-file command line option triggers parsenetrc() with the attacker-controlled .netrc file, making the reproduction steps accurate.

Read the original report

Full report on HackerOne ↗

## Summary: Curl expects the .netrc file to have space characters. So if there is no space character, it will do an out-of-bounds read and a 1-byte out-of-bounds write. This can happen multiple times depending on the state of the memory. ## Steps To Reproduce: `curl --netrc-file .netrc test.local` ".netrc" is attached. The content is 'a' for 4095 bytes. Depending on memory conditions, even single-byte files can cause problems. It's not exactly just spaces and newlines. The condition is that the .netrc file does not contain characters for which ISSPACE() returns true (so it is also a condition that there is no line feed code). There is a problem with parsenetrc() in lib/netrc.c. parsenetrc() has the following loop. ``` while(!done && fgets(netrcbuffer, netrcbuffsize, file)) { char *tok; char *tok_end; bool quoted; if(state == MACDEF) { if((netrcbuffer[0] == '\n') || (netrcbuffer[0] == '\r')) state = NOTHING; else continue; } tok = netrcbuffer; while(tok) { while(ISSPACE(*tok)) tok++; /* tok is first non-space letter */ if(!*tok || (*tok == '#')) /* end of line or the rest is a comment */ break; /* leading double-quote means quoted string */ quoted = (*tok == '\"'); tok_end = tok; if(!quoted) { while(!ISSPACE(*tok_end)) tok_end++; *tok_end = 0; } ``` The 'a' and the terminating character '\0' in the .netrc file are characters for which ISSPACE() returns false, so while on line 25 is true(!false). This causes an out-of-bounds read. Also, line 27 is an out-of-bounds write. (1 byte for '\0). ## Remediation ideas: I think it would be better to include the condition that *tok is not NULL in the while statement. ## Impact Application crash plus other as yet undetermined consequences.

Source code

lib/netrc.clines 58–218 · definition of parsenetrc (truncated)
58static int parsenetrc(const char *host,
C5: parsenetrc is called when --netrc-file option is used
59 char **loginp,
60 char **passwordp,
61 bool *login_changed,
62 bool *password_changed,
63 char *netrcfile)
64{
65 FILE *file;
66 int retcode = NETRC_FILE_MISSING;
67 char *login = *loginp;
68 char *password = *passwordp;
69 bool specific_login = (login && *login != 0);
70 bool login_alloc = FALSE;
71 bool password_alloc = FALSE;
72 enum host_lookup_state state = NOTHING;
73
74 char state_login = 0; /* Found a login keyword */
75 char state_password = 0; /* Found a password keyword */
76 int state_our_login = TRUE; /* With specific_login, found *our* login
77 name (or login-less line) */
78
79 DEBUGASSERT(netrcfile);
80
81 file = fopen(netrcfile, FOPEN_READTEXT);
82 if(file) {
83 bool done = FALSE;
84 char netrcbuffer[4096];
C3: char netrcbuffer[4096]; buffer size is 4096 bytes
85 int netrcbuffsize = (int)sizeof(netrcbuffer);
C3: int netrcbuffsize = (int)sizeof(netrcbuffsize); passed to fgets
86
87 while(!done && fgets(netrcbuffer, netrcbuffsize, file)) {
C3: fgets(netrcbuffer, netrcbuffsize, file) reads at most 4095 chars + null terminator
88 char *tok;
89 char *tok_end;
90 bool quoted;
91 if(state == MACDEF) {
92 if((netrcbuffer[0] == '\n') || (netrcbuffer[0] == '\r'))
93 state = NOTHING;
94 else
95 continue;
96 }
97 tok = netrcbuffer;
98 while(tok) {
99 while(ISSPACE(*tok))
100 tok++;
101 /* tok is first non-space letter */
102 if(!*tok || (*tok == '#'))
103 /* end of line or the rest is a comment */
104 break;
105
106 /* leading double-quote means quoted string */
107 quoted = (*tok == '\"');
108
109 tok_end = tok;
110 if(!quoted) {
111 while(!ISSPACE(*tok_end))
C1: while(!ISSPACE(*tok_end)) tok_end++; reads past the buffer when no space character exists in the netrcbuffer
C4: Out-of-bounds read in loop condition
112 tok_end++;
C1: tok_end increments without bounds check; fgets guarantees null-termination at netrcbuffer[4095] but ISSPACE('\0') is false, so loop continues past buffer end
113 *tok_end = 0;
C2: *tok_end = 0; writes a null byte at the position where the loop stopped, which can be one or more bytes past the 4096-byte buffer
C4: Out-of-bounds write of null byte
114 }
115 else {
116 bool escape = FALSE;
117 bool endquote = FALSE;
118 char *store = tok;
119 tok_end++; /* pass the leading quote */
120 while(*tok_end) {
121 char s = *tok_end;
122 if(escape) {
123 escape = FALSE;
124 switch(s) {
125 case 'n':
126 s = '\n';
127 break;
128 case 'r':
129 s = '\r';
130 break;
131 case 't':
132 s = '\t';
133 break;
134 }
135 }
136 else if(s == '\\') {
137 escape = TRUE;
138 tok_end++;
139 continue;
140 }
141 else if(s == '\"') {
142 tok_end++; /* pass the ending quote */
143 endquote = TRUE;
144 break;
145 }
146 *store++ = s;
147 tok_end++;
148 }
149 *store = 0;
150 if(escape || !endquote) {
151 /* bad syntax, get out */
152 retcode = NETRC_FAILED;
153 goto out;
154 }
155 }
156
157 if((login && *login) && (password && *password)) {
158 done = TRUE;
159 break;
160 }
161
162 switch(state) {
163 case NOTHING:
164 if(strcasecompare("macdef", tok)) {
165 /* Define a macro. A macro is defined with the specified name; its
166 contents begin with the next .netrc line and continue until a
167 null line (consecutive new-line characters) is encountered. */
168 state = MACDEF;
169 }
170 else if(strcasecompare("machine", tok)) {
171 /* the next tok is the machine name, this is in itself the
172 delimiter that starts the stuff entered for this machine,
173 after this we need to search for 'login' and
174 'password'. */
175 state = HOSTFOUND;
176 }
177 else if(strcasecompare("default", tok)) {
178 state = HOSTVALID;
179 retcode = NETRC_SUCCESS; /* we did find our host */
180 }
181 break;
182 case MACDEF:
183 if(!strlen(tok)) {
184 state = NOTHING;
185 }
186 break;
187 case HOSTFOUND:
188 if(strcasecompare(host, tok)) {
189 /* and yes, this is our host! */
190 state = HOSTVALID;
191 retcode = NETRC_SUCCESS; /* we did find our host */
192 }
193 else
194 /* not our host */
195 state = NOTHING;
196 break;
197 case HOSTVALID:
198 /* we are now parsing sub-keywords concerning "our" host */
199 if(state_login) {
200 if(specific_login) {
201 state_our_login = strcasecompare(login, tok);
202 }
203 else if(!login || strcmp(login, tok)) {
204 if(login_alloc) {
205 free(login);
206 login_alloc = FALSE;
207 }
208 login = strdup(tok);
209 if(!login) {
210 retcode = NETRC_FAILED; /* allocation failed */
211 goto out;
212 }
213 login_alloc = TRUE;
214 }
215 state_login = 0;
216 }
217 else if(state_password) {
218 if((state_our_login || !specific_login)
lib/warnless.hlines 80–80 · definition of read
80# define read(fd, buf, count) curlx_read(fd, buf, count)
lib/curl_ctype.hlines 40–40 · definition of ISSPACE
Show 1 lines (not cited by the judge)
40#define ISSPACE(x) (Curl_isspace((int) ((unsigned char)x)))
lib/curl_setup_once.hlines 242–242 · definition of true
Show 1 lines (not cited by the judge)
242# define true 1
lib/optiontable.pllines 40–83 · definition of while
Show 44 lines (not cited by the judge)
40while(<STDIN>) {
41 if(/^ *CURLOPT\(([^,]*), ([^,]*), (\d+)\)/) {
42 my($opt, $type, $num)=($1,$2,$3);
43 my $name;
44 my $ext = $type;
45
46 if($opt =~ /OBSOLETE/) {
47 # skip obsolete options
48 next;
49 }
50
51 if($opt =~ /^CURLOPT_(.*)/) {
52 $name=$1;
53 }
54 $ext =~ s/CURLOPTTYPE_//;
55 $ext =~ s/CBPOINT/CBPTR/;
56 $ext =~ s/POINT\z//;
57 $type = "CURLOT_$ext";
58
59 $opt{$name} = $opt;
60 $type{$name} = $type;
61 push @names, $name;
62 if($num < $lastnum) {
63 print STDERR "ERROR: $opt has bad number\n";
64 exit 2;
65 }
66 else {
67 $lastnum = $num;
68 }
69 }
70
71 # alias for an older option
72 # old = new
73 if(/^#define (CURLOPT_[^ ]*) *(CURLOPT_\S*)/) {
74 my ($o, $n)=($1, $2);
75 # skip obsolete ones
76 if($n !~ /OBSOLETE/) {
77 $o =~ s/^CURLOPT_//;
78 $n =~ s/^CURLOPT_//;
79 $alias{$o} = $n;
80 push @names, $o,
81 }
82 }
83}