From e6ccbab5d42b110ac4f6ce1f72cb1e9ccbe4400a Mon Sep 17 00:00:00 2001 From: Li xin Date: Tue, 16 Jun 2015 19:02:38 +0900 Subject: [PATCH] mod_cgi buffers data without bound so fix it Upstream-Status: Submitted [http://redmine.lighttpd.net/issues/1264] Signed-off-by: Li Xin Update context for 1.4.36. Signed-off-by: Kai Kang --- doc/config/lighttpd.conf | 8 ++ src/mod_cgi.c | 188 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 187 insertions(+), 9 deletions(-) diff --git a/doc/config/lighttpd.conf b/doc/config/lighttpd.conf index 60b0ae1..9c101a7 100644 --- a/doc/config/lighttpd.conf +++ b/doc/config/lighttpd.conf @@ -375,6 +375,14 @@ server.upload-dirs = ( "/var/tmp" ) ## ####################################################################### +####################################################################### +## +## +## maximum bytes in send_raw before backing off [KByte] +## cgi.high-waterlevel = 10240 +## minimum bytes in send_raw to disable backoff [KByte] +## cgi.low-waterlevel = 5120 +####################################################################### ####################################################################### ## diff --git a/src/mod_cgi.c b/src/mod_cgi.c index 01b1877..7c67eb5 100644 --- a/src/mod_cgi.c +++ b/src/mod_cgi.c @@ -38,6 +38,10 @@ #include "version.h" +/* for output logs */ +char msgbuf[2048]; + + enum {EOL_UNSET, EOL_N, EOL_RN}; typedef struct { @@ -53,9 +57,19 @@ typedef struct { size_t size; } buffer_pid_t; +struct handler_ctx; + +typedef struct { + struct handler_ctx **hctx; + size_t used; + size_t size; +} buffer_ctx_t; + typedef struct { array *cgi; unsigned short execute_x_only; + unsigned int high_waterlevel; /* maximum bytes in send_raw before backing off */ + unsigned int low_waterlevel; /* minimum bytes in send_raw to disable backoff */ } plugin_config; typedef struct { @@ -68,9 +82,11 @@ typedef struct { plugin_config **config_storage; plugin_config conf; + + buffer_ctx_t cgi_ctx; } plugin_data; -typedef struct { +typedef struct handler_ctx { pid_t pid; int fd; int fde_ndx; /* index into the fd-event buffer */ @@ -78,11 +94,16 @@ typedef struct { connection *remote_conn; /* dumb pointer */ plugin_data *plugin_data; /* dumb pointer */ + int throttling; /* 1=waiting for send_raw buffer to drain */ + off_t high_waterlevel; /* maximum bytes in send_raw before backing off */ + off_t low_waterlevel; /* minimum bytes in send_raw to disable backoff */ + off_t bytes_in_buffer; + buffer *response; buffer *response_header; } handler_ctx; -static handler_ctx * cgi_handler_ctx_init(void) { +static handler_ctx * cgi_handler_ctx_init(plugin_data *p) { handler_ctx *hctx = calloc(1, sizeof(*hctx)); force_assert(hctx); @@ -90,13 +111,26 @@ static handler_ctx * cgi_handler_ctx_init(void) { hctx->response = buffer_init(); hctx->response_header = buffer_init(); + hctx->throttling = 0; + hctx->high_waterlevel = (off_t)p->conf.high_waterlevel * 1024; + hctx->low_waterlevel = (off_t)p->conf.low_waterlevel * 1024; + if (hctx->low_waterlevel >= hctx->high_waterlevel) { + hctx->low_waterlevel = hctx->high_waterlevel * 3 / 4; /* 75% */ + } + hctx->bytes_in_buffer = 0; + return hctx; } -static void cgi_handler_ctx_free(handler_ctx *hctx) { +static void cgi_handler_ctx_free(server *srv, handler_ctx *hctx) { buffer_free(hctx->response); buffer_free(hctx->response_header); + /* to avoid confusion */ + if (hctx->throttling) { + log_error_write(srv, __FILE__, __LINE__, "s", "unthrottled"); + } + free(hctx); } @@ -154,6 +188,8 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { config_values_t cv[] = { { "cgi.assign", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ { "cgi.execute-x-only", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ + { "cgi.high-waterlevel", NULL, T_CONFIG_INT, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ + { "cgi.low-waterlevel", NULL, T_CONFIG_INT, T_CONFIG_SCOPE_CONNECTION }, /* 3 */ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET} }; @@ -169,9 +205,13 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { s->cgi = array_init(); s->execute_x_only = 0; + s->high_waterlevel = 0; /* 0 == disabled */ + s->low_waterlevel = 0; cv[0].destination = s->cgi; cv[1].destination = &(s->execute_x_only); + cv[2].destination = &(s->high_waterlevel); + cv[3].destination = &(s->low_waterlevel); p->config_storage[i] = s; @@ -184,6 +224,51 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { } +static void cgi_recount_bytes_in_buffer(handler_ctx *hctx) +{ + chunkqueue *cq = hctx->remote_conn->write_queue; + hctx->bytes_in_buffer = chunkqueue_length(cq) - chunkqueue_written(cq); +} + + +static void cgi_throttling_control(server *srv, handler_ctx *hctx) +{ + cgi_recount_bytes_in_buffer(hctx); + +#ifdef DEBUG + sprintf(msgbuf, "throttling=%d, chars=%llu, high=%llu, low=%llu", + hctx->throttling, hctx->bytes_in_buffer, + hctx->high_waterlevel, hctx->low_waterlevel); + log_error_write(srv, __FILE__, __LINE__, "ss", + "(debug) throttling control,", msgbuf); +#endif + + if (hctx->throttling) { + sprintf(msgbuf, "throttling; chars in queue=%llu," + " low-waterlevel=%llu, high-waterlevel=%llu", + hctx->bytes_in_buffer, + hctx->low_waterlevel, hctx->high_waterlevel); + log_error_write(srv, __FILE__, __LINE__, "s", msgbuf); + if (hctx->bytes_in_buffer <= hctx->low_waterlevel) { + fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + hctx->throttling = 0; + log_error_write(srv, __FILE__, __LINE__, "s", "unthrottled"); + } + } else { + if (hctx->high_waterlevel != 0 && + hctx->high_waterlevel <= hctx->bytes_in_buffer) { + fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd); + hctx->throttling = 1; + sprintf(msgbuf, "throttled; chars in queue=%llu," + " low-waterlevel=%llu, high-waterlevel=%llu", + hctx->bytes_in_buffer, + hctx->low_waterlevel, hctx->high_waterlevel); + log_error_write(srv, __FILE__, __LINE__, "s", msgbuf); + } + } +} + + static int cgi_pid_add(server *srv, plugin_data *p, pid_t pid) { int m = -1; size_t i; @@ -230,6 +315,39 @@ static int cgi_pid_del(server *srv, plugin_data *p, pid_t pid) { return 0; } + +static void cgi_ctx_add(plugin_data *p, handler_ctx *hctx) { + buffer_ctx_t *r = &(p->cgi_ctx); + + if (r->size == 0) { + r->size = 16; + r->hctx = malloc(sizeof(*r->hctx) * r->size); + } else if (r->used == r->size) { + r->size += 16; + r->hctx = realloc(r->hctx, sizeof(*r->hctx) * r->size); + } + + r->hctx[r->used++] = hctx; +} + +static void cgi_ctx_del(plugin_data *p, handler_ctx *hctx) { + size_t i; + buffer_ctx_t *r = &(p->cgi_ctx); + + for (i = 0; i < r->used; i++) { + if (r->hctx[i] == hctx) break; + } + + if (i != r->used) { + /* found */ + + if (i != r->used - 1) { + r->hctx[i] = r->hctx[r->used - 1]; + } + r->used--; + } +} + static int cgi_response_parse(server *srv, connection *con, plugin_data *p, buffer *in) { char *ns; const char *s; @@ -380,6 +498,14 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) { buffer_commit(hctx->response, n); +#ifdef DEBUG + sprintf(msgbuf, "n=%d, bytes_out=%llu, bytes_in=%llu", n, + (unsigned long long)con->write_queue->bytes_out, + (unsigned long long)con->write_queue->bytes_in); + log_error_write(srv, __FILE__, __LINE__, "ss", + "(debug) read,", msgbuf); +#endif + /* split header from body */ if (con->file_started == 0) { @@ -503,7 +629,20 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) { } } else { http_chunk_append_buffer(srv, con, hctx->response); +#ifdef DEBUG + sprintf(msgbuf, "n=%d, bytes_out=%llu, bytes_in=%llu, limit=%llu", n, + (unsigned long long)con->write_queue->bytes_out, + (unsigned long long)con->write_queue->bytes_in, + (unsigned long long)hctx->high_waterlevel); + log_error_write(srv, __FILE__, __LINE__, + "ss", "(debug) append,", msgbuf); +#endif joblist_append(srv, con); + + cgi_throttling_control(srv, hctx); + if (hctx->throttling) { + return FDEVENT_HANDLED_NOT_FINISHED; + } } #if 0 @@ -553,8 +692,9 @@ static handler_t cgi_connection_close(server *srv, handler_ctx *hctx) { con->plugin_ctx[p->id] = NULL; /* is this a good idea ? */ - cgi_handler_ctx_free(hctx); - + cgi_ctx_del(p, hctx); + cgi_handler_ctx_free(srv, hctx); + /* if waitpid hasn't been called by response.c yet, do it here */ if (pid) { /* check if the CGI-script is already gone */ @@ -1105,7 +1245,8 @@ static int cgi_create_env(server *srv, connection *con, plugin_data *p, buffer * con->mode = p->id; buffer_reset(con->physical.path); - hctx = cgi_handler_ctx_init(); + hctx = cgi_handler_ctx_init(p); + cgi_ctx_add(p, hctx); hctx->remote_conn = con; hctx->plugin_data = p; @@ -1114,6 +1255,11 @@ static int cgi_create_env(server *srv, connection *con, plugin_data *p, buffer * hctx->fde_ndx = -1; con->plugin_ctx[p->id] = hctx; +#ifdef DEBUG + sprintf(msgbuf, "hctx=%p, con=%p", (void*)hctx, (void*)con); + log_error_write(srv, __FILE__, __LINE__, "ss", + "(debug) hctx generated, ", msgbuf); +#endif fdevent_register(srv->ev, hctx->fd, cgi_handle_fdevent, hctx); fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); @@ -1128,7 +1274,8 @@ static int cgi_create_env(server *srv, connection *con, plugin_data *p, buffer * close(hctx->fd); - cgi_handler_ctx_free(hctx); + cgi_ctx_del(p, hctx); + cgi_handler_ctx_free(srv, hctx); con->plugin_ctx[p->id] = NULL; @@ -1153,6 +1300,8 @@ static int mod_cgi_patch_connection(server *srv, connection *con, plugin_data *p PATCH(cgi); PATCH(execute_x_only); + PATCH(high_waterlevel); + PATCH(low_waterlevel); /* skip the first, the global context */ for (i = 1; i < srv->config_context->used; i++) { @@ -1170,6 +1319,10 @@ static int mod_cgi_patch_connection(server *srv, connection *con, plugin_data *p PATCH(cgi); } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cgi.execute-x-only"))) { PATCH(execute_x_only); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cgi.high-waterlevel"))) { + PATCH(high_waterlevel); + } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cgi.low-waterlevel"))) { + PATCH(low_waterlevel); } } } @@ -1222,6 +1375,21 @@ URIHANDLER_FUNC(cgi_is_handled) { TRIGGER_FUNC(cgi_trigger) { plugin_data *p = p_d; size_t ndx; + + for (ndx = 0; ndx < p->cgi_ctx.used; ndx++) { + handler_ctx *hctx = p->cgi_ctx.hctx[ndx]; +#ifdef DEBUG + connection *con = hctx->remote_conn; + + sprintf(msgbuf, "hctx=%p, con=%p, bytes_in_buffer=%llu", + (void*)hctx, (void*)con, + (unsigned long long)hctx->bytes_in_buffer); + log_error_write(srv, __FILE__, __LINE__, "ss", + "(debug) found using ctx,", msgbuf); +#endif + cgi_throttling_control(srv, hctx); + } + /* the trigger handle only cares about lonely PID which we have to wait for */ #ifndef __WIN32 @@ -1330,7 +1498,8 @@ SUBREQUEST_FUNC(mod_cgi_handle_subrequest) { log_error_write(srv, __FILE__, __LINE__, "sds", "cgi close failed ", hctx->fd, strerror(errno)); } - cgi_handler_ctx_free(hctx); + cgi_ctx_del(p, hctx); + cgi_handler_ctx_free(srv, hctx); con->plugin_ctx[p->id] = NULL; @@ -1362,7 +1531,8 @@ SUBREQUEST_FUNC(mod_cgi_handle_subrequest) { log_error_write(srv, __FILE__, __LINE__, "sds", "cgi close failed ", hctx->fd, strerror(errno)); } - cgi_handler_ctx_free(hctx); + cgi_ctx_del(p, hctx); + cgi_handler_ctx_free(srv, hctx); con->plugin_ctx[p->id] = NULL; return HANDLER_FINISHED;