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