1
0
Fork 0
mirror of https://github.com/puma/puma.git synced 2022-11-09 13:48:40 -05:00

Several robustness improvements to protect mongrel against bad HTTP requests.

git-svn-id: svn+ssh://rubyforge.org/var/svn/mongrel/trunk@138 19e92222-5c0b-0410-8929-a290d50e31e9
This commit is contained in:
zedshaw 2006-04-03 02:27:59 +00:00
parent 6c8d479b38
commit 3c804d5e15
6 changed files with 201 additions and 77 deletions

View file

@ -9,6 +9,7 @@
static VALUE mMongrel; static VALUE mMongrel;
static VALUE cHttpParser; static VALUE cHttpParser;
static VALUE cURIClassifier; static VALUE cURIClassifier;
static VALUE eHttpParserError;
static int id_handler_map; static int id_handler_map;
static VALUE global_http_prefix; static VALUE global_http_prefix;
@ -32,15 +33,36 @@ static VALUE global_mongrel_version;
static VALUE global_server_software; static VALUE global_server_software;
static VALUE global_port_80; static VALUE global_port_80;
#define DEF_GLOBAL(name, val) global_##name = rb_obj_freeze(rb_str_new2(val)); rb_global_variable(&global_##name); /** Defines common length and error messages for input length validation. */
#define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N " is longer than the " # length " allowed length.";
/** Validates the max length of given input and throws an HttpParserError exception if over. */
#define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR); }
/** Defines global strings in the init method. */
#define DEF_GLOBAL(N, val) global_##N = rb_obj_freeze(rb_str_new2(val)); rb_global_variable(&global_##N);
/* Defines the maximum allowed lengths for various input elements.*/
DEF_MAX_LENGTH(FIELD_NAME, 256);
DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
DEF_MAX_LENGTH(REQUEST_URI, 512);
DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
void http_field(void *data, const char *field, size_t flen, const char *value, size_t vlen) void http_field(void *data, const char *field, size_t flen, const char *value, size_t vlen)
{ {
char *ch, *end; char *ch, *end;
VALUE req = (VALUE)data; VALUE req = (VALUE)data;
VALUE v = rb_str_new(value, vlen); VALUE v = Qnil;
VALUE f = rb_str_dup(global_http_prefix); VALUE f = Qnil;
VALIDATE_MAX_LENGTH(flen, FIELD_NAME);
VALIDATE_MAX_LENGTH(vlen, FIELD_VALUE);
v = rb_str_new(value, vlen);
f = rb_str_dup(global_http_prefix);
f = rb_str_buf_cat(f, field, flen); f = rb_str_buf_cat(f, field, flen);
for(ch = RSTRING(f)->ptr, end = ch + RSTRING(f)->len; ch < end; ch++) { for(ch = RSTRING(f)->ptr, end = ch + RSTRING(f)->len; ch < end; ch++) {
@ -57,14 +79,20 @@ void http_field(void *data, const char *field, size_t flen, const char *value, s
void request_method(void *data, const char *at, size_t length) void request_method(void *data, const char *at, size_t length)
{ {
VALUE req = (VALUE)data; VALUE req = (VALUE)data;
VALUE val = rb_str_new(at, length); VALUE val = Qnil;
val = rb_str_new(at, length);
rb_hash_aset(req, global_request_method, val); rb_hash_aset(req, global_request_method, val);
} }
void request_uri(void *data, const char *at, size_t length) void request_uri(void *data, const char *at, size_t length)
{ {
VALUE req = (VALUE)data; VALUE req = (VALUE)data;
VALUE val = rb_str_new(at, length); VALUE val = Qnil;
VALIDATE_MAX_LENGTH(length, REQUEST_URI);
val = rb_str_new(at, length);
rb_hash_aset(req, global_request_uri, val); rb_hash_aset(req, global_request_uri, val);
} }
@ -72,7 +100,11 @@ void request_uri(void *data, const char *at, size_t length)
void query_string(void *data, const char *at, size_t length) void query_string(void *data, const char *at, size_t length)
{ {
VALUE req = (VALUE)data; VALUE req = (VALUE)data;
VALUE val = rb_str_new(at, length); VALUE val = Qnil;
VALIDATE_MAX_LENGTH(length, QUERY_STRING);
val = rb_str_new(at, length);
rb_hash_aset(req, global_query_string, val); rb_hash_aset(req, global_query_string, val);
} }
@ -136,6 +168,7 @@ VALUE HttpParser_alloc(VALUE klass)
hp->query_string = query_string; hp->query_string = query_string;
hp->http_version = http_version; hp->http_version = http_version;
hp->header_done = header_done; hp->header_done = header_done;
http_parser_init(hp);
obj = Data_Wrap_Struct(klass, NULL, HttpParser_free, hp); obj = Data_Wrap_Struct(klass, NULL, HttpParser_free, hp);
@ -214,8 +247,10 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data)
http->data = (void *)req_hash; http->data = (void *)req_hash;
http_parser_execute(http, RSTRING(data)->ptr, RSTRING(data)->len); http_parser_execute(http, RSTRING(data)->ptr, RSTRING(data)->len);
VALIDATE_MAX_LENGTH(http_parser_nread(http), HEADER);
if(http_parser_has_error(http)) { if(http_parser_has_error(http)) {
rb_raise(rb_eStandardError, "HTTP Parsing failure"); rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
} else { } else {
return INT2FIX(http_parser_nread(http)); return INT2FIX(http_parser_nread(http));
} }
@ -479,6 +514,8 @@ void Init_http11()
DEF_GLOBAL(server_software, "SERVER_SOFTWARE"); DEF_GLOBAL(server_software, "SERVER_SOFTWARE");
DEF_GLOBAL(port_80, "80"); DEF_GLOBAL(port_80, "80");
eHttpParserError = rb_define_class_under(mMongrel, "HttpParserError", rb_eIOError);
cHttpParser = rb_define_class_under(mMongrel, "HttpParser", rb_cObject); cHttpParser = rb_define_class_under(mMongrel, "HttpParser", rb_cObject);
rb_define_alloc_func(cHttpParser, HttpParser_alloc); rb_define_alloc_func(cHttpParser, HttpParser_alloc);
rb_define_method(cHttpParser, "initialize", HttpParser_init,0); rb_define_method(cHttpParser, "initialize", HttpParser_init,0);

View file

@ -6,10 +6,10 @@
#include <ctype.h> #include <ctype.h>
#include <string.h> #include <string.h>
#define MARK(S,F) assert((F) - (S)->mark >= 0); (S)->mark = (F); #define MARK(S,F) (S)->mark = (F);
/** machine **/ /** machine **/
#line 101 "ext/http11/http11_parser.rl" #line 107 "ext/http11/http11_parser.rl"
/** Data **/ /** Data **/
@ -21,7 +21,7 @@ static int http_parser_first_final = 53;
static int http_parser_error = 1; static int http_parser_error = 1;
#line 105 "ext/http11/http11_parser.rl" #line 111 "ext/http11/http11_parser.rl"
int http_parser_init(http_parser *parser) { int http_parser_init(http_parser *parser) {
int cs = 0; int cs = 0;
@ -30,7 +30,7 @@ int http_parser_init(http_parser *parser) {
{ {
cs = http_parser_start; cs = http_parser_start;
} }
#line 109 "ext/http11/http11_parser.rl" #line 115 "ext/http11/http11_parser.rl"
parser->cs = cs; parser->cs = cs;
parser->body_start = NULL; parser->body_start = NULL;
parser->content_len = 0; parser->content_len = 0;
@ -71,7 +71,7 @@ st1:
goto _out1; goto _out1;
tr13: tr13:
#line 14 "ext/http11/http11_parser.rl" #line 14 "ext/http11/http11_parser.rl"
{ MARK(parser, p); } {MARK(parser, p); }
goto st2; goto st2;
st2: st2:
if ( ++p == pe ) if ( ++p == pe )
@ -117,9 +117,10 @@ case 7:
goto tr33; goto tr33;
goto st1; goto st1;
tr33: tr33:
#line 29 "ext/http11/http11_parser.rl" #line 30 "ext/http11/http11_parser.rl"
{ {
if(parser->request_method != NULL) assert(p - parser->mark >= 0 && "buffer overflow");
if(parser->request_method != NULL)
parser->request_method(parser->data, parser->mark, p - parser->mark); parser->request_method(parser->data, parser->mark, p - parser->mark);
} }
goto st8; goto st8;
@ -127,7 +128,7 @@ st8:
if ( ++p == pe ) if ( ++p == pe )
goto _out8; goto _out8;
case 8: case 8:
#line 131 "ext/http11/http11_parser.c" #line 132 "ext/http11/http11_parser.c"
switch( (*p) ) { switch( (*p) ) {
case 42: goto tr27; case 42: goto tr27;
case 43: goto tr28; case 43: goto tr28;
@ -145,26 +146,28 @@ case 8:
goto st1; goto st1;
tr27: tr27:
#line 14 "ext/http11/http11_parser.rl" #line 14 "ext/http11/http11_parser.rl"
{ MARK(parser, p); } {MARK(parser, p); }
goto st9; goto st9;
st9: st9:
if ( ++p == pe ) if ( ++p == pe )
goto _out9; goto _out9;
case 9: case 9:
#line 155 "ext/http11/http11_parser.c" #line 156 "ext/http11/http11_parser.c"
if ( (*p) == 32 ) if ( (*p) == 32 )
goto tr34; goto tr34;
goto st1; goto st1;
tr34: tr34:
#line 33 "ext/http11/http11_parser.rl" #line 35 "ext/http11/http11_parser.rl"
{ {
assert(p - parser->mark >= 0 && "buffer overflow");
if(parser->request_uri != NULL) if(parser->request_uri != NULL)
parser->request_uri(parser->data, parser->mark, p - parser->mark); parser->request_uri(parser->data, parser->mark, p - parser->mark);
} }
goto st10; goto st10;
tr46: tr46:
#line 37 "ext/http11/http11_parser.rl" #line 40 "ext/http11/http11_parser.rl"
{ {
assert(p - parser->mark >= 0 && "buffer overflow");
if(parser->query_string != NULL) if(parser->query_string != NULL)
parser->query_string(parser->data, parser->mark, p - parser->mark); parser->query_string(parser->data, parser->mark, p - parser->mark);
} }
@ -173,19 +176,19 @@ st10:
if ( ++p == pe ) if ( ++p == pe )
goto _out10; goto _out10;
case 10: case 10:
#line 177 "ext/http11/http11_parser.c" #line 180 "ext/http11/http11_parser.c"
if ( (*p) == 72 ) if ( (*p) == 72 )
goto tr11; goto tr11;
goto st1; goto st1;
tr11: tr11:
#line 14 "ext/http11/http11_parser.rl" #line 14 "ext/http11/http11_parser.rl"
{ MARK(parser, p); } {MARK(parser, p); }
goto st11; goto st11;
st11: st11:
if ( ++p == pe ) if ( ++p == pe )
goto _out11; goto _out11;
case 11: case 11:
#line 189 "ext/http11/http11_parser.c" #line 192 "ext/http11/http11_parser.c"
if ( (*p) == 84 ) if ( (*p) == 84 )
goto st12; goto st12;
goto st1; goto st1;
@ -243,8 +246,9 @@ case 18:
goto st18; goto st18;
goto st1; goto st1;
tr37: tr37:
#line 42 "ext/http11/http11_parser.rl" #line 46 "ext/http11/http11_parser.rl"
{ {
assert(p - parser->mark >= 0 && "buffer overflow");
if(parser->http_version != NULL) if(parser->http_version != NULL)
parser->http_version(parser->data, parser->mark, p - parser->mark); parser->http_version(parser->data, parser->mark, p - parser->mark);
} }
@ -252,6 +256,7 @@ tr37:
tr49: tr49:
#line 22 "ext/http11/http11_parser.rl" #line 22 "ext/http11/http11_parser.rl"
{ {
assert(p - (parser->mark - 1) >= 0 && "buffer overflow");
if(parser->http_field != NULL) { if(parser->http_field != NULL) {
parser->http_field(parser->data, parser->http_field(parser->data,
parser->field_start, parser->field_len, parser->field_start, parser->field_len,
@ -263,7 +268,7 @@ st19:
if ( ++p == pe ) if ( ++p == pe )
goto _out19; goto _out19;
case 19: case 19:
#line 267 "ext/http11/http11_parser.c" #line 272 "ext/http11/http11_parser.c"
if ( (*p) == 10 ) if ( (*p) == 10 )
goto st20; goto st20;
goto st1; goto st1;
@ -303,7 +308,7 @@ case 21:
goto tr40; goto tr40;
goto st1; goto st1;
tr40: tr40:
#line 46 "ext/http11/http11_parser.rl" #line 52 "ext/http11/http11_parser.rl"
{ {
parser->body_start = p+1; parser->body_start = p+1;
if(parser->header_done != NULL) if(parser->header_done != NULL)
@ -315,7 +320,7 @@ st53:
if ( ++p == pe ) if ( ++p == pe )
goto _out53; goto _out53;
case 53: case 53:
#line 319 "ext/http11/http11_parser.c" #line 324 "ext/http11/http11_parser.c"
goto st1; goto st1;
tr36: tr36:
#line 16 "ext/http11/http11_parser.rl" #line 16 "ext/http11/http11_parser.rl"
@ -325,7 +330,7 @@ st22:
if ( ++p == pe ) if ( ++p == pe )
goto _out22; goto _out22;
case 22: case 22:
#line 329 "ext/http11/http11_parser.c" #line 334 "ext/http11/http11_parser.c"
switch( (*p) ) { switch( (*p) ) {
case 33: goto st22; case 33: goto st22;
case 58: goto tr32; case 58: goto tr32;
@ -360,7 +365,7 @@ st23:
if ( ++p == pe ) if ( ++p == pe )
goto _out23; goto _out23;
case 23: case 23:
#line 364 "ext/http11/http11_parser.c" #line 369 "ext/http11/http11_parser.c"
if ( (*p) == 13 ) if ( (*p) == 13 )
goto tr49; goto tr49;
goto tr52; goto tr52;
@ -372,19 +377,19 @@ st24:
if ( ++p == pe ) if ( ++p == pe )
goto _out24; goto _out24;
case 24: case 24:
#line 376 "ext/http11/http11_parser.c" #line 381 "ext/http11/http11_parser.c"
if ( (*p) == 13 ) if ( (*p) == 13 )
goto tr49; goto tr49;
goto st24; goto st24;
tr28: tr28:
#line 14 "ext/http11/http11_parser.rl" #line 14 "ext/http11/http11_parser.rl"
{ MARK(parser, p); } {MARK(parser, p); }
goto st25; goto st25;
st25: st25:
if ( ++p == pe ) if ( ++p == pe )
goto _out25; goto _out25;
case 25: case 25:
#line 388 "ext/http11/http11_parser.c" #line 393 "ext/http11/http11_parser.c"
switch( (*p) ) { switch( (*p) ) {
case 43: goto st25; case 43: goto st25;
case 58: goto st26; case 58: goto st26;
@ -403,13 +408,13 @@ case 25:
goto st1; goto st1;
tr30: tr30:
#line 14 "ext/http11/http11_parser.rl" #line 14 "ext/http11/http11_parser.rl"
{ MARK(parser, p); } {MARK(parser, p); }
goto st26; goto st26;
st26: st26:
if ( ++p == pe ) if ( ++p == pe )
goto _out26; goto _out26;
case 26: case 26:
#line 413 "ext/http11/http11_parser.c" #line 418 "ext/http11/http11_parser.c"
switch( (*p) ) { switch( (*p) ) {
case 32: goto tr34; case 32: goto tr34;
case 37: goto st27; case 37: goto st27;
@ -451,20 +456,20 @@ case 28:
goto st1; goto st1;
tr29: tr29:
#line 14 "ext/http11/http11_parser.rl" #line 14 "ext/http11/http11_parser.rl"
{ MARK(parser, p); } {MARK(parser, p); }
goto st29; goto st29;
st29: st29:
if ( ++p == pe ) if ( ++p == pe )
goto _out29; goto _out29;
case 29: case 29:
#line 461 "ext/http11/http11_parser.c" #line 466 "ext/http11/http11_parser.c"
switch( (*p) ) { switch( (*p) ) {
case 32: goto tr34; case 32: goto tr34;
case 37: goto st31; case 37: goto st31;
case 47: goto st1; case 47: goto st1;
case 60: goto st1; case 60: goto st1;
case 62: goto st1; case 62: goto st1;
case 63: goto tr44; case 63: goto tr43;
case 127: goto st1; case 127: goto st1;
} }
if ( (*p) > 31 ) { if ( (*p) > 31 ) {
@ -482,7 +487,7 @@ case 30:
case 37: goto st31; case 37: goto st31;
case 60: goto st1; case 60: goto st1;
case 62: goto st1; case 62: goto st1;
case 63: goto tr44; case 63: goto tr43;
case 127: goto st1; case 127: goto st1;
} }
if ( (*p) > 31 ) { if ( (*p) > 31 ) {
@ -517,9 +522,10 @@ case 32:
} else } else
goto st30; goto st30;
goto st1; goto st1;
tr44: tr43:
#line 33 "ext/http11/http11_parser.rl" #line 35 "ext/http11/http11_parser.rl"
{ {
assert(p - parser->mark >= 0 && "buffer overflow");
if(parser->request_uri != NULL) if(parser->request_uri != NULL)
parser->request_uri(parser->data, parser->mark, p - parser->mark); parser->request_uri(parser->data, parser->mark, p - parser->mark);
} }
@ -528,7 +534,7 @@ st33:
if ( ++p == pe ) if ( ++p == pe )
goto _out33; goto _out33;
case 33: case 33:
#line 532 "ext/http11/http11_parser.c" #line 538 "ext/http11/http11_parser.c"
switch( (*p) ) { switch( (*p) ) {
case 32: goto tr46; case 32: goto tr46;
case 37: goto tr51; case 37: goto tr51;
@ -544,13 +550,13 @@ case 33:
goto tr50; goto tr50;
tr50: tr50:
#line 14 "ext/http11/http11_parser.rl" #line 14 "ext/http11/http11_parser.rl"
{ MARK(parser, p); } {MARK(parser, p); }
goto st34; goto st34;
st34: st34:
if ( ++p == pe ) if ( ++p == pe )
goto _out34; goto _out34;
case 34: case 34:
#line 554 "ext/http11/http11_parser.c" #line 560 "ext/http11/http11_parser.c"
switch( (*p) ) { switch( (*p) ) {
case 32: goto tr46; case 32: goto tr46;
case 37: goto st35; case 37: goto st35;
@ -566,13 +572,13 @@ case 34:
goto st34; goto st34;
tr51: tr51:
#line 14 "ext/http11/http11_parser.rl" #line 14 "ext/http11/http11_parser.rl"
{ MARK(parser, p); } {MARK(parser, p); }
goto st35; goto st35;
st35: st35:
if ( ++p == pe ) if ( ++p == pe )
goto _out35; goto _out35;
case 35: case 35:
#line 576 "ext/http11/http11_parser.c" #line 582 "ext/http11/http11_parser.c"
if ( (*p) < 65 ) { if ( (*p) < 65 ) {
if ( 48 <= (*p) && (*p) <= 57 ) if ( 48 <= (*p) && (*p) <= 57 )
goto st36; goto st36;
@ -597,13 +603,13 @@ case 36:
goto st1; goto st1;
tr14: tr14:
#line 14 "ext/http11/http11_parser.rl" #line 14 "ext/http11/http11_parser.rl"
{ MARK(parser, p); } {MARK(parser, p); }
goto st37; goto st37;
st37: st37:
if ( ++p == pe ) if ( ++p == pe )
goto _out37; goto _out37;
case 37: case 37:
#line 607 "ext/http11/http11_parser.c" #line 613 "ext/http11/http11_parser.c"
if ( (*p) == 69 ) if ( (*p) == 69 )
goto st38; goto st38;
goto st1; goto st1;
@ -616,13 +622,13 @@ case 38:
goto st1; goto st1;
tr15: tr15:
#line 14 "ext/http11/http11_parser.rl" #line 14 "ext/http11/http11_parser.rl"
{ MARK(parser, p); } {MARK(parser, p); }
goto st39; goto st39;
st39: st39:
if ( ++p == pe ) if ( ++p == pe )
goto _out39; goto _out39;
case 39: case 39:
#line 626 "ext/http11/http11_parser.c" #line 632 "ext/http11/http11_parser.c"
if ( (*p) == 69 ) if ( (*p) == 69 )
goto st40; goto st40;
goto st1; goto st1;
@ -642,13 +648,13 @@ case 41:
goto st1; goto st1;
tr16: tr16:
#line 14 "ext/http11/http11_parser.rl" #line 14 "ext/http11/http11_parser.rl"
{ MARK(parser, p); } {MARK(parser, p); }
goto st42; goto st42;
st42: st42:
if ( ++p == pe ) if ( ++p == pe )
goto _out42; goto _out42;
case 42: case 42:
#line 652 "ext/http11/http11_parser.c" #line 658 "ext/http11/http11_parser.c"
if ( (*p) == 80 ) if ( (*p) == 80 )
goto st43; goto st43;
goto st1; goto st1;
@ -689,13 +695,13 @@ case 47:
goto st1; goto st1;
tr17: tr17:
#line 14 "ext/http11/http11_parser.rl" #line 14 "ext/http11/http11_parser.rl"
{ MARK(parser, p); } {MARK(parser, p); }
goto st48; goto st48;
st48: st48:
if ( ++p == pe ) if ( ++p == pe )
goto _out48; goto _out48;
case 48: case 48:
#line 699 "ext/http11/http11_parser.c" #line 705 "ext/http11/http11_parser.c"
switch( (*p) ) { switch( (*p) ) {
case 79: goto st49; case 79: goto st49;
case 85: goto st38; case 85: goto st38;
@ -710,13 +716,13 @@ case 49:
goto st1; goto st1;
tr18: tr18:
#line 14 "ext/http11/http11_parser.rl" #line 14 "ext/http11/http11_parser.rl"
{ MARK(parser, p); } {MARK(parser, p); }
goto st50; goto st50;
st50: st50:
if ( ++p == pe ) if ( ++p == pe )
goto _out50; goto _out50;
case 50: case 50:
#line 720 "ext/http11/http11_parser.c" #line 726 "ext/http11/http11_parser.c"
if ( (*p) == 82 ) if ( (*p) == 82 )
goto st51; goto st51;
goto st1; goto st1;
@ -791,15 +797,15 @@ case 52:
_out: {} _out: {}
} }
#line 128 "ext/http11/http11_parser.rl" #line 134 "ext/http11/http11_parser.rl"
parser->cs = cs; parser->cs = cs;
parser->nread = p - buffer; parser->nread = p - buffer;
if(parser->body_start) { if(parser->body_start) {
/* final \r\n combo encountered so stop right here */ /* final \r\n combo encountered so stop right here */
#line 802 "ext/http11/http11_parser.c" #line 808 "ext/http11/http11_parser.c"
#line 134 "ext/http11/http11_parser.rl" #line 140 "ext/http11/http11_parser.rl"
parser->nread++; parser->nread++;
} }
@ -811,8 +817,8 @@ int http_parser_finish(http_parser *parser)
int cs = parser->cs; int cs = parser->cs;
#line 815 "ext/http11/http11_parser.c" #line 821 "ext/http11/http11_parser.c"
#line 145 "ext/http11/http11_parser.rl" #line 151 "ext/http11/http11_parser.rl"
parser->cs = cs; parser->cs = cs;

View file

@ -5,13 +5,13 @@
#include <ctype.h> #include <ctype.h>
#include <string.h> #include <string.h>
#define MARK(S,F) assert((F) - (S)->mark >= 0); (S)->mark = (F); #define MARK(S,F) (S)->mark = (F);
/** machine **/ /** machine **/
%%{ %%{
machine http_parser; machine http_parser;
action mark { MARK(parser, fpc); } action mark {MARK(parser, fpc); }
action start_field { parser->field_start = fpc; } action start_field { parser->field_start = fpc; }
action write_field { action write_field {
@ -20,6 +20,7 @@
action start_value { MARK(parser, fpc); } action start_value { MARK(parser, fpc); }
action write_value { action write_value {
assert(p - (parser->mark - 1) >= 0 && "buffer overflow");
if(parser->http_field != NULL) { if(parser->http_field != NULL) {
parser->http_field(parser->data, parser->http_field(parser->data,
parser->field_start, parser->field_len, parser->field_start, parser->field_len,
@ -27,22 +28,27 @@
} }
} }
action request_method { action request_method {
if(parser->request_method != NULL) assert(p - parser->mark >= 0 && "buffer overflow");
if(parser->request_method != NULL)
parser->request_method(parser->data, parser->mark, p - parser->mark); parser->request_method(parser->data, parser->mark, p - parser->mark);
} }
action request_uri { action request_uri {
assert(p - parser->mark >= 0 && "buffer overflow");
if(parser->request_uri != NULL) if(parser->request_uri != NULL)
parser->request_uri(parser->data, parser->mark, p - parser->mark); parser->request_uri(parser->data, parser->mark, p - parser->mark);
} }
action query_string { action query_string {
assert(p - parser->mark >= 0 && "buffer overflow");
if(parser->query_string != NULL) if(parser->query_string != NULL)
parser->query_string(parser->data, parser->mark, p - parser->mark); parser->query_string(parser->data, parser->mark, p - parser->mark);
} }
action http_version { action http_version {
assert(p - parser->mark >= 0 && "buffer overflow");
if(parser->http_version != NULL) if(parser->http_version != NULL)
parser->http_version(parser->data, parser->mark, p - parser->mark); parser->http_version(parser->data, parser->mark, p - parser->mark);
} }
action done { action done {
parser->body_start = p+1; parser->body_start = p+1;
if(parser->header_done != NULL) if(parser->header_done != NULL)
@ -61,7 +67,7 @@
extra = ("!" | "*" | "'" | "(" | ")" | ","); extra = ("!" | "*" | "'" | "(" | ")" | ",");
reserved = (";" | "/" | "?" | ":" | "@" | "&" | "=" | "+"); reserved = (";" | "/" | "?" | ":" | "@" | "&" | "=" | "+");
unsafe = (CTL | " " | "\"" | "#" | "%" | "<" | ">"); unsafe = (CTL | " " | "\"" | "#" | "%" | "<" | ">");
national = any - (alpha | digit | reserved | extra | safe | unsafe); national = any -- (alpha | digit | reserved | extra | safe | unsafe);
unreserved = (alpha | digit | safe | extra | national); unreserved = (alpha | digit | safe | extra | national);
escape = ("%" xdigit xdigit); escape = ("%" xdigit xdigit);
uchar = (unreserved | escape); uchar = (unreserved | escape);
@ -69,7 +75,7 @@
tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t"); tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");
# elements # elements
token = (ascii - (CTL | tspecials)); token = (ascii -- (CTL | tspecials));
# URI schemes and absolute paths # URI schemes and absolute paths
scheme = ( alpha | digit | "+" | "-" | "." )* ; scheme = ( alpha | digit | "+" | "-" | "." )* ;
@ -89,11 +95,11 @@
HTTP_Version = ("HTTP/" http_number) >mark %http_version ; HTTP_Version = ("HTTP/" http_number) >mark %http_version ;
Request_Line = (Method " " Request_URI " " HTTP_Version CRLF) ; Request_Line = (Method " " Request_URI " " HTTP_Version CRLF) ;
field_name = (token - ":")+ >start_field %write_field; field_name = (token -- ":")+ >start_field %write_field;
field_value = any* >start_value %write_value; field_value = any* >start_value %write_value;
message_header = field_name ":" field_value $0 CRLF >1; message_header = field_name ":" field_value :> CRLF;
Request = Request_Line (message_header)* ( CRLF @done); Request = Request_Line (message_header)* ( CRLF @done);

View file

@ -110,7 +110,11 @@ module Mongrel
ERROR_503_RESPONSE="HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze ERROR_503_RESPONSE="HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
# The basic max request size we'll try to read. # The basic max request size we'll try to read.
CHUNK_SIZE=(16 * 1024) CHUNK_SIZE=(4 * 1024)
# This is the maximum header that is allowed before a client is booted. The parser detects
# this, but we'd also like to do this as well.
MAX_HEADER=1024 * (80 + 32)
# Format to generate a correct RFC 1123 date. rdoc for Time is wrong, there is no httpdate function. # Format to generate a correct RFC 1123 date. rdoc for Time is wrong, there is no httpdate function.
RFC_1123_DATE_FORMAT="%a, %d %B %Y %H:%M:%S GMT".freeze RFC_1123_DATE_FORMAT="%a, %d %B %Y %H:%M:%S GMT".freeze
@ -259,13 +263,12 @@ module Mongrel
attr_reader :header_sent attr_reader :header_sent
attr_reader :status_sent attr_reader :status_sent
def initialize(socket, filter = nil) def initialize(socket)
@socket = socket @socket = socket
@body = StringIO.new @body = StringIO.new
@status = 404 @status = 404
@header = HeaderOut.new(StringIO.new) @header = HeaderOut.new(StringIO.new)
@header[Const::DATE] = HttpServer.httpdate(Time.now) @header[Const::DATE] = HttpServer.httpdate(Time.now)
@filter = filter
@body_sent = false @body_sent = false
@header_sent = false @header_sent = false
@status_sent = false @status_sent = false
@ -343,6 +346,8 @@ module Mongrel
end end
# This is the main driver of Mongrel, while the Mognrel::HttpParser and Mongrel::URIClassifier # This is the main driver of Mongrel, while the Mognrel::HttpParser and Mongrel::URIClassifier
# make up the majority of how the server functions. It's a very simple class that just # make up the majority of how the server functions. It's a very simple class that just
# has a thread accepting connections and a simple HttpServer.process_client function # has a thread accepting connections and a simple HttpServer.process_client function
@ -432,14 +437,20 @@ module Mongrel
else else
# gotta stream and read again until we can get the parser to be character safe # gotta stream and read again until we can get the parser to be character safe
# TODO: make this more efficient since this means we're parsing a lot repeatedly # TODO: make this more efficient since this means we're parsing a lot repeatedly
if data.length >= Const::MAX_HEADER
raise HttpParserError.new("HEADER is longer than allowed, aborting client early.")
end
parser.reset parser.reset
data << client.readpartial(Const::CHUNK_SIZE) data << client.readpartial(Const::CHUNK_SIZE)
end end
end end
rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL
# ignored # ignored
rescue HttpParserError
STDERR.puts "BAD CLIENT: #$!"
rescue => details rescue => details
STDERR.puts "ERROR(#{details.class}): #{details}" STDERR.puts "ERROR: #$!"
STDERR.puts details.backtrace.join("\n") STDERR.puts details.backtrace.join("\n")
ensure ensure
client.close client.close

View file

@ -70,11 +70,9 @@ module ObjectTracker
# Strings can't be tracked easily and are so numerous that they drown out all else # Strings can't be tracked easily and are so numerous that they drown out all else
# so we just ignore them in the counts. # so we just ignore them in the counts.
ObjectSpace.each_object do |obj| ObjectSpace.each_object do |obj|
if not obj.kind_of? String ospace << obj.object_id
ospace << obj.object_id counts[obj.class] ||= 0
counts[obj.class] ||= 0 counts[obj.class] += 1
counts[obj.class] += 1
end
end end
dead_objects = @active_objects - ospace dead_objects = @active_objects - ospace

View file

@ -2,6 +2,7 @@ require 'test/unit'
require 'http11' require 'http11'
require 'mongrel' require 'mongrel'
require 'benchmark' require 'benchmark'
require 'digest/sha1'
include Mongrel include Mongrel
@ -38,6 +39,72 @@ class HttpParserTest < Test::Unit::TestCase
assert parser.error?, "Parser SHOULD have error" assert parser.error?, "Parser SHOULD have error"
end end
# lame random garbage maker
def rand_data(min, max, readable=true)
count = min + ((rand(max)+1) *10).to_i
res = count.to_s + "/"
if readable
res << Digest::SHA1.hexdigest(rand(count * 1000).to_s) * (count / 40)
else
res << Digest::SHA1.digest(rand(count * 1000).to_s) * (count / 20)
end
return res
end
def test_horrible_queries
parser = HttpParser.new
# first verify that large random get requests fail
100.times do |c|
get = "GET /#{rand_data(1024, 1024+(c*1024))} HTTP/1.1\r\n"
assert_raises Mongrel::HttpParserError do
parser.execute({}, get)
parser.reset
end
end
# then that large header names are caught
100.times do |c|
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
assert_raises Mongrel::HttpParserError do
parser.execute({}, get)
parser.reset
end
end
# then that large mangled field values are caught
100.times do |c|
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
assert_raises Mongrel::HttpParserError do
parser.execute({}, get)
parser.reset
end
end
# then large headers are rejected too
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
get << "X-Test: test\r\n" * (80 * 1024)
assert_raises Mongrel::HttpParserError do
parser.execute({}, get)
parser.reset
end
# finally just that random garbage gets blocked all the time
10.times do |c|
get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
assert_raises Mongrel::HttpParserError do
parser.execute({}, get)
parser.reset
end
end
end
def test_query_parse def test_query_parse
res = HttpRequest.query_parse("zed=1&frank=2") res = HttpRequest.query_parse("zed=1&frank=2")
assert res["zed"], "didn't get the request right" assert res["zed"], "didn't get the request right"
@ -51,7 +118,6 @@ class HttpParserTest < Test::Unit::TestCase
assert_equal 4,res["zed"].length, "wrong number for zed" assert_equal 4,res["zed"].length, "wrong number for zed"
assert_equal "11",res["frank"], "wrong number for frank" assert_equal "11",res["frank"], "wrong number for frank"
end end
end end