From f5fbcdd0f12f01f0a9d849079c407ef5cf45775d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 27 Aug 2018 10:37:26 +0300 Subject: [PATCH] use-after-free header unfolding fix --- a9e131..df8e4f.patch | 976 +++++++++++++++++++++++++++++++++++++++++++ lighttpd.spec | 4 +- 2 files changed, 979 insertions(+), 1 deletion(-) create mode 100644 a9e131..df8e4f.patch diff --git a/a9e131..df8e4f.patch b/a9e131..df8e4f.patch new file mode 100644 index 0000000..12ea77c --- /dev/null +++ b/a9e131..df8e4f.patch @@ -0,0 +1,976 @@ +From: =?UTF-8?Q?Stefan_B=c3=bchler?= +To: lighttpd-announce@lists.lighttpd.net +Message-ID: <8cbf0c87-1037-8f70-aa24-b2f14c95170d@web.de> +Date: Sun, 26 Aug 2018 19:06:30 +0200 +Subject: use-after-free bug in header folding + +Hi, + +Or Peles reported some use-after-free scenarios related to header +folding (continuing a header value on a whitespace indented line). + +The fix required some preparation and therefor consists of a series of 4 +patches, see git: + +https://git.lighttpd.net/lighttpd/lighttpd1.4.git/log/?qt=range&q=a9e131..df8e4f&showmsg=1 + +There are currently no plans for a new release. + +cheers, +Stefan + +diff --git a/src/request.c b/src/request.c +index 213a87e1..e94d8591 100644 +--- a/src/request.c ++++ b/src/request.c +@@ -408,26 +408,179 @@ static int request_uri_is_valid_char(unsigned char c) { + return 1; + } + +-static int http_request_missing_CR_before_LF(server *srv, connection *con) { ++static void http_request_missing_CR_before_LF(server *srv, connection *con) { + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", "missing CR before LF in header -> 400"); + log_error_write(srv, __FILE__, __LINE__, "Sb", "request-header:\n", con->request.request); + } ++} + +- con->http_status = 400; +- con->keep_alive = 0; +- con->response.keep_alive = 0; ++enum keep_alive_set { ++ HTTP_CONNECTION_UNSET, ++ HTTP_CONNECTION_KEEPALIVE, ++ HTTP_CONNECTION_CLOSE, ++}; ++ ++typedef struct { ++ enum keep_alive_set keep_alive_set; ++ char con_length_set; ++ char *reqline_host; ++ int reqline_hostlen; ++} parse_header_state; ++ ++static void init_parse_header_state(parse_header_state* state) { ++ state->keep_alive_set = HTTP_CONNECTION_UNSET; ++ state->con_length_set = 0; ++ state->reqline_host = NULL; ++ state->reqline_hostlen = 0; ++} ++ ++/* add a header to the list of headers; certain headers are also parsed in this state. ++ * ++ * Also might drop a header if deemed unnecessary/broken. ++ * ++ * returns 0 on error ++ */ ++static int parse_single_header(server *srv, connection *con, parse_header_state *state, data_string *ds) { ++ int cmp = 0; ++ ++ /* empty header-fields are not allowed by HTTP-RFC, we just ignore them */ ++ if (buffer_string_is_empty(ds->value)) { ++ goto drop_header; ++ } ++ ++ /* retreive values ++ * ++ * ++ * the list of options is sorted to simplify the search ++ */ ++ ++ if (0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Connection")))) { ++ array *vals; ++ size_t vi; ++ ++ /* split on , */ ++ ++ vals = srv->split_vals; ++ ++ array_reset(vals); ++ ++ http_request_split_value(vals, ds->value); ++ ++ for (vi = 0; vi < vals->used; vi++) { ++ data_string *dsv = (data_string *)vals->data[vi]; ++ ++ if (0 == buffer_caseless_compare(CONST_BUF_LEN(dsv->value), CONST_STR_LEN("keep-alive"))) { ++ state->keep_alive_set = HTTP_CONNECTION_KEEPALIVE; ++ ++ break; ++ } else if (0 == buffer_caseless_compare(CONST_BUF_LEN(dsv->value), CONST_STR_LEN("close"))) { ++ state->keep_alive_set = HTTP_CONNECTION_CLOSE; ++ ++ break; ++ } ++ } ++ ++ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Content-Length")))) { ++ char *err; ++ off_t r; ++ ++ if (state->con_length_set) { ++ if (srv->srvconf.log_request_header_on_error) { ++ log_error_write(srv, __FILE__, __LINE__, "s", ++ "duplicate Content-Length-header -> 400"); ++ log_error_write(srv, __FILE__, __LINE__, "Sb", ++ "request-header:\n", ++ con->request.request); ++ } ++ goto invalid_header; ++ } ++ ++ r = strtoll(ds->value->ptr, &err, 10); ++ ++ if (*err == '\0' && r >= 0) { ++ state->con_length_set = 1; ++ con->request.content_length = r; ++ } else { ++ log_error_write(srv, __FILE__, __LINE__, "sbs", ++ "content-length broken:", ds->value, "-> 400"); ++ goto invalid_header; ++ } ++ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Content-Type")))) { ++ /* if dup, only the first one will survive */ ++ if (!con->request.http_content_type) { ++ con->request.http_content_type = ds->value->ptr; ++ } else { ++ if (srv->srvconf.log_request_header_on_error) { ++ log_error_write(srv, __FILE__, __LINE__, "s", ++ "duplicate Content-Type-header -> 400"); ++ log_error_write(srv, __FILE__, __LINE__, "Sb", ++ "request-header:\n", ++ con->request.request); ++ } ++ goto invalid_header; ++ } ++ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Host")))) { ++ if (state->reqline_host) { ++ /* ignore all host: headers as we got the host in the request line */ ++ goto drop_header; ++ } else if (!con->request.http_host) { ++ con->request.http_host = ds->value; ++ } else { ++ if (srv->srvconf.log_request_header_on_error) { ++ log_error_write(srv, __FILE__, __LINE__, "s", ++ "duplicate Host-header -> 400"); ++ log_error_write(srv, __FILE__, __LINE__, "Sb", ++ "request-header:\n", ++ con->request.request); ++ } ++ goto invalid_header; ++ } ++ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("If-Modified-Since")))) { ++ /* Proxies sometimes send dup headers ++ * if they are the same we ignore the second ++ * if not, we raise an error */ ++ if (!con->request.http_if_modified_since) { ++ con->request.http_if_modified_since = ds->value->ptr; ++ } else if (0 == strcasecmp(con->request.http_if_modified_since, ds->value->ptr)) { ++ /* ignore it if they are the same */ ++ goto drop_header; ++ } else { ++ if (srv->srvconf.log_request_header_on_error) { ++ log_error_write(srv, __FILE__, __LINE__, "s", ++ "duplicate If-Modified-Since header -> 400"); ++ log_error_write(srv, __FILE__, __LINE__, "Sb", ++ "request-header:\n", ++ con->request.request); ++ } ++ goto invalid_header; ++ } ++ } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("If-None-Match")))) { ++ /* if dup, only the first one will survive */ ++ if (!con->request.http_if_none_match) { ++ con->request.http_if_none_match = ds->value->ptr; ++ } else { ++ goto drop_header; ++ } ++ } ++ ++ array_insert_unique(con->request.headers, (data_unset *)ds); ++ return 1; ++ ++drop_header: ++ ds->free((data_unset *)ds); ++ return 1; ++ ++invalid_header: ++ ds->free((data_unset *)ds); + return 0; + } + + int http_request_parse(server *srv, connection *con) { +- char *uri = NULL, *proto = NULL, *method = NULL, con_length_set; ++ char *uri = NULL, *proto = NULL, *method = NULL; + int is_key = 1, key_len = 0, is_ws_after_key = 0, in_folding; + char *value = NULL, *key = NULL; +- char *reqline_host = NULL; +- int reqline_hostlen = 0; +- +- enum { HTTP_CONNECTION_UNSET, HTTP_CONNECTION_KEEPALIVE, HTTP_CONNECTION_CLOSE } keep_alive_set = HTTP_CONNECTION_UNSET; ++ data_string *current_header = NULL; + + int line = 0; + +@@ -437,6 +590,9 @@ int http_request_parse(server *srv, connection *con) { + int done = 0; + const unsigned int http_header_strict = (con->conf.http_parseopts & HTTP_PARSEOPT_HEADER_STRICT); + ++ parse_header_state state; ++ init_parse_header_state(&state); ++ + /* + * Request: "^(GET|POST|HEAD) ([^ ]+(\\?[^ ]+|)) (HTTP/1\\.[01])$" + * Option : "^([-a-zA-Z]+): (.+)$" +@@ -457,9 +613,7 @@ int http_request_parse(server *srv, connection *con) { + + #ifdef __COVERITY__ + if (buffer_string_length(con->request.request) < 2) { +- con->keep_alive = 0; +- con->http_status = 400; +- return 0; ++ goto failure; + } + #endif + /* coverity[overflow_sink : FALSE] */ +@@ -467,12 +621,13 @@ int http_request_parse(server *srv, connection *con) { + } else if (con->request_count > 0 && + con->request.request->ptr[1] == '\n') { + /* we are in keep-alive and might get \n after a previous POST request.*/ +- if (http_header_strict) return http_request_missing_CR_before_LF(srv, con); ++ if (http_header_strict) { ++ http_request_missing_CR_before_LF(srv, con); ++ goto failure; ++ } + #ifdef __COVERITY__ + if (buffer_string_length(con->request.request) < 1) { +- con->keep_alive = 0; +- con->http_status = 400; +- return 0; ++ goto failure; + } + #endif + /* coverity[overflow_sink : FALSE] */ +@@ -482,9 +637,6 @@ int http_request_parse(server *srv, connection *con) { + buffer_copy_buffer(con->parse_request, con->request.request); + } + +- keep_alive_set = 0; +- con_length_set = 0; +- + /* parse the first line of the request + * + * should be: +@@ -510,22 +662,19 @@ int http_request_parse(server *srv, connection *con) { + con->parse_request->ptr[i] = '\0'; + ++i; + } else if (http_header_strict) { /* '\n' */ +- return http_request_missing_CR_before_LF(srv, con); ++ http_request_missing_CR_before_LF(srv, con); ++ goto failure; + } + con->parse_request->ptr[i] = '\0'; + + if (request_line_stage != 2) { +- con->http_status = 400; +- con->response.keep_alive = 0; +- con->keep_alive = 0; +- + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", "incomplete request line -> 400"); + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } +- return 0; ++ goto failure; + } + + proto = con->parse_request->ptr + first; +@@ -536,8 +685,6 @@ int http_request_parse(server *srv, connection *con) { + /* we got the first one :) */ + if (HTTP_METHOD_UNSET == (r = get_http_method_key(method))) { + con->http_status = 501; +- con->response.keep_alive = 0; +- con->keep_alive = 0; + + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", "unknown http-method -> 501"); +@@ -546,7 +693,7 @@ int http_request_parse(server *srv, connection *con) { + con->request.request); + } + +- return 0; ++ goto failure; + } + + con->request.http_method = r; +@@ -582,16 +729,13 @@ int http_request_parse(server *srv, connection *con) { + } + + if (invalid_version) { +- con->http_status = 400; +- con->keep_alive = 0; +- + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", "unknown protocol -> 400"); + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } +- return 0; ++ goto failure; + } + + if (major_num == 1 && minor_num == 1) { +@@ -607,19 +751,16 @@ int http_request_parse(server *srv, connection *con) { + "request-header:\n", + con->request.request); + } +- return 0; ++ goto failure; + } + } else { +- con->http_status = 400; +- con->keep_alive = 0; +- + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", "unknown protocol -> 400"); + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } +- return 0; ++ goto failure; + } + + if (*uri == '/') { +@@ -627,14 +768,14 @@ int http_request_parse(server *srv, connection *con) { + buffer_copy_string_len(con->request.uri, uri, proto - uri - 1); + } else if (0 == strncasecmp(uri, "http://", 7) && + NULL != (nuri = strchr(uri + 7, '/'))) { +- reqline_host = uri + 7; +- reqline_hostlen = nuri - reqline_host; ++ state.reqline_host = uri + 7; ++ state.reqline_hostlen = nuri - state.reqline_host; + + buffer_copy_string_len(con->request.uri, nuri, proto - nuri - 1); + } else if (0 == strncasecmp(uri, "https://", 8) && + NULL != (nuri = strchr(uri + 8, '/'))) { +- reqline_host = uri + 8; +- reqline_hostlen = nuri - reqline_host; ++ state.reqline_host = uri + 8; ++ state.reqline_hostlen = nuri - state.reqline_host; + + buffer_copy_string_len(con->request.uri, nuri, proto - nuri - 1); + } else if (!http_header_strict +@@ -643,10 +784,8 @@ int http_request_parse(server *srv, connection *con) { + /* everything looks good so far */ + buffer_copy_string_len(con->request.uri, uri, proto - uri - 1); + } else { +- con->http_status = 400; +- con->keep_alive = 0; + log_error_write(srv, __FILE__, __LINE__, "ss", "request-URI parse error -> 400 for:", uri); +- return 0; ++ goto failure; + } + + /* check uri for invalid characters */ +@@ -660,33 +799,30 @@ int http_request_parse(server *srv, connection *con) { + j = (NULL == z) ? jlen : (size_t)(z - con->request.uri->ptr); + } + if (j < jlen) { +- con->http_status = 400; +- con->keep_alive = 0; +- +- if (srv->srvconf.log_request_header_on_error) { +- unsigned char buf[2]; +- buf[0] = con->request.uri->ptr[j]; +- buf[1] = '\0'; +- +- if (con->request.uri->ptr[j] > 32 && +- con->request.uri->ptr[j] != 127) { +- /* the character is printable -> print it */ +- log_error_write(srv, __FILE__, __LINE__, "ss", +- "invalid character in URI -> 400", +- buf); +- } else { +- /* a control-character, print ascii-code */ +- log_error_write(srv, __FILE__, __LINE__, "sd", +- "invalid character in URI -> 400", +- con->request.uri->ptr[j]); +- } +- +- log_error_write(srv, __FILE__, __LINE__, "Sb", +- "request-header:\n", +- con->request.request); ++ if (srv->srvconf.log_request_header_on_error) { ++ unsigned char buf[2]; ++ buf[0] = con->request.uri->ptr[j]; ++ buf[1] = '\0'; ++ ++ if (con->request.uri->ptr[j] > 32 && ++ con->request.uri->ptr[j] != 127) { ++ /* the character is printable -> print it */ ++ log_error_write(srv, __FILE__, __LINE__, "ss", ++ "invalid character in URI -> 400", ++ buf); ++ } else { ++ /* a control-character, print ascii-code */ ++ log_error_write(srv, __FILE__, __LINE__, "sd", ++ "invalid character in URI -> 400", ++ con->request.uri->ptr[j]); + } + +- return 0; ++ log_error_write(srv, __FILE__, __LINE__, "Sb", ++ "request-header:\n", ++ con->request.request); ++ } ++ ++ goto failure; + } + + buffer_copy_buffer(con->request.orig_uri, con->request.uri); +@@ -711,17 +847,13 @@ int http_request_parse(server *srv, connection *con) { + break; + default: + /* ERROR, one space to much */ +- con->http_status = 400; +- con->response.keep_alive = 0; +- con->keep_alive = 0; +- + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", "overlong request line -> 400"); + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } +- return 0; ++ goto failure; + } + + request_line_stage++; +@@ -732,20 +864,16 @@ int http_request_parse(server *srv, connection *con) { + in_folding = 0; + + if (buffer_string_is_empty(con->request.uri)) { +- con->http_status = 400; +- con->response.keep_alive = 0; +- con->keep_alive = 0; +- + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", "no uri specified -> 400"); + log_error_write(srv, __FILE__, __LINE__, "Sb", + "request-header:\n", + con->request.request); + } +- return 0; ++ goto failure; + } + +- if (reqline_host) { ++ if (state.reqline_host) { + /* Insert as host header */ + data_string *ds; + +@@ -754,7 +882,7 @@ int http_request_parse(server *srv, connection *con) { + } + + buffer_copy_string_len(ds->key, CONST_STR_LEN("Host")); +- buffer_copy_string_len(ds->value, reqline_host, reqline_hostlen); ++ buffer_copy_string_len(ds->value, state.reqline_host, state.reqline_hostlen); + array_insert_unique(con->request.headers, (data_unset *)ds); + con->request.http_host = ds->value; + } +@@ -799,10 +927,6 @@ int http_request_parse(server *srv, connection *con) { + case '=': + case '{': + case '}': +- con->http_status = 400; +- con->keep_alive = 0; +- con->response.keep_alive = 0; +- + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "sbsds", + "invalid character in key", con->request.request, cur, *cur, "-> 400"); +@@ -811,7 +935,7 @@ int http_request_parse(server *srv, connection *con) { + "request-header:\n", + con->request.request); + } +- return 0; ++ goto failure; + case ' ': + case '\t': + if (i == first) { +@@ -850,11 +974,7 @@ int http_request_parse(server *srv, connection *con) { + con->request.request); + } + +- con->http_status = 400; +- con->response.keep_alive = 0; +- con->keep_alive = 0; +- +- return 0; ++ goto failure; + } + } + +@@ -876,15 +996,13 @@ int http_request_parse(server *srv, connection *con) { + con->request.request); + } + +- con->http_status = 400; +- con->keep_alive = 0; +- con->response.keep_alive = 0; +- return 0; ++ goto failure; + } + break; + case '\n': + if (http_header_strict) { +- return http_request_missing_CR_before_LF(srv, con); ++ http_request_missing_CR_before_LF(srv, con); ++ goto failure; + } else if (i == first) { + con->parse_request->ptr[i] = '\0'; + done = 1; +@@ -893,10 +1011,6 @@ int http_request_parse(server *srv, connection *con) { + /* fall through */ + default: + if (http_header_strict ? (*cur < 32 || ((unsigned char)*cur) >= 127) : *cur == '\0') { +- con->http_status = 400; +- con->keep_alive = 0; +- con->response.keep_alive = 0; +- + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "sbsds", + "invalid character in key", con->request.request, cur, *cur, "-> 400"); +@@ -906,7 +1020,7 @@ int http_request_parse(server *srv, connection *con) { + con->request.request); + } + +- return 0; ++ goto failure; + } + /* ok */ + break; +@@ -916,9 +1030,13 @@ int http_request_parse(server *srv, connection *con) { + case '\r': + case '\n': + if (*cur == '\n' || con->parse_request->ptr[i+1] == '\n') { +- data_string *ds = NULL; ++ int value_len; ++ + if (*cur == '\n') { +- if (http_header_strict) return http_request_missing_CR_before_LF(srv, con); ++ if (http_header_strict) { ++ http_request_missing_CR_before_LF(srv, con); ++ goto failure; ++ } + } else { /* (con->parse_request->ptr[i+1] == '\n') */ + con->parse_request->ptr[i] = '\0'; + ++i; +@@ -927,17 +1045,15 @@ int http_request_parse(server *srv, connection *con) { + /* End of Headerline */ + con->parse_request->ptr[i] = '\0'; + ++ value_len = cur - value; ++ ++ /* strip trailing white-spaces */ ++ while (value_len > 0 && (value[value_len - 1] == ' ' || value[value_len - 1] == '\t')) { ++ --value_len; ++ } ++ + if (in_folding) { +- /** +- * we use a evil hack to handle the line-folding +- * +- * As array_insert_unique() deletes 'ds' in the case of a duplicate +- * ds points somewhere and we get a evil crash. As a solution we keep the old +- * "key" and get the current value from the hash and append us +- * +- * */ +- +- if (!key || !key_len) { ++ if (!current_header) { + /* 400 */ + + if (srv->srvconf.log_request_header_on_error) { +@@ -948,193 +1064,35 @@ int http_request_parse(server *srv, connection *con) { + con->request.request); + } + +- +- con->http_status = 400; +- con->keep_alive = 0; +- con->response.keep_alive = 0; +- return 0; ++ goto failure; + } + +- if (NULL != (ds = (data_string *)array_get_element_klen(con->request.headers, key, key_len))) { +- buffer_append_string(ds->value, value); +- } ++ buffer_append_string_len(current_header->value, value, value_len); + } else { +- int s_len; +- key = con->parse_request->ptr + first; +- +- s_len = cur - value; +- +- /* strip trailing white-spaces */ +- for (; s_len > 0 && +- (value[s_len - 1] == ' ' || +- value[s_len - 1] == '\t'); s_len--); +- +- value[s_len] = '\0'; +- +- if (s_len > 0) { +- int cmp = 0; +- if (NULL == (ds = (data_string *)array_get_unused_element(con->request.headers, TYPE_STRING))) { +- ds = data_string_init(); +- } +- buffer_copy_string_len(ds->key, key, key_len); +- buffer_copy_string_len(ds->value, value, s_len); +- +- /* retreive values +- * +- * +- * the list of options is sorted to simplify the search +- */ +- +- if (0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Connection")))) { +- array *vals; +- size_t vi; +- +- /* split on , */ +- +- vals = srv->split_vals; +- +- array_reset(vals); +- +- http_request_split_value(vals, ds->value); +- +- for (vi = 0; vi < vals->used; vi++) { +- data_string *dsv = (data_string *)vals->data[vi]; +- +- if (0 == buffer_caseless_compare(CONST_BUF_LEN(dsv->value), CONST_STR_LEN("keep-alive"))) { +- keep_alive_set = HTTP_CONNECTION_KEEPALIVE; +- +- break; +- } else if (0 == buffer_caseless_compare(CONST_BUF_LEN(dsv->value), CONST_STR_LEN("close"))) { +- keep_alive_set = HTTP_CONNECTION_CLOSE; +- +- break; +- } +- } +- +- } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Content-Length")))) { +- char *err; +- off_t r; +- +- if (con_length_set) { +- con->http_status = 400; +- con->keep_alive = 0; +- +- if (srv->srvconf.log_request_header_on_error) { +- log_error_write(srv, __FILE__, __LINE__, "s", +- "duplicate Content-Length-header -> 400"); +- log_error_write(srv, __FILE__, __LINE__, "Sb", +- "request-header:\n", +- con->request.request); +- } +- array_insert_unique(con->request.headers, (data_unset *)ds); +- return 0; +- } +- +- r = strtoll(ds->value->ptr, &err, 10); +- +- if (*err == '\0' && r >= 0) { +- con_length_set = 1; +- con->request.content_length = r; +- } else { +- log_error_write(srv, __FILE__, __LINE__, "sbs", +- "content-length broken:", ds->value, "-> 400"); +- +- con->http_status = 400; +- con->keep_alive = 0; +- +- array_insert_unique(con->request.headers, (data_unset *)ds); +- return 0; +- } +- } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Content-Type")))) { +- /* if dup, only the first one will survive */ +- if (!con->request.http_content_type) { +- con->request.http_content_type = ds->value->ptr; +- } else { +- con->http_status = 400; +- con->keep_alive = 0; +- +- if (srv->srvconf.log_request_header_on_error) { +- log_error_write(srv, __FILE__, __LINE__, "s", +- "duplicate Content-Type-header -> 400"); +- log_error_write(srv, __FILE__, __LINE__, "Sb", +- "request-header:\n", +- con->request.request); +- } +- array_insert_unique(con->request.headers, (data_unset *)ds); +- return 0; +- } +- } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("Host")))) { +- if (reqline_host) { +- /* ignore all host: headers as we got the host in the request line */ +- ds->free((data_unset*) ds); +- ds = NULL; +- } else if (!con->request.http_host) { +- con->request.http_host = ds->value; +- } else { +- con->http_status = 400; +- con->keep_alive = 0; +- +- if (srv->srvconf.log_request_header_on_error) { +- log_error_write(srv, __FILE__, __LINE__, "s", +- "duplicate Host-header -> 400"); +- log_error_write(srv, __FILE__, __LINE__, "Sb", +- "request-header:\n", +- con->request.request); +- } +- array_insert_unique(con->request.headers, (data_unset *)ds); +- return 0; +- } +- } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("If-Modified-Since")))) { +- /* Proxies sometimes send dup headers +- * if they are the same we ignore the second +- * if not, we raise an error */ +- if (!con->request.http_if_modified_since) { +- con->request.http_if_modified_since = ds->value->ptr; +- } else if (0 == strcasecmp(con->request.http_if_modified_since, +- ds->value->ptr)) { +- /* ignore it if they are the same */ +- +- ds->free((data_unset *)ds); +- ds = NULL; +- } else { +- con->http_status = 400; +- con->keep_alive = 0; +- +- if (srv->srvconf.log_request_header_on_error) { +- log_error_write(srv, __FILE__, __LINE__, "s", +- "duplicate If-Modified-Since header -> 400"); +- log_error_write(srv, __FILE__, __LINE__, "Sb", +- "request-header:\n", +- con->request.request); +- } +- array_insert_unique(con->request.headers, (data_unset *)ds); +- return 0; +- } +- } else if (cmp > 0 && 0 == (cmp = buffer_caseless_compare(CONST_BUF_LEN(ds->key), CONST_STR_LEN("If-None-Match")))) { +- /* if dup, only the first one will survive */ +- if (!con->request.http_if_none_match) { +- con->request.http_if_none_match = ds->value->ptr; +- } else { +- ds->free((data_unset*) ds); +- ds = NULL; +- } ++ /* process previous header */ ++ if (current_header) { ++ data_string *ds = current_header; ++ current_header = NULL; ++ if (!parse_single_header(srv, con, &state, ds)) { ++ /* parse_single_header should already have logged it */ ++ goto failure; + } ++ } + +- if (ds) array_insert_unique(con->request.headers, (data_unset *)ds); +- } else { +- /* empty header-fields are not allowed by HTTP-RFC, we just ignore them */ ++ key = con->parse_request->ptr + first; ++ ++ if (NULL == (current_header = (data_string *)array_get_unused_element(con->request.headers, TYPE_STRING))) { ++ current_header = data_string_init(); + } ++ ++ buffer_copy_string_len(current_header->key, key, key_len); ++ buffer_copy_string_len(current_header->value, value, value_len); + } + + first = i+1; + is_key = 1; + value = NULL; +-#if 0 +- /** +- * for Bug 1230 keep the key_len a live +- */ + key_len = 0; +-#endif + in_folding = 0; + } else { + if (srv->srvconf.log_request_header_on_error) { +@@ -1142,10 +1100,7 @@ int http_request_parse(server *srv, connection *con) { + "CR without LF", con->request.request, "-> 400"); + } + +- con->http_status = 400; +- con->keep_alive = 0; +- con->response.keep_alive = 0; +- return 0; ++ goto failure; + } + break; + case ' ': +@@ -1160,22 +1115,29 @@ int http_request_parse(server *srv, connection *con) { + "invalid char in header", (int)*cur, "-> 400"); + } + +- con->http_status = 400; +- con->keep_alive = 0; +- +- return 0; ++ goto failure; + } + break; + } + } + } + ++ /* process last header */ ++ if (current_header) { ++ data_string* ds = current_header; ++ current_header = NULL; ++ if (!parse_single_header(srv, con, &state, ds)) { ++ /* parse_single_header should already have logged it */ ++ goto failure; ++ } ++ } ++ + con->header_len = i; + + /* do some post-processing */ + + if (con->request.http_version == HTTP_VERSION_1_1) { +- if (keep_alive_set != HTTP_CONNECTION_CLOSE) { ++ if (state.keep_alive_set != HTTP_CONNECTION_CLOSE) { + /* no Connection-Header sent */ + + /* HTTP/1.1 -> keep-alive default TRUE */ +@@ -1187,9 +1149,6 @@ int http_request_parse(server *srv, connection *con) { + /* RFC 2616, 14.23 */ + if (con->request.http_host == NULL || + buffer_string_is_empty(con->request.http_host)) { +- con->http_status = 400; +- con->response.keep_alive = 0; +- con->keep_alive = 0; + + if (srv->srvconf.log_request_header_on_error) { + log_error_write(srv, __FILE__, __LINE__, "s", "HTTP/1.1 but Host missing -> 400"); +@@ -1197,10 +1156,10 @@ int http_request_parse(server *srv, connection *con) { + "request-header:\n", + con->request.request); + } +- return 0; ++ goto failure; + } + } else { +- if (keep_alive_set == HTTP_CONNECTION_KEEPALIVE) { ++ if (state.keep_alive_set == HTTP_CONNECTION_KEEPALIVE) { + /* no Connection-Header sent */ + + /* HTTP/1.0 -> keep-alive default FALSE */ +@@ -1222,11 +1181,7 @@ int http_request_parse(server *srv, connection *con) { + con->request.request); + } + +- con->http_status = 400; +- con->response.keep_alive = 0; +- con->keep_alive = 0; +- +- return 0; ++ goto failure; + } + + { +@@ -1235,24 +1190,21 @@ int http_request_parse(server *srv, connection *con) { + if (con->request.http_version == HTTP_VERSION_1_0) { + log_error_write(srv, __FILE__, __LINE__, "s", + "HTTP/1.0 with Transfer-Encoding (bad HTTP/1.0 proxy?) -> 400"); +- con->keep_alive = 0; +- con->http_status = 400; /* Bad Request */ +- return 0; ++ goto failure; + } + + if (0 != strcasecmp(ds->value->ptr, "chunked")) { + /* Transfer-Encoding might contain additional encodings, + * which are not currently supported by lighttpd */ +- con->keep_alive = 0; + con->http_status = 501; /* Not Implemented */ +- return 0; ++ goto failure; + } + + /* reset value for Transfer-Encoding, a hop-by-hop header, + * which must not be blindly forwarded to backends */ + buffer_reset(ds->value); /* headers with empty values are ignored */ + +- con_length_set = 1; ++ state.con_length_set = 1; + con->request.content_length = -1; + + /*(note: ignore whether or not Content-Length was provided)*/ +@@ -1265,27 +1217,23 @@ int http_request_parse(server *srv, connection *con) { + case HTTP_METHOD_GET: + case HTTP_METHOD_HEAD: + /* content-length is forbidden for those */ +- if (con_length_set && con->request.content_length != 0) { ++ if (state.con_length_set && con->request.content_length != 0) { + /* content-length is missing */ + log_error_write(srv, __FILE__, __LINE__, "s", + "GET/HEAD with content-length -> 400"); + +- con->keep_alive = 0; +- con->http_status = 400; +- return 0; ++ goto failure; + } + break; + case HTTP_METHOD_POST: + /* content-length is required for them */ +- if (!con_length_set) { ++ if (!state.con_length_set) { + /* content-length is missing */ + log_error_write(srv, __FILE__, __LINE__, "s", + "POST-request, but content-length missing -> 411"); + +- con->keep_alive = 0; + con->http_status = 411; +- return 0; +- ++ goto failure; + } + break; + default: +@@ -1294,12 +1242,21 @@ int http_request_parse(server *srv, connection *con) { + + + /* check if we have read post data */ +- if (con_length_set) { ++ if (state.con_length_set) { + /* we have content */ + if (con->request.content_length != 0) { + return 1; + } + } + ++ return 0; ++ ++failure: ++ if (current_header) current_header->free((data_unset *)current_header); ++ ++ con->keep_alive = 0; ++ con->response.keep_alive = 0; ++ if (!con->http_status) con->http_status = 400; ++ + return 0; + } diff --git a/lighttpd.spec b/lighttpd.spec index dc77eff..5ac2ff0 100644 --- a/lighttpd.spec +++ b/lighttpd.spec @@ -42,7 +42,7 @@ Summary: Fast and light HTTP server Summary(pl.UTF-8): Szybki i lekki serwer HTTP Name: lighttpd Version: 1.4.50 -Release: 1 +Release: 2 License: BSD Group: Networking/Daemons/HTTP Source0: https://download.lighttpd.net/lighttpd/releases-1.4.x/%{name}-%{version}.tar.xz @@ -120,6 +120,7 @@ Patch2: %{name}-mod_h264_streaming.patch Patch3: %{name}-branding.patch Patch4: systemd.patch Patch5: test-port-setup.patch +Patch6: a9e131..df8e4f.patch URL: https://www.lighttpd.net/ %{?with_geoip:BuildRequires: GeoIP-devel} %{?with_xattr:BuildRequires: attr-devel} @@ -952,6 +953,7 @@ Plik monitrc do monitorowania serwera www lighttpd. %patch3 -p1 %patch4 -p1 %patch5 -p1 +%patch6 -p1 rm -f src/mod_ssi_exprparser.h # bad patching: should be removed by is emptied instead -- 2.43.0