summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLi xin <lixin.fnst@cn.fujitsu.com>2015-07-13 16:06:26 +0800
committerRichard Purdie <richard.purdie@linuxfoundation.org>2015-07-16 15:09:22 +0100
commitaad604f231cb020ba0199c83c12b6f107ce3403d (patch)
treee067a59e3731d81955e6f4932eae74f70e7e6cfd
parent88d7df46d6aa3f31c8a3613031eab1cf192863a8 (diff)
downloadpoky-aad604f231cb020ba0199c83c12b6f107ce3403d.tar.gz
lighttpd: Fix mod_cgi to avoid it buffers data without bound.
If there is a CGI that provides a continuous stream of data, If lighttpd client reads slower then the CGI is pushing the data, then lighttpd's buffers will grow until the (embedded) machine OOMs. Ref: http://redmine.lighttpd.net/issues/1264 (From OE-Core rev: 6d098587415be098913a3b551b0b7ee8c0270274) Signed-off-by: Li Xin <lixin.fnst@cn.fujitsu.com> Signed-off-by: Ross Burton <ross.burton@intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r--meta/recipes-extended/lighttpd/lighttpd/0001-mod_cgi-buffers-data-without-bound.patch386
-rw-r--r--meta/recipes-extended/lighttpd/lighttpd_1.4.35.bb1
2 files changed, 387 insertions, 0 deletions
diff --git a/meta/recipes-extended/lighttpd/lighttpd/0001-mod_cgi-buffers-data-without-bound.patch b/meta/recipes-extended/lighttpd/lighttpd/0001-mod_cgi-buffers-data-without-bound.patch
new file mode 100644
index 0000000000..b1678e6ee2
--- /dev/null
+++ b/meta/recipes-extended/lighttpd/lighttpd/0001-mod_cgi-buffers-data-without-bound.patch
@@ -0,0 +1,386 @@
1From e6ccbab5d42b110ac4f6ce1f72cb1e9ccbe4400a Mon Sep 17 00:00:00 2001
2From: Li xin <lixin.fnst@cn.fujitsu.com>
3Date: Tue, 16 Jun 2015 19:02:38 +0900
4Subject: [PATCH] mod_cgi buffers data without bound so fix it
5
6Upstream-Status: Submitted [http://redmine.lighttpd.net/issues/1264]
7
8Signed-off-by: Li Xin <lixin.fnst@cn.fujitsu.com>
9---
10 doc/config/lighttpd.conf | 8 ++
11 src/mod_cgi.c | 188 ++++++++++++++++++++++++++++++++++++++++++++---
12 2 files changed, 186 insertions(+), 10 deletions(-)
13
14diff --git a/doc/config/lighttpd.conf b/doc/config/lighttpd.conf
15index 60b0ae1..9c101a7 100644
16--- a/doc/config/lighttpd.conf
17+++ b/doc/config/lighttpd.conf
18@@ -375,6 +375,14 @@ server.upload-dirs = ( "/var/tmp" )
19 ##
20 #######################################################################
21
22+#######################################################################
23+##
24+##
25+## maximum bytes in send_raw before backing off [KByte]
26+## cgi.high-waterlevel = 10240
27+## minimum bytes in send_raw to disable backoff [KByte]
28+## cgi.low-waterlevel = 5120
29+#######################################################################
30
31 #######################################################################
32 ##
33diff --git a/src/mod_cgi.c b/src/mod_cgi.c
34index 734ecee..c51f43c 100644
35--- a/src/mod_cgi.c
36+++ b/src/mod_cgi.c
37@@ -38,6 +38,10 @@
38
39 #include "version.h"
40
41+/* for output logs */
42+char msgbuf[2048];
43+
44+
45 enum {EOL_UNSET, EOL_N, EOL_RN};
46
47 typedef struct {
48@@ -53,9 +57,19 @@ typedef struct {
49 size_t size;
50 } buffer_pid_t;
51
52+struct handler_ctx;
53+
54+typedef struct {
55+ struct handler_ctx **hctx;
56+ size_t used;
57+ size_t size;
58+} buffer_ctx_t;
59+
60 typedef struct {
61 array *cgi;
62 unsigned short execute_x_only;
63+ unsigned int high_waterlevel; /* maximum bytes in send_raw before backing off */
64+ unsigned int low_waterlevel; /* minimum bytes in send_raw to disable backoff */
65 } plugin_config;
66
67 typedef struct {
68@@ -68,9 +82,11 @@ typedef struct {
69 plugin_config **config_storage;
70
71 plugin_config conf;
72+
73+ buffer_ctx_t cgi_ctx;
74 } plugin_data;
75
76-typedef struct {
77+typedef struct handler_ctx {
78 pid_t pid;
79 int fd;
80 int fde_ndx; /* index into the fd-event buffer */
81@@ -78,11 +94,16 @@ typedef struct {
82 connection *remote_conn; /* dumb pointer */
83 plugin_data *plugin_data; /* dumb pointer */
84
85+ int throttling; /* 1=waiting for send_raw buffer to drain */
86+ off_t high_waterlevel; /* maximum bytes in send_raw before backing off */
87+ off_t low_waterlevel; /* minimum bytes in send_raw to disable backoff */
88+ off_t bytes_in_buffer;
89+
90 buffer *response;
91 buffer *response_header;
92 } handler_ctx;
93
94-static handler_ctx * cgi_handler_ctx_init(void) {
95+static handler_ctx * cgi_handler_ctx_init(plugin_data *p) {
96 handler_ctx *hctx = calloc(1, sizeof(*hctx));
97
98 force_assert(hctx);
99@@ -90,13 +111,26 @@ static handler_ctx * cgi_handler_ctx_init(void) {
100 hctx->response = buffer_init();
101 hctx->response_header = buffer_init();
102
103+ hctx->throttling = 0;
104+ hctx->high_waterlevel = (off_t)p->conf.high_waterlevel * 1024;
105+ hctx->low_waterlevel = (off_t)p->conf.low_waterlevel * 1024;
106+ if (hctx->low_waterlevel >= hctx->high_waterlevel) {
107+ hctx->low_waterlevel = hctx->high_waterlevel * 3 / 4; /* 75% */
108+ }
109+ hctx->bytes_in_buffer = 0;
110+
111 return hctx;
112 }
113
114-static void cgi_handler_ctx_free(handler_ctx *hctx) {
115+static void cgi_handler_ctx_free(server *srv, handler_ctx *hctx) {
116 buffer_free(hctx->response);
117 buffer_free(hctx->response_header);
118
119+ /* to avoid confusion */
120+ if (hctx->throttling) {
121+ log_error_write(srv, __FILE__, __LINE__, "s", "unthrottled");
122+ }
123+
124 free(hctx);
125 }
126
127@@ -152,6 +186,8 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) {
128 config_values_t cv[] = {
129 { "cgi.assign", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
130 { "cgi.execute-x-only", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
131+ { "cgi.high-waterlevel", NULL, T_CONFIG_INT, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
132+ { "cgi.low-waterlevel", NULL, T_CONFIG_INT, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
133 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET}
134 };
135
136@@ -167,9 +203,13 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) {
137
138 s->cgi = array_init();
139 s->execute_x_only = 0;
140+ s->high_waterlevel = 0; /* 0 == disabled */
141+ s->low_waterlevel = 0;
142
143 cv[0].destination = s->cgi;
144 cv[1].destination = &(s->execute_x_only);
145+ cv[2].destination = &(s->high_waterlevel);
146+ cv[3].destination = &(s->low_waterlevel);
147
148 p->config_storage[i] = s;
149
150@@ -182,6 +222,51 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) {
151 }
152
153
154+static void cgi_recount_bytes_in_buffer(handler_ctx *hctx)
155+{
156+ chunkqueue *cq = hctx->remote_conn->write_queue;
157+ hctx->bytes_in_buffer = chunkqueue_length(cq) - chunkqueue_written(cq);
158+}
159+
160+
161+static void cgi_throttling_control(server *srv, handler_ctx *hctx)
162+{
163+ cgi_recount_bytes_in_buffer(hctx);
164+
165+#ifdef DEBUG
166+ sprintf(msgbuf, "throttling=%d, chars=%llu, high=%llu, low=%llu",
167+ hctx->throttling, hctx->bytes_in_buffer,
168+ hctx->high_waterlevel, hctx->low_waterlevel);
169+ log_error_write(srv, __FILE__, __LINE__, "ss",
170+ "(debug) throttling control,", msgbuf);
171+#endif
172+
173+ if (hctx->throttling) {
174+ sprintf(msgbuf, "throttling; chars in queue=%llu,"
175+ " low-waterlevel=%llu, high-waterlevel=%llu",
176+ hctx->bytes_in_buffer,
177+ hctx->low_waterlevel, hctx->high_waterlevel);
178+ log_error_write(srv, __FILE__, __LINE__, "s", msgbuf);
179+ if (hctx->bytes_in_buffer <= hctx->low_waterlevel) {
180+ fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
181+ hctx->throttling = 0;
182+ log_error_write(srv, __FILE__, __LINE__, "s", "unthrottled");
183+ }
184+ } else {
185+ if (hctx->high_waterlevel != 0 &&
186+ hctx->high_waterlevel <= hctx->bytes_in_buffer) {
187+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
188+ hctx->throttling = 1;
189+ sprintf(msgbuf, "throttled; chars in queue=%llu,"
190+ " low-waterlevel=%llu, high-waterlevel=%llu",
191+ hctx->bytes_in_buffer,
192+ hctx->low_waterlevel, hctx->high_waterlevel);
193+ log_error_write(srv, __FILE__, __LINE__, "s", msgbuf);
194+ }
195+ }
196+}
197+
198+
199 static int cgi_pid_add(server *srv, plugin_data *p, pid_t pid) {
200 int m = -1;
201 size_t i;
202@@ -228,6 +313,39 @@ static int cgi_pid_del(server *srv, plugin_data *p, pid_t pid) {
203 return 0;
204 }
205
206+
207+static void cgi_ctx_add(plugin_data *p, handler_ctx *hctx) {
208+ buffer_ctx_t *r = &(p->cgi_ctx);
209+
210+ if (r->size == 0) {
211+ r->size = 16;
212+ r->hctx = malloc(sizeof(*r->hctx) * r->size);
213+ } else if (r->used == r->size) {
214+ r->size += 16;
215+ r->hctx = realloc(r->hctx, sizeof(*r->hctx) * r->size);
216+ }
217+
218+ r->hctx[r->used++] = hctx;
219+}
220+
221+static void cgi_ctx_del(plugin_data *p, handler_ctx *hctx) {
222+ size_t i;
223+ buffer_ctx_t *r = &(p->cgi_ctx);
224+
225+ for (i = 0; i < r->used; i++) {
226+ if (r->hctx[i] == hctx) break;
227+ }
228+
229+ if (i != r->used) {
230+ /* found */
231+
232+ if (i != r->used - 1) {
233+ r->hctx[i] = r->hctx[r->used - 1];
234+ }
235+ r->used--;
236+ }
237+}
238+
239 static int cgi_response_parse(server *srv, connection *con, plugin_data *p, buffer *in) {
240 char *ns;
241 const char *s;
242@@ -378,6 +496,13 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) {
243
244 hctx->response->ptr[n] = '\0';
245 hctx->response->used = n+1;
246+#ifdef DEBUG
247+ sprintf(msgbuf, "n=%d, bytes_out=%llu, bytes_in=%llu", n,
248+ (unsigned long long)con->write_queue->bytes_out,
249+ (unsigned long long)con->write_queue->bytes_in);
250+ log_error_write(srv, __FILE__, __LINE__, "ss",
251+ "(debug) read,", msgbuf);
252+#endif
253
254 /* split header from body */
255
256@@ -502,8 +627,20 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) {
257 }
258 } else {
259 http_chunk_append_mem(srv, con, hctx->response->ptr, hctx->response->used);
260+#ifdef DEBUG
261+ sprintf(msgbuf, "n=%d, bytes_out=%llu, bytes_in=%llu, limit=%llu", n,
262+ (unsigned long long)con->write_queue->bytes_out,
263+ (unsigned long long)con->write_queue->bytes_in,
264+ (unsigned long long)hctx->high_waterlevel);
265+ log_error_write(srv, __FILE__, __LINE__,
266+ "ss", "(debug) append,", msgbuf);
267+#endif
268 joblist_append(srv, con);
269- }
270+ cgi_throttling_control(srv, hctx);
271+ if (hctx->throttling) {
272+ return FDEVENT_HANDLED_NOT_FINISHED;
273+ }
274+ }
275
276 #if 0
277 log_error_write(srv, __FILE__, __LINE__, "ddss", con->fd, hctx->fd, connection_get_state(con->state), b->ptr);
278@@ -552,8 +689,9 @@ static handler_t cgi_connection_close(server *srv, handler_ctx *hctx) {
279 con->plugin_ctx[p->id] = NULL;
280
281 /* is this a good idea ? */
282- cgi_handler_ctx_free(hctx);
283-
284+ cgi_ctx_del(p, hctx);
285+ cgi_handler_ctx_free(srv, hctx);
286+
287 /* if waitpid hasn't been called by response.c yet, do it here */
288 if (pid) {
289 /* check if the CGI-script is already gone */
290@@ -1156,7 +1294,8 @@ static int cgi_create_env(server *srv, connection *con, plugin_data *p, buffer *
291 con->mode = p->id;
292 buffer_reset(con->physical.path);
293
294- hctx = cgi_handler_ctx_init();
295+ hctx = cgi_handler_ctx_init(p);
296+ cgi_ctx_add(p, hctx);
297
298 hctx->remote_conn = con;
299 hctx->plugin_data = p;
300@@ -1165,6 +1304,11 @@ static int cgi_create_env(server *srv, connection *con, plugin_data *p, buffer *
301 hctx->fde_ndx = -1;
302
303 con->plugin_ctx[p->id] = hctx;
304+#ifdef DEBUG
305+ sprintf(msgbuf, "hctx=%p, con=%p", (void*)hctx, (void*)con);
306+ log_error_write(srv, __FILE__, __LINE__, "ss",
307+ "(debug) hctx generated, ", msgbuf);
308+#endif
309
310 fdevent_register(srv->ev, hctx->fd, cgi_handle_fdevent, hctx);
311 fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
312@@ -1179,7 +1323,8 @@ static int cgi_create_env(server *srv, connection *con, plugin_data *p, buffer *
313
314 close(hctx->fd);
315
316- cgi_handler_ctx_free(hctx);
317+ cgi_ctx_del(p, hctx);
318+ cgi_handler_ctx_free(srv, hctx);
319
320 con->plugin_ctx[p->id] = NULL;
321
322@@ -1204,6 +1349,8 @@ static int mod_cgi_patch_connection(server *srv, connection *con, plugin_data *p
323
324 PATCH(cgi);
325 PATCH(execute_x_only);
326+ PATCH(high_waterlevel);
327+ PATCH(low_waterlevel);
328
329 /* skip the first, the global context */
330 for (i = 1; i < srv->config_context->used; i++) {
331@@ -1221,6 +1368,10 @@ static int mod_cgi_patch_connection(server *srv, connection *con, plugin_data *p
332 PATCH(cgi);
333 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cgi.execute-x-only"))) {
334 PATCH(execute_x_only);
335+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cgi.high-waterlevel"))) {
336+ PATCH(high_waterlevel);
337+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cgi.low-waterlevel"))) {
338+ PATCH(low_waterlevel);
339 }
340 }
341 }
342@@ -1273,6 +1424,21 @@ URIHANDLER_FUNC(cgi_is_handled) {
343 TRIGGER_FUNC(cgi_trigger) {
344 plugin_data *p = p_d;
345 size_t ndx;
346+
347+ for (ndx = 0; ndx < p->cgi_ctx.used; ndx++) {
348+ handler_ctx *hctx = p->cgi_ctx.hctx[ndx];
349+#ifdef DEBUG
350+ connection *con = hctx->remote_conn;
351+
352+ sprintf(msgbuf, "hctx=%p, con=%p, bytes_in_buffer=%llu",
353+ (void*)hctx, (void*)con,
354+ (unsigned long long)hctx->bytes_in_buffer);
355+ log_error_write(srv, __FILE__, __LINE__, "ss",
356+ "(debug) found using ctx,", msgbuf);
357+#endif
358+ cgi_throttling_control(srv, hctx);
359+ }
360+
361 /* the trigger handle only cares about lonely PID which we have to wait for */
362 #ifndef __WIN32
363
364@@ -1381,7 +1547,8 @@ SUBREQUEST_FUNC(mod_cgi_handle_subrequest) {
365 log_error_write(srv, __FILE__, __LINE__, "sds", "cgi close failed ", hctx->fd, strerror(errno));
366 }
367
368- cgi_handler_ctx_free(hctx);
369+ cgi_ctx_del(p, hctx);
370+ cgi_handler_ctx_free(srv, hctx);
371
372 con->plugin_ctx[p->id] = NULL;
373
374@@ -1413,7 +1580,8 @@ SUBREQUEST_FUNC(mod_cgi_handle_subrequest) {
375 log_error_write(srv, __FILE__, __LINE__, "sds", "cgi close failed ", hctx->fd, strerror(errno));
376 }
377
378- cgi_handler_ctx_free(hctx);
379+ cgi_ctx_del(p, hctx);
380+ cgi_handler_ctx_free(srv, hctx);
381
382 con->plugin_ctx[p->id] = NULL;
383 return HANDLER_FINISHED;
384--
3851.8.4.2
386
diff --git a/meta/recipes-extended/lighttpd/lighttpd_1.4.35.bb b/meta/recipes-extended/lighttpd/lighttpd_1.4.35.bb
index 0cf5aa2df2..d3888e62e5 100644
--- a/meta/recipes-extended/lighttpd/lighttpd_1.4.35.bb
+++ b/meta/recipes-extended/lighttpd/lighttpd_1.4.35.bb
@@ -24,6 +24,7 @@ SRC_URI = "http://download.lighttpd.net/lighttpd/releases-1.4.x/lighttpd-${PV}.t
24 file://lighttpd \ 24 file://lighttpd \
25 file://lighttpd.service \ 25 file://lighttpd.service \
26 file://pkgconfig.patch \ 26 file://pkgconfig.patch \
27 file://0001-mod_cgi-buffers-data-without-bound.patch \
27 " 28 "
28 29
29SRC_URI[md5sum] = "f7a88130ee9984b421ad8aa80629750a" 30SRC_URI[md5sum] = "f7a88130ee9984b421ad8aa80629750a"