diff options
-rw-r--r-- | alloc.c | 54 | ||||
-rw-r--r-- | cmogstored.h | 2 | ||||
-rw-r--r-- | http.c | 27 | ||||
-rw-r--r-- | http_put.c | 2 | ||||
-rw-r--r-- | mgmt.c | 12 | ||||
-rw-r--r-- | test/http.rb | 29 |
6 files changed, 106 insertions, 20 deletions
@@ -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); @@ -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; } @@ -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); @@ -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 |