Skip to content

Commit 3beda5f

Browse files
committed
count headers, URL and start lines separately
Counting headers, URL and start lines separately because it was counting everything as headers before the headers_done state, throwing HPE_HEADER_OVERFLOW errors even when what really caused the overflow was the request URL being too long Creating an HPE_URL_OVERFLOW err to make sure embedders still have a request line limit, but now differentiating what went over such limit. Refs: nodejs/node#26553
1 parent 0d0a24e commit 3beda5f

File tree

3 files changed

+135
-52
lines changed

3 files changed

+135
-52
lines changed

http_parser.c

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include <limits.h>
2727

2828
static uint32_t max_header_size = HTTP_MAX_HEADER_SIZE;
29+
static uint32_t max_url_size = HTTP_MAX_URL_SIZE;
2930

3031
#ifndef ULLONG_MAX
3132
# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */
@@ -160,6 +161,24 @@ do { \
160161
} \
161162
} while (0)
162163

164+
/* Don't allow the total size of the request uri to exceed
165+
* the max_url_size. If we don't check the request uri size and
166+
* it exceeds the max_header_size, the embedder will get an
167+
* HPE_HEADER_OVERFLOW, which is ambiguous in situations where
168+
* what caused the overflow matters, e.g. servers deciding to
169+
* reply either 414 or 431 status.
170+
*/
171+
#include <stdio.h>
172+
#define COUNT_URL_SIZE(V, O) \
173+
do { \
174+
nread += (uint32_t)(V); \
175+
url_offset = (uint8_t)(O); \
176+
if (UNLIKELY(nread > url_offset && \
177+
(nread - url_offset) > max_url_size)) { \
178+
SET_ERRNO(HPE_URL_OVERFLOW); \
179+
goto error; \
180+
} \
181+
} while(0) \
163182

