http1: fix potential NULL dereference in Curl_h1_req_parse_read()#20779
http1: fix potential NULL dereference in Curl_h1_req_parse_read()#20779vszakats wants to merge 4 commits intocurl:masterfrom
Curl_h1_req_parse_read()#20779Conversation
|
I'm not sure if this is the correct thing to do when a NULL buffer |
|
I think it should primarily have an assert for it so that a debug build would crash on it because it is a fundamental requirement that this function is never called with a NULL pointer and I don't see how this can actually legitimately happen. Then it becomes a mostly academic question how to handle it in run-time. It should probably return an error because it is a sign of a serious internal confusion. |
|
augment review |
🤖 Augment PR SummarySummary: Adds a defensive Why: Addresses a clang-analyzer finding that the function could be analyzed as doing pointer arithmetic on a NULL buffer. 🤖 Was this summary useful? React with 👍 or 👎 |
|
@aisle-analyzer augment review |
🔒 Aisle Security Analysis✅ We scanned this PR and did not find any security vulnerabilities. Aisle supplements but does not replace security review. Analyzed PR: #20779 at commit Last updated on: 2026-03-02T12:33:36Z |
Reported by clang-tidy v22 with `clang-analyzer-*` explicitly enabled.
```
lib/http1.c:89:31: error: Subtraction of a non-null pointer (from variable 'line_end') and a null pointer (via field 'line') results in undefined behavior [clang-analyzer-core.NullPointerArithm,-warnings-as-errors]
89 | parser->line_len = line_end - parser->line + 1;
| ^
lib/http1.c:272:9: note: Assuming field 'done' is 0
272 | while(!parser->done) {
| ^~~~~~~~~~~~~
lib/http1.c:272:3: note: Loop condition is true. Entering loop body
272 | while(!parser->done) {
| ^
lib/http1.c:273:14: note: Calling 'next_line'
273 | result = next_line(parser, buf, buflen, options, &nread);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/http1.c:101:6: note: Assuming field 'line' is null
101 | if(parser->line) {
| ^~~~~~~~~~~~
lib/http1.c:101:3: note: Taking false branch
101 | if(parser->line) {
| ^
lib/http1.c:107:12: note: Calling 'detect_line'
107 | result = detect_line(parser, buf, buflen, pnread);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/http1.c:83:3: note: Loop condition is false. Exiting loop
83 | DEBUGASSERT(!parser->line);
| ^
lib/curl_setup.h:1072:24: note: expanded from macro 'DEBUGASSERT'
1072 | #define DEBUGASSERT(x) do {} while(0)
| ^
lib/http1.c:86:6: note: Assuming 'line_end' is non-null
86 | if(!line_end)
| ^~~~~~~~~
lib/http1.c:86:3: note: Taking false branch
86 | if(!line_end)
| ^
lib/http1.c:88:3: note: The value of 'buf' is assigned to field 'line', which participates in a condition later
88 | parser->line = (const char *)buf;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/http1.c:89:3: note: Value assigned to field 'line_len'
89 | parser->line_len = line_end - parser->line + 1;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/http1.c:90:3: note: Value assigned to 'nread'
90 | *pnread = parser->line_len;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
lib/http1.c:90:3: note: Value assigned to 'nread', which participates in a condition later
90 | *pnread = parser->line_len;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
lib/http1.c:91:3: note: Returning zero, which participates in a condition later
91 | return CURLE_OK;
| ^~~~~~~~~~~~~~~
lib/http1.c:107:12: note: Returning from 'detect_line'
107 | result = detect_line(parser, buf, buflen, pnread);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/http1.c:108:7: note: 'result' is 0
108 | if(!result) {
| ^~~~~~
lib/http1.c:108:3: note: Taking true branch
108 | if(!result) {
| ^
lib/http1.c:109:8: note: Assuming the condition is false
109 | if(curlx_dyn_len(&parser->scratch)) {
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/http1.c:109:5: note: Taking false branch
109 | if(curlx_dyn_len(&parser->scratch)) {
| ^
lib/http1.c:118:14: note: Calling 'trim_line'
118 | result = trim_line(parser, options);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
lib/http1.c:55:3: note: Loop condition is false. Exiting loop
55 | DEBUGASSERT(parser->line);
| ^
lib/curl_setup.h:1072:24: note: expanded from macro 'DEBUGASSERT'
1072 | #define DEBUGASSERT(x) do {} while(0)
| ^
lib/http1.c:56:6: note: Assuming field 'line_len' is 0
56 | if(parser->line_len) {
| ^~~~~~~~~~~~~~~~
lib/http1.c:56:3: note: Taking false branch
56 | if(parser->line_len) {
| ^
lib/http1.c:68:11: note: Assuming the condition is false
68 | else if(options & H1_PARSE_OPT_STRICT)
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/http1.c:68:8: note: Taking false branch
68 | else if(options & H1_PARSE_OPT_STRICT)
| ^
lib/http1.c:71:6: note: Assuming field 'line_len' is <= field 'max_line_len'
71 | if(parser->line_len > parser->max_line_len) {
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/http1.c:71:3: note: Taking false branch
71 | if(parser->line_len > parser->max_line_len) {
| ^
lib/http1.c:74:3: note: Returning zero, which participates in a condition later
74 | return CURLE_OK;
| ^~~~~~~~~~~~~~~
lib/http1.c:118:14: note: Returning from 'trim_line'
118 | result = trim_line(parser, options);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
lib/http1.c:119:8: note: 'result' is 0
119 | if(result)
| ^~~~~~
lib/http1.c:119:5: note: Taking false branch
119 | if(result)
| ^
lib/http1.c:128:3: note: Returning zero (loaded from 'result'), which participates in a condition later
128 | return result;
| ^~~~~~~~~~~~~
lib/http1.c:273:14: note: Returning from 'next_line'
273 | result = next_line(parser, buf, buflen, options, &nread);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/http1.c:274:8: note: 'result' is 0
274 | if(result) {
| ^~~~~~
lib/http1.c:274:5: note: Taking false branch
274 | if(result) {
| ^
lib/http1.c:282:5: note: Value assigned to 'buf'
282 | buf += nread;
| ^~~~~~~~~~~~
lib/http1.c:285:8: note: Assuming field 'line' is null
285 | if(!parser->line) {
| ^~~~~~~~~~~~~
lib/http1.c:285:5: note: Taking true branch
285 | if(!parser->line) {
| ^
lib/http1.c:287:10: note: Assuming 'buflen' is not equal to 0
287 | if(!buflen)
| ^~~~~~~
lib/http1.c:287:7: note: Taking false branch
287 | if(!buflen)
| ^
lib/http1.c:272:3: note: Loop condition is true. Entering loop body
272 | while(!parser->done) {
| ^
lib/http1.c:273:32: note: Passing null pointer value via 2nd parameter 'buf'
273 | result = next_line(parser, buf, buflen, options, &nread);
| ^~~
lib/http1.c:273:14: note: Calling 'next_line'
273 | result = next_line(parser, buf, buflen, options, &nread);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/http1.c:101:14: note: Field 'line' is null
101 | if(parser->line) {
| ^
lib/http1.c:101:3: note: Taking false branch
101 | if(parser->line) {
| ^
lib/http1.c:107:32: note: Passing null pointer value via 2nd parameter 'buf'
107 | result = detect_line(parser, buf, buflen, pnread);
| ^~~
lib/http1.c:107:12: note: Calling 'detect_line'
107 | result = detect_line(parser, buf, buflen, pnread);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/http1.c:83:3: note: Loop condition is false. Exiting loop
83 | DEBUGASSERT(!parser->line);
| ^
lib/curl_setup.h:1072:24: note: expanded from macro 'DEBUGASSERT'
1072 | #define DEBUGASSERT(x) do {} while(0)
| ^
lib/http1.c:86:6: note: Assuming 'line_end' is non-null
86 | if(!line_end)
| ^~~~~~~~~
lib/http1.c:86:3: note: Taking false branch
86 | if(!line_end)
| ^
lib/http1.c:88:3: note: Null pointer value stored to field 'line'
88 | parser->line = (const char *)buf;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib/http1.c:89:31: note: Subtraction of a non-null pointer (from variable 'line_end') and a null pointer (via field 'line') results in undefined behavior
89 | parser->line_len = line_end - parser->line + 1;
| ~~~~~~~~ ^ ~~~~
384 warnings generated.
```
Ref: https://github.com/curl/curl/actions/runs/22534731241/job/65279952830?pr=20778#step:11:85
Cherry-picked from curl#20778
Reported by clang-tidy v22 with
clang-analyzer-*explicitly enabled:Ref: https://github.com/curl/curl/actions/runs/22534731241/job/65279952830?pr=20778#step:11:85
Ref: #20778