about summary refs log tree commit homepage
diff options
context:
space:
mode:
-rw-r--r--alloc.c54
-rw-r--r--cmogstored.h2
-rw-r--r--http.c27
-rw-r--r--http_put.c2
-rw-r--r--mgmt.c12
-rw-r--r--test/http.rb29
6 files changed, 106 insertions, 20 deletions
diff --git a/alloc.c b/alloc.c
index f4fdc08..a7d820e 100644
--- a/alloc.c
+++ b/alloc.c
@@ -108,9 +108,20 @@ void *mog_cachealign(size_t size)
 /* allocates a new mog_rbuf of +size+ bytes */
 struct mog_rbuf *mog_rbuf_new(size_t size)
 {
-        struct mog_rbuf *rbuf = mog_cachealign(size + sizeof(struct mog_rbuf));
+        struct mog_rbuf *rbuf;
+        size_t bytes = size + sizeof(struct mog_rbuf);
 
         assert(size > 0 && "tried to allocate a zero-byte mog_rbuf");
+
+        /*
+         * only cache-align for common allocation sizes, for larger
+         * allocations it'll lead to fragmentation and we'll only
+         * end up touching later sections of memory...
+         */
+        if (size == MOG_RBUF_BASE_SIZE)
+                rbuf = mog_cachealign(bytes);
+        else
+                rbuf = xmalloc(bytes);
         rbuf->rcapa = size;
 
         return rbuf;
@@ -150,6 +161,47 @@ struct mog_rbuf *mog_rbuf_defer(struct mog_rbuf *rbuf)
         return rbuf;
 }
 