164183
#define PROXY_CONNECTION "proxy-connection"
165184
#define CONNECTION "connection"
@@ -173,12 +192,17 @@ do { \
173192

174193
static const char *method_strings[] =
175194
{
176-
#define XX(num, name, string) #string,
195+
#define XX(num, name, string, length) #string,
177196
HTTP_METHOD_MAP(XX)
178197
#undef XX
179198
};
180199

181-
200+
static const uint8_t method_lengths[] =
201+
{
202+
#define XX(num, name, string, length) length,
203+
HTTP_METHOD_MAP(XX)
204+
#undef XX
205+
};
182206
/* Tokens as defined by rfc 2616. Also lowercases them.
183207
* token = 1*<any CHAR except CTLs or separators>
184208
* separators = "(" | ")" | "<" | ">" | "@"
@@ -358,9 +382,9 @@ enum state
358382
, s_message_done
359383
};
360384

361-
362-
#define PARSING_HEADER(state) (state <= s_headers_done)
363-
385+
#define PARSING_HEADER(state) (state > s_req_http_end && state <= s_headers_done)
386+
#define PARSING_URL(state) (state > s_req_spaces_before_url && state < s_req_http_start)
387+
#define PARSING_START_LINE(state) (state <= s_req_spaces_before_url || (state >= s_req_http_start && state <= s_req_http_end))
364388

365389
enum header_states
366390
{ h_general = 0
@@ -642,6 +666,8 @@ size_t http_parser_execute (http_parser *parser,
642666
{
643667
char c, ch;
644668
int8_t unhex_val;
669+
uint8_t url_offset;
670+
uint32_t spaces_before_url = 0;
645671
const char *p = data;
646672
const char *header_field_mark = 0;
647673
const char *header_value_mark = 0;
@@ -707,9 +733,14 @@ size_t http_parser_execute (http_parser *parser,
707733
for (p=data; p != data + len; p++) {
708734
ch = *p;
709735

710-
if (PARSING_HEADER(CURRENT_STATE()))
736+
if (PARSING_HEADER(CURRENT_STATE())) {
711737
COUNT_HEADER_SIZE(1);
712-
738+
} else if (PARSING_URL(CURRENT_STATE())) {
739+
COUNT_URL_SIZE(1, method_lengths[parser->method] + spaces_before_url);
740+
} else if (PARSING_START_LINE(CURRENT_STATE())) {
741+
nread++;
742+
}
743+
713744
reexecute:
714745
switch (CURRENT_STATE()) {
715746

@@ -1015,7 +1046,10 @@ size_t http_parser_execute (http_parser *parser,
10151046

10161047
case s_req_spaces_before_url:
10171048
{
1018-
if (ch == ' ') break;
1049+
if (ch == ' ') {
1050+
++spaces_before_url;
1051+
break;
1052+
}
10191053

10201054
MARK(url);
10211055
if (parser->method == HTTP_CONNECT) {
@@ -2499,3 +2533,8 @@ void
24992533
http_parser_set_max_header_size(uint32_t size) {
25002534
max_header_size = size;
25012535
}
2536+
2537+
void
2538+
http_parser_set_max_uri_size(uint32_t size) {
2539+
max_url_size = size;
2540+
}

http_parser.h

Lines changed: 60 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,17 @@ typedef unsigned __int64 uint64_t;
6363
# define HTTP_MAX_HEADER_SIZE (80*1024)
6464
#endif
6565

66+
/* Maximium uri size allowed. If the macro is not defined
67+
* before including this header then the default is used. To
68+
* change the maximum uri size, define the macro in the build
69+
* environment (e.g. -DHTTP_MAX_URL_SIZE=<value>). To remove
70+
* the effective limit on the size of the uri, define the macro
71+
* to a very large number (e.g. -DHTTP_MAX_URL_SIZE=0x7fffffff)
72+
*/
73+
#ifndef HTTP_MAX_URL_SIZE
74+
# define HTTP_MAX_URL_SIZE (80*1024)
75+
#endif
76+
6677
typedef struct http_parser http_parser;
6778
typedef struct http_parser_settings http_parser_settings;
6879

@@ -160,53 +171,53 @@ enum http_status
160171

161172

162173
/* Request Methods */
163-
#define HTTP_METHOD_MAP(XX) \
164-
XX(0, DELETE, DELETE) \
165-
XX(1, GET, GET) \
166-
XX(2, HEAD, HEAD) \
167-
XX(3, POST, POST) \
168-
XX(4, PUT, PUT) \
169-
/* pathological */ \
170-
XX(5, CONNECT, CONNECT) \
171-
XX(6, OPTIONS, OPTIONS) \
172-
XX(7, TRACE, TRACE) \
173-
/* WebDAV */ \
174-
XX(8, COPY, COPY) \
175-
XX(9, LOCK, LOCK) \
176-
XX(10, MKCOL, MKCOL) \
177-
XX(11, MOVE, MOVE) \
178-
XX(12, PROPFIND, PROPFIND) \
179-
XX(13, PROPPATCH, PROPPATCH) \
180-
XX(14, SEARCH, SEARCH) \
181-
XX(15, UNLOCK, UNLOCK) \
182-
XX(16, BIND, BIND) \
183-
XX(17, REBIND, REBIND) \
184-
XX(18, UNBIND, UNBIND) \
185-
XX(19, ACL, ACL) \
186-
/* subversion */ \
187-
XX(20, REPORT, REPORT) \
188-
XX(21, MKACTIVITY, MKACTIVITY) \
189-
XX(22, CHECKOUT, CHECKOUT) \
190-
XX(23, MERGE, MERGE) \
191-
/* upnp */ \
192-
XX(24, MSEARCH, M-SEARCH) \
193-
XX(25, NOTIFY, NOTIFY) \
194-
XX(26, SUBSCRIBE, SUBSCRIBE) \
195-
XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
196-
/* RFC-5789 */ \
197-
XX(28, PATCH, PATCH) \
198-
XX(29, PURGE, PURGE) \
199-
/* CalDAV */ \
200-
XX(30, MKCALENDAR, MKCALENDAR) \
201-
/* RFC-2068, section 19.6.1.2 */ \
202-
XX(31, LINK, LINK) \
203-
XX(32, UNLINK, UNLINK) \
204-
/* icecast */ \
205-
XX(33, SOURCE, SOURCE) \
174+
#define HTTP_METHOD_MAP(XX) \
175+
XX(0, DELETE, DELETE, 6) \
176+
XX(1, GET, GET, 3) \
177+
XX(2, HEAD, HEAD, 4) \
178+
XX(3, POST, POST, 4) \
179+
XX(4, PUT, PUT, 3) \
180+
/* pathological */ \
181+
XX(5, CONNECT, CONNECT, 7) \
182+
XX(6, OPTIONS, OPTIONS, 7) \
183+
XX(7, TRACE, TRACE, 5) \
184+
/* WebDAV */ \
185+
XX(8, COPY, COPY, 4) \
186+
XX(9, LOCK, LOCK, 4) \
187+
XX(10, MKCOL, MKCOL, 5) \
188+
XX(11, MOVE, MOVE, 4) \
189+
XX(12, PROPFIND, PROPFIND, 8) \
190+
XX(13, PROPPATCH, PROPPATCH, 9) \
191+
XX(14, SEARCH, SEARCH, 6) \
192+
XX(15, UNLOCK, UNLOCK, 6) \
193+
XX(16, BIND, BIND, 4) \
194+
XX(17, REBIND, REBIND, 6) \
195+
XX(18, UNBIND, UNBIND, 6) \
196+
XX(19, ACL, ACL, 3) \
197+
/* subversion */ \
198+
XX(20, REPORT, REPORT, 6) \
199+
XX(21, MKACTIVITY, MKACTIVITY, 10) \
200+
XX(22, CHECKOUT, CHECKOUT, 8) \
201+
XX(23, MERGE, MERGE, 5) \
202+
/* upnp */ \
203+
XX(24, MSEARCH, M-SEARCH, 7) \
204+
XX(25, NOTIFY, NOTIFY, 6) \
205+
XX(26, SUBSCRIBE, SUBSCRIBE, 9) \
206+
XX(27, UNSUBSCRIBE, UNSUBSCRIBE, 11) \
207+
/* RFC-5789 */ \
208+
XX(28, PATCH, PATCH, 5) \
209+
XX(29, PURGE, PURGE, 5) \
210+
/* CalDAV */ \
211+
XX(30, MKCALENDAR, MKCALENDAR, 10) \
212+
/* RFC-2068, section 19.6.1.2 */ \
213+
XX(31, LINK, LINK, 4) \
214+
XX(32, UNLINK, UNLINK, 6) \
215+
/* icecast */ \
216+
XX(33, SOURCE, SOURCE, 6) \
206217

207218
enum http_method
208219
{
209-
#define XX(num, name, string) HTTP_##name = num,
220+
#define XX(num, name, string, length) HTTP_##name = num,
210221
HTTP_METHOD_MAP(XX)
211222
#undef XX
212223
};
@@ -252,6 +263,8 @@ enum flags
252263
XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
253264
XX(HEADER_OVERFLOW, \
254265
"too many header bytes seen; overflow detected") \
266+
XX(URL_OVERFLOW, \
267+
"too many url bytes seen; overflow detected") \
255268
XX(CLOSED_CONNECTION, \
256269
"data received after completed connection: close message") \
257270
XX(INVALID_VERSION, "invalid HTTP version") \
@@ -433,6 +446,9 @@ int http_body_is_final(const http_parser *parser);
433446
/* Change the maximum header size provided at compile time. */
434447
void http_parser_set_max_header_size(uint32_t size);
435448

449+
/* Change the maximum uri size provided at compile time. */
450+
void http_parser_set_max_uri_size(uint32_t size);
451+
436452
#ifdef __cplusplus
437453
}
438454
#endif

