File fix-slow-request-dos-in-1.4.x.patch of Package lighttpd
Subject: [PATCH] Append to previous buffer in con read (fixes #2147, found by liming)
* Remove ssl_error_want_reuse_buffer for SSL_read:
Although the manual states we have to use the same arguments in the
next call after SSL_ERROR_WANT_*, it has been running without this
in 1.5 for a long time now.
* As POST-data chunks get copied to the next queue, we reuse chunks
there as well.
---
src/base.h | 1 -
src/chunk.c | 2 -
src/connections.c | 94 ++++++++++++++++++++++++++++++-----------------------
3 files changed, 53 insertions(+), 44 deletions(-)
Index: src/base.h
===================================================================
--- src/base.h.orig 2008-01-18 10:07:54.000000000 +0100
+++ src/base.h 2010-02-01 14:59:42.776090957 +0100
@@ -414,7 +414,6 @@ typedef struct {
#ifdef USE_OPENSSL
SSL *ssl;
- buffer *ssl_error_want_reuse_buffer;
#endif
/* etag handling */
etag_flags_t etag_flags;
Index: src/chunk.c
===================================================================
--- src/chunk.c.orig 2006-10-04 15:26:23.000000000 +0200
+++ src/chunk.c 2010-02-01 14:58:14.844607476 +0100
@@ -197,8 +197,6 @@ int chunkqueue_append_buffer(chunkqueue
int chunkqueue_append_buffer_weak(chunkqueue *cq, buffer *mem) {
chunk *c;
- if (mem->used == 0) return 0;
-
c = chunkqueue_get_unused_chunk(cq);
c->type = MEM_CHUNK;
c->offset = 0;
Index: src/connections.c
===================================================================
--- src/connections.c.orig 2010-02-01 14:58:12.000000000 +0100
+++ src/connections.c 2010-02-01 15:21:21.277998227 +0100
@@ -192,39 +192,41 @@ static void dump_packet(const unsigned c
static int connection_handle_read_ssl(server *srv, connection *con) {
#ifdef USE_OPENSSL
- int r, ssl_err, len;
+ int r, ssl_err, len, count = 0, read_offset, toread;
buffer *b = NULL;
if (!con->conf.is_ssl) return -1;
- /* don't resize the buffer if we were in SSL_ERROR_WANT_* */
-
ERR_clear_error();
do {
- if (!con->ssl_error_want_reuse_buffer) {
- b = buffer_init();
- buffer_prepare_copy(b, SSL_pending(con->ssl) + (16 * 1024)); /* the pending bytes + 16kb */
+ if (NULL != con->read_queue->last)
+ b = con->read_queue->last->mem;
+
+ if (NULL == b || b->size - b->used < 1024) {
+ b = chunkqueue_get_append_buffer(con->read_queue);
+ len = SSL_pending(con->ssl);
+ if (len < 4*1024) len = 4*1024; /* always alloc >= 4k buffer */
+ buffer_prepare_copy(b, len + 1);
/* overwrite everything with 0 */
memset(b->ptr, 0, b->size);
- } else {
- b = con->ssl_error_want_reuse_buffer;
}
- len = SSL_read(con->ssl, b->ptr, b->size - 1);
- con->ssl_error_want_reuse_buffer = NULL; /* reuse it only once */
+ read_offset = (b->used > 0) ? b->used - 1 : 0;
+ toread = b->size - 1 - read_offset;
+
+ len = SSL_read(con->ssl, b->ptr + read_offset, toread);
+
if (len > 0) {
- b->used = len;
+ if (b->used > 0) b->used--;
+ b->used += len;
b->ptr[b->used++] = '\0';
- /* we move the buffer to the chunk-queue, no need to free it */
-
- chunkqueue_append_buffer_weak(con->read_queue, b);
con->bytes_read += len;
- b = NULL;
+ count += len;
}
- } while (len > 0);
+ } while (len == toread && count < MAX_READ_LIMIT);
if (len < 0) {
@@ -232,11 +234,11 @@ static int connection_handle_read_ssl(se
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
con->is_readable = 0;
- con->ssl_error_want_reuse_buffer = b;
- b = NULL;
+ /* the manual says we have to call SSL_read with the same arguments next time.
+ * we ignore this restriction; no one has complained about it in 1.5 yet, so it probably works anyway.
+ */
- /* we have to steal the buffer from the queue-queue */
return 0;
case SSL_ERROR_SYSCALL:
/**
@@ -285,16 +287,11 @@ static int connection_handle_read_ssl(se
connection_set_state(srv, con, CON_STATE_ERROR);
- buffer_free(b);
-
return -1;
} else if (len == 0) {
con->is_readable = 0;
/* the other end close the connection -> KEEP-ALIVE */
- /* pipelining */
- buffer_free(b);
-
return -2;
}
@@ -307,27 +304,41 @@ static int connection_handle_read_ssl(se
static int connection_handle_read(server *srv, connection *con) {
int len;
buffer *b;
- int toread;
+ int toread, read_offset;
if (con->conf.is_ssl) {
return connection_handle_read_ssl(srv, con);
}
+ b = (NULL != con->read_queue->last) ? con->read_queue->last->mem : NULL;
+
+ /* default size for chunks is 4kb; only use bigger chunks if FIONREAD tells
+ * us more than 4kb is available
+ * if FIONREAD doesn't signal a big chunk we fill the previous buffer
+ * if it has >= 1kb free
+ */
#if defined(__WIN32)
- b = chunkqueue_get_append_buffer(con->read_queue);
- buffer_prepare_copy(b, 4 * 1024);
- len = recv(con->fd, b->ptr, b->size - 1, 0);
+ if (NULL == b || b->size - b->used < 1024) {
+ b = chunkqueue_get_append_buffer(con->read_queue);
+ buffer_prepare_copy(b, 4 * 1024);
+ }
+
+ read_offset = (b->used == 0) ? 0 : b->used - 1;
+ len = recv(con->fd, b->ptr + read_offset, b->size - 1 - read_offset, 0);
#else
- if (ioctl(con->fd, FIONREAD, &toread)) {
- log_error_write(srv, __FILE__, __LINE__, "sd",
- "unexpected end-of-file:",
- con->fd);
- return -1;
+ if (ioctl(con->fd, FIONREAD, &toread) || toread == 0 || toread <= 4*1024) {
+ if (NULL == b || b->size - b->used < 1024) {
+ b = chunkqueue_get_append_buffer(con->read_queue);
+ buffer_prepare_copy(b, 4 * 1024);
+ }
+ } else {
+ if (toread > MAX_READ_LIMIT) toread = MAX_READ_LIMIT;
+ b = chunkqueue_get_append_buffer(con->read_queue);
+ buffer_prepare_copy(b, toread + 1);
}
- b = chunkqueue_get_append_buffer(con->read_queue);
- buffer_prepare_copy(b, toread + 1);
- len = read(con->fd, b->ptr, b->size - 1);
+ read_offset = (b->used == 0) ? 0 : b->used - 1;
+ len = read(con->fd, b->ptr + read_offset, b->size - 1 - read_offset);
#endif
if (len < 0) {
@@ -361,7 +372,8 @@ static int connection_handle_read(server
con->is_readable = 0;
}
- b->used = len;
+ if (b->used > 0) b->used--;
+ b->used += len;
b->ptr[b->used++] = '\0';
con->bytes_read += len;
@@ -849,13 +861,6 @@ int connection_reset(server *srv, connec
/* The cond_cache gets reset in response.c */
// config_cond_cache_reset(srv, con);
-#ifdef USE_OPENSSL
- if (con->ssl_error_want_reuse_buffer) {
- buffer_free(con->ssl_error_want_reuse_buffer);
- con->ssl_error_want_reuse_buffer = NULL;
- }
-#endif
-
con->header_len = 0;
con->in_error_handler = 0;
@@ -1136,8 +1141,15 @@ int connection_handle_read_state(server
} else {
buffer *b;
- b = chunkqueue_get_append_buffer(dst_cq);
- buffer_copy_string_len(b, c->mem->ptr + c->offset, toRead);
+ if (dst_cq->last &&
+ dst_cq->last->type == MEM_CHUNK) {
+ b = dst_cq->last->mem;
+ } else {
+ b = chunkqueue_get_append_buffer(dst_cq);
+ /* prepare buffer size for remaining POST data; is < 64kb */
+ buffer_prepare_copy(b, con->request.content_length - dst_cq->bytes_in + 1);
+ }
+ buffer_append_string_len(b, c->mem->ptr + c->offset, toRead);
}
c->offset += toRead;
Index: src/settings.h
===================================================================
--- src/settings.h.orig 2006-10-04 15:26:23.000000000 +0200
+++ src/settings.h 2010-02-01 15:32:12.229211465 +0100
@@ -13,6 +13,7 @@
* 64kB (no real reason, just a guess)
*/
#define BUFFER_MAX_REUSE_SIZE (4 * 1024)
+#define MAX_READ_LIMIT (4*1024*1024)
/**
* max size of the HTTP request header