summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2010-10-05 22:01:19 +0000
committerEric Wong <normalperson@yhbt.net>2010-10-05 22:08:21 +0000
commit350e8fa3a94838bcc936782315b3472615fe6517 (patch)
tree2e989526cc59d071be5a91fd124a461dbb2ddd4d
parentc2975b85b9378797631d3ab133cac371f9fadf54 (diff)
It's expensive to generate a backtrace and this exception
is only triggered by bad clients.  So make it harder for
them to DoS us by sending bad requests.
-rw-r--r--ext/unicorn_http/global_variables.h4
-rw-r--r--ext/unicorn_http/unicorn_http.rl21
-rw-r--r--test/unit/test_http_parser_ng.rb11
3 files changed, 29 insertions, 7 deletions
diff --git a/ext/unicorn_http/global_variables.h b/ext/unicorn_http/global_variables.h
index 7319bcd..8377704 100644
--- a/ext/unicorn_http/global_variables.h
+++ b/ext/unicorn_http/global_variables.h
@@ -35,13 +35,15 @@ static VALUE g_HEAD;
   static const char * const MAX_##N##_LENGTH_ERR = \
     "HTTP element " # N  " is longer than the " # length " allowed length."
 
+NORETURN(static void parser_error(const char *));
+
 /**
  * Validates the max length of given input and throws an HttpParserError
  * exception if over.
  */
 #define VALIDATE_MAX_LENGTH(len, N) do { \
   if (len > MAX_##N##_LENGTH) \
-    rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR); \
+    parser_error(MAX_##N##_LENGTH_ERR); \
 } while (0)
 
 /** Defines global strings in the init method. */
diff --git a/ext/unicorn_http/unicorn_http.rl b/ext/unicorn_http/unicorn_http.rl
index 1ad2a5d..390bbe8 100644
--- a/ext/unicorn_http/unicorn_http.rl
+++ b/ext/unicorn_http/unicorn_http.rl
@@ -48,6 +48,15 @@ struct http_parser {
 
 static void finalize_header(struct http_parser *hp, VALUE req);
 
+static void parser_error(const char *msg)
+{
+  VALUE exc = rb_exc_new2(eHttpParserError, msg);
+  VALUE bt = rb_ary_new();
+
+  rb_funcall(exc, rb_intern("set_backtrace"), 1, bt);
+  rb_exc_raise(exc);
+}
+
 #define REMAINING (unsigned long)(pe - p)
 #define LEN(AT, FPC) (FPC - buffer - hp->AT)
 #define MARK(M,FPC) (hp->M = (FPC) - buffer)
@@ -132,7 +141,7 @@ http_version(struct http_parser *hp, VALUE req, const char *ptr, size_t len)
 static inline void hp_invalid_if_trailer(struct http_parser *hp)
 {
   if (HP_FL_TEST(hp, INTRAILER))
-    rb_raise(eHttpParserError, "invalid Trailer");
+    parser_error("invalid Trailer");
 }
 
 static void write_cont_value(struct http_parser *hp,
@@ -141,7 +150,7 @@ static void write_cont_value(struct http_parser *hp,
   char *vptr;
 
   if (hp->cont == Qfalse)
-     rb_raise(eHttpParserError, "invalid continuation line");
+     parser_error("invalid continuation line");
   if (NIL_P(hp->cont))
      return; /* we're ignoring this header (probably Host:) */
 
@@ -192,7 +201,7 @@ static void write_value(VALUE req, struct http_parser *hp,
   } else if (f == g_content_length) {
     hp->len.content = parse_length(RSTRING_PTR(v), RSTRING_LEN(v));
     if (hp->len.content < 0)
-      rb_raise(eHttpParserError, "invalid Content-Length");
+      parser_error("invalid Content-Length");
     HP_FL_SET(hp, HASBODY);
     hp_invalid_if_trailer(hp);
   } else if (f == g_http_transfer_encoding) {
@@ -285,7 +294,7 @@ static void write_value(VALUE req, struct http_parser *hp,
   action add_to_chunk_size {
     hp->len.chunk = step_incr(hp->len.chunk, fc, 16);
     if (hp->len.chunk < 0)
-      rb_raise(eHttpParserError, "invalid chunk size");
+      parser_error("invalid chunk size");
   }
   action header_done {
     finalize_header(hp, req);
@@ -550,7 +559,7 @@ static VALUE HttpParser_headers(VALUE self, VALUE req, VALUE data)
   }
 
   if (hp->cs == http_parser_error)
-    rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
+    parser_error("Invalid HTTP format, parsing fails.");
 
   return Qnil;
 }
@@ -643,7 +652,7 @@ static VALUE HttpParser_filter_body(VALUE self, VALUE buf, VALUE data)
       hp->s.dest_offset = 0;
       http_parser_execute(hp, buf, dptr, dlen);
       if (hp->cs == http_parser_error)
-        rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
+        parser_error("Invalid HTTP format, parsing fails.");
 
       assert(hp->s.dest_offset <= hp->offset &&
              "destination buffer overflow");
diff --git a/test/unit/test_http_parser_ng.rb b/test/unit/test_http_parser_ng.rb
index cb30f32..8e8c933 100644
--- a/test/unit/test_http_parser_ng.rb
+++ b/test/unit/test_http_parser_ng.rb
@@ -440,6 +440,17 @@ class HttpParserNgTest < Test::Unit::TestCase
     end
   end
 
+  def test_backtrace_is_empty
+    begin
+      @parser.headers({}, "AAADFSFDSFD\r\n\r\n")
+      assert false, "should never get here line:#{__LINE__}"
+    rescue HttpParserError => e
+      assert_equal [], e.backtrace
+      return
+    end
+    assert false, "should never get here line:#{__LINE__}"
+  end
+
   def test_ignore_version_header
     http = "GET / HTTP/1.1\r\nVersion: hello\r\n\r\n"
     req = {}