test.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3756,6 +3756,32 @@ test_header_overflow_error (int req)
37563756
abort();
37573757
}
37583758

3759+
void
3760+
test_URL_OVERFLOW_error ()
3761+
{
3762+
http_parser parser;
3763+
http_parser_init(&parser, HTTP_REQUEST);
3764+
size_t parsed;
3765+
const char *buf;
3766+
buf = "GET /";
3767+
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
3768+
assert(parsed == strlen(buf));
3769+
3770+
buf = "a";
3771+
size_t buflen = strlen(buf);
3772+
3773+
int i;
3774+
for (i = 0; i < HTTP_MAX_URL_SIZE - 1; i++) { //-1 because it started with a slash
3775+
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
3776+
if (parsed != buflen) {
3777+
assert(HTTP_PARSER_ERRNO(&parser) == HPE_URL_OVERFLOW);
3778+
return;
3779+
}
3780+
}
3781+
3782+
fprintf(stderr, "\n*** Error expected but none in uri overflow test ***\n");
3783+
abort();
3784+
}
37593785

37603786
void
37613787
test_header_nread_value ()
@@ -4159,6 +4185,8 @@ main (void)
41594185
//// OVERFLOW CONDITIONS
41604186
test_no_overflow_parse_url();
41614187

4188+
test_URL_OVERFLOW_error();
4189+
41624190
test_header_overflow_error(HTTP_REQUEST);
41634191
test_no_overflow_long_body(HTTP_REQUEST, 1000);
41644192
test_no_overflow_long_body(HTTP_REQUEST, 100000);

0 commit comments

Comments
 (0)