Skip to content

Commit 0eae95e

Browse files
committed
deps: update http-parser to version 2.5.1
includes parsing improvements to ensure closer HTTP spec conformance PR-URL: nodejs-private/node-private#20
1 parent da3750f commit 0eae95e

14 files changed

+411
-13
lines changed

deps/http_parser/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
# IN THE SOFTWARE.
2020

2121
PLATFORM ?= $(shell sh -c 'uname -s | tr "[A-Z]" "[a-z]"')
22-
SONAME ?= libhttp_parser.so.2.5.0
22+
SONAME ?= libhttp_parser.so.2.5.1
2323

2424
CC?=gcc
2525
AR?=ar

deps/http_parser/http_parser.c

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,12 @@ enum http_host_state
433433
(IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')
434434
#endif
435435

436+
/**
437+
* Verify that a char is a valid visible (printable) US-ASCII
438+
* character or %x80-FF
439+
**/
440+
#define IS_HEADER_CHAR(ch) \
441+
(ch == CR || ch == LF || ch == 9 || (ch > 31 && ch != 127))
436442

437443
#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res)
438444

@@ -638,6 +644,8 @@ size_t http_parser_execute (http_parser *parser,
638644
const char *status_mark = 0;
639645
enum state p_state = (enum state) parser->state;
640646

647+
const unsigned int lenient = parser->lenient_http_headers;
648+
641649
/* We're in an error state. Don't bother doing anything. */
642650
if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
643651
return 0;
@@ -1384,7 +1392,12 @@ size_t http_parser_execute (http_parser *parser,
13841392
|| c != CONTENT_LENGTH[parser->index]) {
13851393
parser->header_state = h_general;
13861394
} else if (parser->index == sizeof(CONTENT_LENGTH)-2) {
1395+
if (parser->flags & F_CONTENTLENGTH) {
1396+
SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
1397+
goto error;
1398+
}
13871399
parser->header_state = h_content_length;
1400+
parser->flags |= F_CONTENTLENGTH;
13881401
}
13891402
break;
13901403

@@ -1536,6 +1549,11 @@ size_t http_parser_execute (http_parser *parser,
15361549
REEXECUTE();
15371550
}
15381551

1552+
if (!lenient && !IS_HEADER_CHAR(ch)) {
1553+
SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
1554+
goto error;
1555+
}
1556+
15391557
c = LOWER(ch);
15401558

15411559
switch (h_state) {
@@ -1703,7 +1721,10 @@ size_t http_parser_execute (http_parser *parser,
17031721

17041722
case s_header_almost_done:
17051723
{
1706-
STRICT_CHECK(ch != LF);
1724+
if (UNLIKELY(ch != LF)) {
1725+
SET_ERRNO(HPE_LF_EXPECTED);
1726+
goto error;
1727+
}
17071728

17081729
UPDATE_STATE(s_header_value_lws);
17091730
break;
@@ -1787,6 +1808,14 @@ size_t http_parser_execute (http_parser *parser,
17871808
REEXECUTE();
17881809
}
17891810

1811+
/* Cannot use chunked encoding and a content-length header together
1812+
per the HTTP specification. */
1813+
if ((parser->flags & F_CHUNKED) &&
1814+
(parser->flags & F_CONTENTLENGTH)) {
1815+
SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
1816+
goto error;
1817+
}
1818+
17901819
UPDATE_STATE(s_headers_done);
17911820

17921821
/* Set this here so that on_headers_complete() callbacks can see it */

deps/http_parser/http_parser.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ extern "C" {
2727
/* Also update SONAME in the Makefile whenever you change these. */
2828
#define HTTP_PARSER_VERSION_MAJOR 2
2929
#define HTTP_PARSER_VERSION_MINOR 5
30-
#define HTTP_PARSER_VERSION_PATCH 0
30+
#define HTTP_PARSER_VERSION_PATCH 1
3131

3232
#include <sys/types.h>
3333
#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600)
@@ -140,6 +140,7 @@ enum flags
140140
, F_TRAILING = 1 << 4
141141
, F_UPGRADE = 1 << 5
142142
, F_SKIPBODY = 1 << 6
143+
, F_CONTENTLENGTH = 1 << 7
143144
};
144145

145146

@@ -182,6 +183,8 @@ enum flags
182183
XX(INVALID_HEADER_TOKEN, "invalid character in header") \
183184
XX(INVALID_CONTENT_LENGTH, \
184185
"invalid character in content-length header") \
186+
XX(UNEXPECTED_CONTENT_LENGTH, \
187+
"unexpected content-length header") \
185188
XX(INVALID_CHUNK_SIZE, \
186189
"invalid character in chunk size header") \
187190
XX(INVALID_CONSTANT, "invalid constant string") \
@@ -206,10 +209,11 @@ enum http_errno {
206209
struct http_parser {
207210
/** PRIVATE **/
208211
unsigned int type : 2; /* enum http_parser_type */
209-
unsigned int flags : 7; /* F_* values from 'flags' enum; semi-public */
212+
unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */
210213
unsigned int state : 7; /* enum state from http_parser.c */
211-
unsigned int header_state : 8; /* enum header_state from http_parser.c */
212-
unsigned int index : 8; /* index into current matcher */
214+
unsigned int header_state : 7; /* enum header_state from http_parser.c */
215+
unsigned int index : 7; /* index into current matcher */
216+
unsigned int lenient_http_headers : 1;
213217

214218
uint32_t nread; /* # bytes read in various scenarios */
215219
uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */

deps/http_parser/test.c

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3165,6 +3165,155 @@ test_simple (const char *buf, enum http_errno err_expected)
31653165
}
31663166
}
31673167

3168+
void
3169+
test_invalid_header_content (int req, const char* str)
3170+
{
3171+
http_parser parser;
3172+
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
3173+
size_t parsed;
3174+
const char *buf;
3175+
buf = req ?
3176+
"GET / HTTP/1.1\r\n" :
3177+
"HTTP/1.1 200 OK\r\n";
3178+
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
3179+
assert(parsed == strlen(buf));
3180+
3181+
buf = str;
3182+
size_t buflen = strlen(buf);
3183+
3184+
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
3185+
if (parsed != buflen) {
3186+
assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_HEADER_TOKEN);
3187+
return;
3188+
}
3189+
3190+
fprintf(stderr,
3191+
"\n*** Error expected but none in invalid header content test ***\n");
3192+
abort();
3193+
}
3194+
3195+
void
3196+
test_invalid_header_field_content_error (int req)
3197+
{
3198+
test_invalid_header_content(req, "Foo: F\01ailure");
3199+
test_invalid_header_content(req, "Foo: B\02ar");
3200+
}
3201+
3202+
void
3203+
test_invalid_header_field (int req, const char* str)
3204+
{
3205+
http_parser parser;
3206+
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
3207+
size_t parsed;
3208+
const char *buf;
3209+
buf = req ?
3210+
"GET / HTTP/1.1\r\n" :
3211+
"HTTP/1.1 200 OK\r\n";
3212+
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
3213+
assert(parsed == strlen(buf));
3214+
3215+
buf = str;
3216+
size_t buflen = strlen(buf);
3217+
3218+
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
3219+
if (parsed != buflen) {
3220+
assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_HEADER_TOKEN);
3221+
return;
3222+
}
3223+
3224+
fprintf(stderr,
3225+
"\n*** Error expected but none in invalid header token test ***\n");
3226+
abort();
3227+
}
3228+
3229+
void
3230+
test_invalid_header_field_token_error (int req)
3231+
{
3232+
test_invalid_header_field(req, "Fo@: Failure");
3233+
test_invalid_header_field(req, "Foo\01\test: Bar");
3234+
}
3235+
3236+
void
3237+
test_double_content_length_error (int req)
3238+
{
3239+
http_parser parser;
3240+
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
3241+
size_t parsed;
3242+
const char *buf;
3243+
buf = req ?
3244+
"GET / HTTP/1.1\r\n" :
3245+
"HTTP/1.1 200 OK\r\n";
3246+
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
3247+
assert(parsed == strlen(buf));
3248+
3249+
buf = "Content-Length: 0\r\nContent-Length: 1\r\n\r\n";
3250+
size_t buflen = strlen(buf);
3251+
3252+
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
3253+
if (parsed != buflen) {
3254+
assert(HTTP_PARSER_ERRNO(&parser) == HPE_MULTIPLE_CONTENT_LENGTH);
3255+
return;
3256+
}
3257+
3258+
fprintf(stderr,
3259+
"\n*** Error expected but none in double content-length test ***\n");
3260+
abort();
3261+
}
3262+
3263+
void
3264+
test_chunked_content_length_error (int req)
3265+
{
3266+
http_parser parser;
3267+
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
3268+
size_t parsed;
3269+
const char *buf;
3270+
buf = req ?
3271+
"GET / HTTP/1.1\r\n" :
3272+
"HTTP/1.1 200 OK\r\n";
3273+
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
3274+
assert(parsed == strlen(buf));
3275+
3276+
buf = "Transfer-Encoding: chunked\r\nContent-Length: 1\r\n\r\n";
3277+
size_t buflen = strlen(buf);
3278+
3279+
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
3280+
if (parsed != buflen) {
3281+
assert(HTTP_PARSER_ERRNO(&parser) == HPE_CHUNKED_WITH_CONTENT_LENGTH);
3282+
return;
3283+
}
3284+
3285+
fprintf(stderr,
3286+
"\n*** Error expected but none in chunked content-length test ***\n");
3287+
abort();
3288+
}
3289+
3290+
void
3291+
test_header_cr_no_lf_error (int req)
3292+
{
3293+
http_parser parser;
3294+
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
3295+
size_t parsed;
3296+
const char *buf;
3297+
buf = req ?
3298+
"GET / HTTP/1.1\r\n" :
3299+
"HTTP/1.1 200 OK\r\n";
3300+
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
3301+
assert(parsed == strlen(buf));
3302+
3303+
buf = "Foo: 1\rBar: 1\r\n\r\n";
3304+
size_t buflen = strlen(buf);
3305+
3306+
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
3307+
if (parsed != buflen) {
3308+
assert(HTTP_PARSER_ERRNO(&parser) == HPE_LF_EXPECTED);
3309+
return;
3310+
}
3311+
3312+
fprintf(stderr,
3313+
"\n*** Error expected but none in header whitespace test ***\n");
3314+
abort();
3315+
}
3316+
31683317
void
31693318
test_header_overflow_error (int req)
31703319
{
@@ -3591,6 +3740,18 @@ main (void)
35913740
test_header_content_length_overflow_error();
35923741
test_chunk_content_length_overflow_error();
35933742

3743+
//// HEADER FIELD CONDITIONS
3744+
test_double_content_length_error(HTTP_REQUEST);
3745+
test_chunked_content_length_error(HTTP_REQUEST);
3746+
test_header_cr_no_lf_error(HTTP_REQUEST);
3747+
test_invalid_header_field_token_error(HTTP_REQUEST);
3748+
test_invalid_header_field_content_error(HTTP_REQUEST);
3749+
test_double_content_length_error(HTTP_RESPONSE);
3750+
test_chunked_content_length_error(HTTP_RESPONSE);
3751+
test_header_cr_no_lf_error(HTTP_RESPONSE);
3752+
test_invalid_header_field_token_error(HTTP_RESPONSE);
3753+
test_invalid_header_field_content_error(HTTP_RESPONSE);
3754+
35943755
//// RESPONSES
35953756

35963757
for (i = 0; i < response_count; i++) {

src/node_http_parser.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "node.h"
22
#include "node_buffer.h"
33
#include "node_http_parser.h"
4+
#include "node_revert.h"
45

56
#include "base-object.h"
67
#include "base-object-inl.h"
@@ -670,6 +671,8 @@ class Parser : public BaseObject {
670671

671672
void Init(enum http_parser_type type) {
672673
http_parser_init(&parser_, type);
674+
/* Allow the strict http header parsing to be reverted */
675+
parser_.lenient_http_headers = IsReverted(REVERT_CVE_2016_2216) ? 1 : 0;
673676
url_.Reset();
674677
status_message_.Reset();
675678
num_fields_ = 0;

src/node_revert.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
* For *master* this list should always be empty!
1313
*
1414
**/
15-
#define REVERSIONS(XX)
16-
// XX(CVE_2016_PEND, "CVE-2016-PEND", "Vulnerability Title")
15+
#define REVERSIONS(XX) \
16+
XX(CVE_2016_2216, "CVE-2016-2216", "Strict HTTP Header Parsing")
1717

1818
namespace node {
1919

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const http = require('http');
5+
const net = require('net');
6+
const assert = require('assert');
7+
8+
const reqstr = 'HTTP/1.1 200 OK\r\n' +
9+
'Content-Length: 1\r\n' +
10+
'Transfer-Encoding: chunked\r\n\r\n';
11+
12+
const server = net.createServer((socket) => {
13+
socket.write(reqstr);
14+
});
15+
16+
server.listen(common.PORT, () => {
17+
// The callback should not be called because the server is sending
18+
// both a Content-Length header and a Transfer-Encoding: chunked
19+
// header, which is a violation of the HTTP spec.
20+
const req = http.get({port:common.PORT}, (res) => {
21+
assert.fail(null, null, 'callback should not be called');
22+
});
23+
req.on('error', common.mustCall((err) => {
24+
assert(/^Parse Error/.test(err.message));
25+
assert.equal(err.code, 'HPE_UNEXPECTED_CONTENT_LENGTH');
26+
server.close();
27+
}));
28+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const http = require('http');
5+
const net = require('net');
6+
const assert = require('assert');
7+
8+
const reqstr = 'HTTP/1.1 200 OK\r\n' +
9+
'Foo: Bar\r' +
10+
'Content-Length: 1\r\n\r\n';
11+
12+
const server = net.createServer((socket) => {
13+
socket.write(reqstr);
14+
});
15+
16+
server.listen(common.PORT, () => {
17+
// The callback should not be called because the server is sending a
18+
// header field that ends only in \r with no following \n
19+
const req = http.get({port:common.PORT}, (res) => {
20+
assert.fail(null, null, 'callback should not be called');
21+
});
22+
req.on('error', common.mustCall((err) => {
23+
assert(/^Parse Error/.test(err.message));
24+
assert.equal(err.code, 'HPE_LF_EXPECTED');
25+
server.close();
26+
}));
27+
});

0 commit comments

Comments
 (0)