388 lines
12 KiB
Diff
388 lines
12 KiB
Diff
From e6ccbab5d42b110ac4f6ce1f72cb1e9ccbe4400a Mon Sep 17 00:00:00 2001
|
|
From: Li xin <lixin.fnst@cn.fujitsu.com>
|
|
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 <lixin.fnst@cn.fujitsu.com>
|
|
|
|
Update context for 1.4.36.
|
|
|
|
Signed-off-by: Kai Kang <kai.kang@windriver.com>
|
|
---
|
|
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;
|