Browse Source

fixed crash on mixed \r\n and \n sequences

git-svn-id: svn://svn.lighttpd.net/lighttpd/branches/lighttpd-1.4.x@1925 152afb58-edef-0310-8abb-c4023f1b3aa9
svn/tags/lighttpd-1.4.17
Jan Kneschke 15 years ago
parent
commit
f0333c8c0d
  1. 2
      NEWS
  2. 144
      src/mod_cgi.c
  3. 3
      tests/mod-cgi.t

2
NEWS

@ -9,6 +9,8 @@ NEWS
* fixed different ETag length on 32/64 platforms (#1279)
* fixed compression of files < 128 bytes by disabling compression (#1241)
* fixed mysql server reconnects (#518)
* fixed disabled keep-alive for dynamic content with HTTP/1.0 (#1166)
* fixed crash on mixed EOL sequences in mod_cgi
- 1.4.16 -

144
src/mod_cgi.c

@ -222,7 +222,7 @@ static int cgi_pid_del(server *srv, plugin_data *p, pid_t pid) {
return 0;
}
static int cgi_response_parse(server *srv, connection *con, plugin_data *p, buffer *in, int eol) {
static int cgi_response_parse(server *srv, connection *con, plugin_data *p, buffer *in) {
char *ns;
const char *s;
int line = 0;
@ -232,14 +232,17 @@ static int cgi_response_parse(server *srv, connection *con, plugin_data *p, buff
buffer_copy_string_buffer(p->parse_response, in);
for (s = p->parse_response->ptr;
NULL != (ns = (eol == EOL_RN ? strstr(s, "\r\n") : strchr(s, '\n')));
s = ns + (eol == EOL_RN ? 2 : 1), line++) {
NULL != (ns = strchr(s, '\n'));
s = ns + 1, line++) {
const char *key, *value;
int key_len;
data_string *ds;
/* strip the \n */
ns[0] = '\0';
if (ns > s && ns[-1] == '\r') ns[-1] = '\0';
if (line == 0 &&
0 == strncmp(s, "HTTP/1.", 7)) {
/* non-parsed header ... we parse them anyway */
@ -260,7 +263,7 @@ static int cgi_response_parse(server *srv, connection *con, plugin_data *p, buff
}
}
} else {
/* parse the headers */
key = s;
if (NULL == (value = strchr(s, ':'))) {
/* we expect: "<key>: <value>\r\n" */
@ -362,63 +365,81 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) {
/* split header from body */
if (con->file_started == 0) {
char *c;
int in_header = 0;
int header_end = 0;
int cp, eol = EOL_UNSET;
size_t used = 0;
int is_header = 0;
int is_header_end = 0;
size_t last_eol = 0;
size_t i;
buffer_append_string_buffer(hctx->response_header, hctx->response);
/**
* we have to handle a few cases:
*
* nph:
*
* HTTP/1.0 200 Ok\n
* Header: Value\n
* \n
*
* CGI:
* Header: Value\n
* Status: 200\n
* \n
*
* and different mixes of \n and \r\n combinations
*
* Some users also forget about CGI and just send a response and hope
* we handle it. No headers, no header-content seperator
*
*/
/* nph (non-parsed headers) */
if (0 == strncmp(hctx->response_header->ptr, "HTTP/1.", 7)) in_header = 1;
/* search for the \r\n\r\n or \n\n in the string */
for (c = hctx->response_header->ptr, cp = 0, used = hctx->response_header->used - 1; used; c++, cp++, used--) {
if (*c == ':') in_header = 1;
else if (*c == '\n') {
if (in_header == 0) {
/* got a response without a response header */
c = NULL;
header_end = 1;
break;
}
if (0 == strncmp(hctx->response_header->ptr, "HTTP/1.", 7)) is_header = 1;
for (i = 0; !is_header_end && i < hctx->response_header->used - 1; i++) {
char c = hctx->response_header->ptr[i];
switch (c) {
case ':':
/* we found a colon
*
* looks like we have a normal header
*/
is_header = 1;
break;
case '\n':
/* EOL */
if (is_header == 0) {
/* we got a EOL but we don't seem to got a HTTP header */
if (eol == EOL_UNSET) eol = EOL_N;
is_header_end = 1;
if (*(c+1) == '\n') {
header_end = 1;
break;
}
} else if (used > 1 && *c == '\r' && *(c+1) == '\n') {
if (in_header == 0) {
/* got a response without a response header */
c = NULL;
header_end = 1;
/**
* check if we saw a \n(\r)?\n sequence
*/
if (last_eol > 0 &&
((i - last_eol == 1) ||
(i - last_eol == 2 && hctx->response_header->ptr[i - 1] == '\r'))) {
log_error_write(srv, __FILE__, __LINE__,
"sdd",
"EOL at",
i, last_eol
);
is_header_end = 1;
break;
}
if (eol == EOL_UNSET) eol = EOL_RN;
last_eol = i;
if (used > 3 &&
*(c+2) == '\r' &&
*(c+3) == '\n') {
header_end = 1;
break;
}
/* skip the \n */
c++;
cp++;
used--;
break;
}
}
if (header_end) {
if (c == NULL) {
if (is_header_end) {
if (!is_header) {
/* no header, but a body */
if (con->request.http_version == HTTP_VERSION_1_1) {
@ -428,15 +449,30 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) {
http_chunk_append_mem(srv, con, hctx->response_header->ptr, hctx->response_header->used);
joblist_append(srv, con);
} else {
size_t hlen = c - hctx->response_header->ptr + (eol == EOL_RN ? 4 : 2);
size_t blen = hctx->response_header->used - hlen - 1;
/* a small hack: terminate after at the second \r */
hctx->response_header->used = hlen + 1 - (eol == EOL_RN ? 2 : 1);
hctx->response_header->ptr[hlen - (eol == EOL_RN ? 2 : 1)] = '\0';
const char *bstart;
size_t blen;
/**
* i still points to the char after the terminating EOL EOL
*
* put it on the last \n again
*/
i--;
/* the body starts after the EOL */
bstart = hctx->response_header->ptr + (i + 1);
blen = (hctx->response_header->used - 1) - (i + 1);
/* string the last \r?\n */
if (i > 0 && (hctx->response_header->ptr[i - 1] == '\r')) {
i--;
}
hctx->response_header->ptr[i] = '\0';
hctx->response_header->used = i + 1; /* the string + \0 */
/* parse the response header */
cgi_response_parse(srv, con, p, hctx->response_header, eol);
cgi_response_parse(srv, con, p, hctx->response_header);
/* enable chunked-transfer-encoding */
if (con->request.http_version == HTTP_VERSION_1_1 &&
@ -444,8 +480,8 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) {
con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED;
}
if ((hctx->response->used != hlen) && blen > 0) {
http_chunk_append_mem(srv, con, c + (eol == EOL_RN ? 4: 2), blen + 1);
if (blen > 0) {
http_chunk_append_mem(srv, con, bstart, blen + 1);
joblist_append(srv, con);
}
}

3
tests/mod-cgi.t

@ -115,12 +115,13 @@ EOF
);
$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200, '+Content-Length' => '' } ];
ok($tf->handle_http($t) == 0, 'cgi-env: HTTP_HOST');
# broken header crash
$t->{REQUEST} = ( <<EOF
GET /crlfcrash.pl HTTP/1.0
EOF
);
$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 500 } ];
$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 302, 'Location' => 'http://www.example.org/' } ];
ok($tf->handle_http($t) == 0, 'broken header via perl cgi');
ok($tf->stop_proc == 0, "Stopping lighttpd");

Loading…
Cancel
Save