+/*
+ * Behaves similarly to realloc(), but is safe for posix_memalign()
+ * Returns a detached rbuf with the contents of +cur+
+ * (which may be cur itself)
+ * Releases memory and returns NULL if rbuf is too big.
+ */
+struct mog_rbuf *mog_rbuf_grow(struct mog_rbuf *cur)
+{
+        struct mog_rbuf *ret;
+        size_t new_size = cur->rsize + 500; /* grow by 500 bytes or so */
+
+        if (cur->rsize == MOG_RBUF_MAX_SIZE) {
+                assert(cur != pthread_getspecific(mog_rbuf_key) &&
+                       "TLS rbuf is HUGE");
+                return NULL;
+        }
+        assert(cur->rsize < MOG_RBUF_MAX_SIZE && "rbuf rsize got too big");
+
+        if (new_size > MOG_RBUF_MAX_SIZE)
+                new_size = MOG_RBUF_MAX_SIZE;
+        if (cur->rcapa < new_size) {
+                if (cur->rcapa == MOG_RBUF_BASE_SIZE) {
+                        /* can't safely realloc posix_memalign'ed memory */
+                        ret = mog_rbuf_new(new_size);
+                        memcpy(ret->rptr, cur->rptr, cur->rsize);
+                        if (cur != pthread_getspecific(mog_rbuf_key))
+                                mog_rbuf_free(cur);
+                } else {
+                        assert(cur != pthread_getspecific(mog_rbuf_key) &&
+                               "bug rbuf found in TLS");
+                        ret = xrealloc(cur, new_size + sizeof(struct mog_rbuf));
+                        ret->rcapa = new_size;
+                }
+        } else {
+                /* this may not even happen, just in case: */
+                ret = mog_rbuf_defer(cur);
+        }
+
+        return ret;
+}
+
 void mog_rbuf_free(struct mog_rbuf *rbuf)
 {
         assert(((rbuf == NULL) ||
diff --git a/cmogstored.h b/cmogstored.h
index 0755ab9..0183a0e 100644
--- a/cmogstored.h
+++ b/cmogstored.h
@@ -101,6 +101,7 @@ struct mog_rbuf {
 #define MOG_MEMALIGN_OVERHEAD (sizeof(size_t) * 2)
 #define MOG_RBUF_OVERHEAD (sizeof(struct mog_rbuf) + MOG_MEMALIGN_OVERHEAD)
 #define MOG_RBUF_BASE_SIZE (512 - MOG_RBUF_OVERHEAD)
+#define MOG_RBUF_MAX_SIZE (UINT16_MAX)
 
 struct mog_mgmt;
 struct mog_mgmt {
@@ -270,6 +271,7 @@ void *mog_cachealign(size_t size);
 struct mog_rbuf *mog_rbuf_new(size_t size);
 struct mog_rbuf *mog_rbuf_get(size_t size);
 struct mog_rbuf *mog_rbuf_defer(struct mog_rbuf *rbuf);
+struct mog_rbuf *mog_rbuf_grow(struct mog_rbuf *);
 void mog_rbuf_free(struct mog_rbuf *);
 void mog_rbuf_free_and_null(struct mog_rbuf **);
 void *mog_fsbuf_get(size_t *size);
diff --git a/http.c b/http.c
index 0f54da0..38cbe53 100644
--- a/http.c
+++ b/http.c
@@ -19,7 +19,7 @@ http_defer_rbuf(struct mog_http *http, struct mog_rbuf *rbuf, size_t buf_len)
         }
 
         assert(http->offset >= 0 && "http->offset negative");
-        assert(defer_bytes <= MOG_RBUF_BASE_SIZE && "defer bytes overflow");
+        assert(defer_bytes <= MOG_RBUF_MAX_SIZE && "defer bytes overflow");
 
         if (defer_bytes == 0) {
                 mog_rbuf_free_and_null(&http->rbuf);
@@ -28,7 +28,7 @@ http_defer_rbuf(struct mog_http *http, struct mog_rbuf *rbuf, size_t buf_len)
                 memmove(old->rptr, src, defer_bytes);
                 old->rsize = defer_bytes;
         } else {
-                http->rbuf = mog_rbuf_new(MOG_RBUF_BASE_SIZE);
+                http->rbuf = mog_rbuf_new(defer_bytes);
                 memcpy(http->rbuf->rptr, src, defer_bytes);
                 http->rbuf->rsize = defer_bytes;
         }
@@ -94,7 +94,6 @@ static enum mog_next http_forward_in_progress(struct mog_fd *mfd)
 
 static enum mog_next http_queue_step(struct mog_fd *mfd)
 {
-        static const size_t capa = MOG_RBUF_BASE_SIZE;
         struct mog_http *http = &mfd->as.http;
         struct mog_rbuf *rbuf;
         char *buf;
@@ -109,11 +108,11 @@ static enum mog_next http_queue_step(struct mog_fd *mfd)
         if (http->forward) return http_forward_in_progress(mfd);
 
         /* we may have pipelined data in http->rbuf */
-        rbuf = http->rbuf ? http->rbuf : mog_rbuf_get(capa);
+        rbuf = http->rbuf ? http->rbuf : mog_rbuf_get(MOG_RBUF_BASE_SIZE);
         buf = rbuf->rptr;
         off = http->offset;
         assert(off >= 0 && "offset is negative");
-        assert(off < capa && "offset is too big");
+        assert(off < rbuf->rcapa && "offset is too big");
         if (http->rbuf) {
                 /* request got pipelined, resuming now */
                 buf_len = http->rbuf->rsize;
@@ -123,7 +122,7 @@ static enum mog_next http_queue_step(struct mog_fd *mfd)
                         goto parse;
         }
 reread:
-        r = read(mfd->fd, buf + off, capa - off);
+        r = read(mfd->fd, buf + off, rbuf->rcapa - off);
         if (r > 0) {
                 buf_len = r + off;
 parse:
@@ -131,15 +130,16 @@ parse:
 
                 switch (state) {
                 case MOG_PARSER_ERROR:
-                        mog_http_resp(http, "400 Bad Request", false);
-                        return MOG_NEXT_CLOSE;
+                        goto err400;
                 case MOG_PARSER_CONTINUE:
                         assert(http->wbuf == NULL &&
                                "tried to write (and failed) with partial req");
-                        if (http->offset == capa) {
-                                assert(buf_len == capa && "bad rbuf");
-                                syslog(LOG_ERR, "http request too large");
-                                return MOG_NEXT_CLOSE;
+                        if (http->offset >= MOG_RBUF_BASE_SIZE) {
+                                rbuf->rsize = buf_len;
+                                http->rbuf = rbuf = mog_rbuf_grow(rbuf);
+                                if (!rbuf)
+                                        goto err400;
+                                buf = rbuf->rptr;
                         }
                         off = http->offset;
                         goto reread;
@@ -182,6 +182,9 @@ parse:
         }
 
         assert(0 && "compiler bug?");
+
+err400:
+        mog_http_resp(http, "400 Bad Request", false);
         return MOG_NEXT_CLOSE;
 }
 
diff --git a/http_put.c b/http_put.c
index 093ce1d..cdc2297 100644
--- a/http_put.c
+++ b/http_put.c
@@ -136,7 +136,7 @@ static void stash_advance_rbuf(struct mog_http *http, char *buf, size_t buf_len)
         }
 
         assert(buf[http->line_end] == '\n' && "line_end is not LF");
-        assert(buf_len <= MOG_RBUF_BASE_SIZE && "bad rbuf size");
+        assert(buf_len <= MOG_RBUF_MAX_SIZE && "bad rbuf size");
         assert(end <= http->offset && "invalid line end");
         if (rbuf == NULL)
                 http->rbuf = rbuf = mog_rbuf_new(MOG_RBUF_BASE_SIZE);
diff --git a/mgmt.c b/mgmt.c
index 46b84ac..9093d49 100644
--- a/mgmt.c
+++ b/mgmt.c
@@ -112,7 +112,6 @@ mgmt_defer_rbuf(struct mog_mgmt *mgmt, struct mog_rbuf *rbuf, size_t buf_len)
  */
 static enum mog_next mgmt_queue_step(struct mog_fd *mfd)
 {
-        static const size_t capa = MOG_RBUF_BASE_SIZE;
         struct mog_mgmt *mgmt = &mfd->as.mgmt;
         struct mog_rbuf *rbuf;
         char *buf;
@@ -127,18 +126,18 @@ static enum mog_next mgmt_queue_step(struct mog_fd *mfd)
         if (mgmt->forward) return mgmt_md5_in_progress(mgmt);
 
         /* we may have pipelined data in mgmt->rbuf */
-        rbuf = mgmt->rbuf ? mgmt->rbuf : mog_rbuf_get(capa);
+        rbuf = mgmt->rbuf ? mgmt->rbuf : mog_rbuf_get(MOG_RBUF_BASE_SIZE);
         buf = rbuf->rptr;
         off = mgmt->offset;
         assert(off >= 0 && "offset is negative");
-        assert(off < capa && "offset is too big");
+        assert(off < MOG_RBUF_BASE_SIZE && "offset is too big");
         if (mgmt->rbuf && off == 0) {
                 /* request got "pipelined", resuming now */
                 buf_len = mgmt->rbuf->rsize;
                 goto parse;
         }
 reread:
-        r = read(mfd->fd, buf + off, capa - off);
+        r = read(mfd->fd, buf + off, MOG_RBUF_BASE_SIZE - off);
         if (r > 0) {
                 buf_len = r + off;
 parse:
@@ -152,8 +151,9 @@ parse:
                 case MOG_PARSER_CONTINUE:
                         assert(mgmt->wbuf == NULL &&
                                "tried to write (and failed) with partial req");
-                        if (mgmt->offset == capa) {
-                                assert(buf_len == capa && "bad rbuf");
+                        if (mgmt->offset == MOG_RBUF_BASE_SIZE) {
+                                assert(buf_len == MOG_RBUF_BASE_SIZE &&
+                                       "bad rbuf");
                                 syslog(LOG_ERR, "mgmt request too large");
                                 return MOG_NEXT_CLOSE;
                         }
diff --git a/test/http.rb b/test/http.rb
index ad21af4..c4d56a6 100644
--- a/test/http.rb
+++ b/test/http.rb
@@ -176,4 +176,33 @@ class TestHTTP < Test::Unit::TestCase
     assert_match(%r{\AHTTP/1\.1 400 Bad Request\r\n}, buf)
     assert_nil(@client.read(666))
   end
+
+  def test_monster_headers
+    buf = "GET /hello-world HTTP/1.1\r\n"
+    4094.times { buf << "X-Hello: World\r\n" }
+    buf << "\r\n"
+    assert_operator(buf.bytesize, :<, 0xffff)
+    buf.each_line do |line|
+      @client.write(line)
+      sleep 0.000666
+    end
+    buf = @client.readpartial(666)
+    assert_match %r{\AHTTP/1\.1 404 Not Found\r\n}, buf
+  end
+
+  def test_large_request_rejected
+    buf = "GET /hello-world HTTP/1.1\r\n"
+    4095.times { buf << "X-Hello: World\r\n" }
+    buf << "\r\n"
+    assert_operator(buf.bytesize, :>=, 0xffff)
+    begin
+      buf.each_line do |line|
+        @client.write(line)
+        sleep 0.000666
+      end
+    rescue Errno::ECONNRESET
+    end
+    buf = @client.readpartial(666)
+    assert_match %r{\AHTTP/1\.1 400 Bad Request\r\n}, buf
+  end
 end