apache: CVE-2014-0117 The patch comes from upstream: http://svn.apache.org/viewvc?view=revision&revision=1610674 SECURITY (CVE-2014-0117): Fix a crash in mod_proxy. In a reverse proxy configuration, a remote attacker could send a carefully crafted request which could crash a server process, resulting in denial of service. Thanks to Marek Kroemeke working with HP's Zero Day Initiative for reporting this issue. Upstream-Status: Backport Submitted by: Edward Lu, breser, covener Signed-off-by: Zhang Xiao --- modules/proxy/mod_proxy_http.c | 8 +++- include/httpd.h | 17 ++++++++ modules/proxy/proxy_util.c | 67 ++++++++++++++---------------- server/util.c | 89 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 143 insertions(+), 38 deletions(-) diff --git a/modules/proxy/mod_proxy_http.c b/modules/proxy/mod_proxy_http.c index cffad2e..f11c16f 100644 --- a/modules/proxy/mod_proxy_http.c +++ b/modules/proxy/mod_proxy_http.c @@ -1362,6 +1362,7 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, */ if (apr_date_checkmask(buffer, "HTTP/#.# ###*")) { int major, minor; + int toclose; major = buffer[5] - '0'; minor = buffer[7] - '0'; @@ -1470,7 +1471,12 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, te = apr_table_get(r->headers_out, "Transfer-Encoding"); /* strip connection listed hop-by-hop headers from response */ - backend->close = ap_proxy_clear_connection_fn(r, r->headers_out); + toclose = ap_proxy_clear_connection_fn(r, r->headers_out); + backend->close = (toclose != 0); + if (toclose < 0) { + return ap_proxyerror(r, HTTP_BAD_REQUEST, + "Malformed connection header"); + } if ((buf = apr_table_get(r->headers_out, "Content-Type"))) { ap_set_content_type(r, apr_pstrdup(p, buf)); diff --git a/include/httpd.h b/include/httpd.h index 36cd58d..9a2cf5c 100644 --- a/include/httpd.h +++ b/include/httpd.h @@ -1528,6 +1528,23 @@ AP_DECLARE(int) ap_find_etag_weak(apr_pool_t *p, const char *line, const char *t AP_DECLARE(int) ap_find_etag_strong(apr_pool_t *p, const char *line, const char *tok); /** + * Retrieve an array of tokens in the format "1#token" defined in RFC2616. Only + * accepts ',' as a delimiter, does not accept quoted strings, and errors on + * any separator. + * @param p The pool to allocate from + * @param tok The line to read tokens from + * @param tokens Pointer to an array of tokens. If not NULL, must be an array + * of char*, otherwise it will be allocated on @a p when a token is found + * @param skip_invalid If true, when an invalid separator is encountered, it + * will be ignored. + * @return NULL on success, an error string otherwise. + * @remark *tokens may be NULL on output if NULL in input and no token is found + */ +AP_DECLARE(const char *) ap_parse_token_list_strict(apr_pool_t *p, const char *tok, + apr_array_header_t **tokens, + int skip_invalid); + +/** * Retrieve a token, spacing over it and adjusting the pointer to * the first non-white byte afterwards. Note that these tokens * are delimited by semis and commas and can also be delimited diff --git a/modules/proxy/proxy_util.c b/modules/proxy/proxy_util.c index 67dc939..58daa21 100644 --- a/modules/proxy/proxy_util.c +++ b/modules/proxy/proxy_util.c @@ -2847,68 +2847,59 @@ PROXY_DECLARE(proxy_balancer_shared *) ap_proxy_find_balancershm(ap_slotmem_prov typedef struct header_connection { apr_pool_t *pool; apr_array_header_t *array; - const char *first; - unsigned int closed:1; + const char *error; + int is_req; } header_connection; static int find_conn_headers(void *data, const char *key, const char *val) { header_connection *x = data; - const char *name; - - do { - while (*val == ',' || *val == ';') { - val++; - } - name = ap_get_token(x->pool, &val, 0); - if (!strcasecmp(name, "close")) { - x->closed = 1; - } - if (!x->first) { - x->first = name; - } - else { - const char **elt; - if (!x->array) { - x->array = apr_array_make(x->pool, 4, sizeof(char *)); - } - elt = apr_array_push(x->array); - *elt = name; - } - } while (*val); - return 1; + x->error = ap_parse_token_list_strict(x->pool, val, &x->array, !x->is_req); + return !x->error; } /** * Remove all headers referred to by the Connection header. + * Returns -1 on error. Otherwise, returns 1 if 'Close' was seen in + * the Connection header tokens, and 0 if not. */ static int ap_proxy_clear_connection(request_rec *r, apr_table_t *headers) { - const char **name; + int closed = 0; header_connection x; x.pool = r->pool; x.array = NULL; - x.first = NULL; - x.closed = 0; + x.error = NULL; + x.is_req = (headers == r->headers_in); apr_table_unset(headers, "Proxy-Connection"); apr_table_do(find_conn_headers, &x, headers, "Connection", NULL); - if (x.first) { - /* fast path - no memory allocated for one header */ - apr_table_unset(headers, "Connection"); - apr_table_unset(headers, x.first); + apr_table_unset(headers, "Connection"); + + if (x.error) { + ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO() + "Error parsing Connection header: %s", x.error); + return -1; } + if (x.array) { - /* two or more headers */ - while ((name = apr_array_pop(x.array))) { - apr_table_unset(headers, *name); + int i; + for (i = 0; i < x.array->nelts; i++) { + const char *name = APR_ARRAY_IDX(x.array, i, const char *); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO() + "Removing header '%s' listed in Connection header", + name); + if (!strcasecmp(name, "close")) { + closed = 1; + } + apr_table_unset(headers, name); } } - return x.closed; + return closed; } PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p, @@ -3095,7 +3086,9 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p, * apr is compiled with APR_POOL_DEBUG. */ headers_in_copy = apr_table_copy(r->pool, r->headers_in); - ap_proxy_clear_connection(r, headers_in_copy); + if (ap_proxy_clear_connection(r, headers_in_copy) < 0) { + return HTTP_BAD_REQUEST; + } /* send request headers */ headers_in_array = apr_table_elts(headers_in_copy); headers_in = (const apr_table_entry_t *) headers_in_array->elts; diff --git a/server/util.c b/server/util.c index e0ba5c2..541c9f0 100644 --- a/server/util.c +++ b/server/util.c @@ -1449,6 +1449,95 @@ AP_DECLARE(int) ap_find_etag_weak(apr_pool_t *p, const char *line, return find_list_item(p, line, tok, AP_ETAG_WEAK); } +/* Grab a list of tokens of the format 1#token (from RFC7230) */ +AP_DECLARE(const char *) ap_parse_token_list_strict(apr_pool_t *p, + const char *str_in, + apr_array_header_t **tokens, + int skip_invalid) +{ + int in_leading_space = 1; + int in_trailing_space = 0; + int string_end = 0; + const char *tok_begin; + const char *cur; + + if (!str_in) { + return NULL; + } + + tok_begin = cur = str_in; + + while (!string_end) { + const unsigned char c = (unsigned char)*cur; + + if (!TEST_CHAR(c, T_HTTP_TOKEN_STOP) && c != '\0') { + /* Non-separator character; we are finished with leading + * whitespace. We must never have encountered any trailing + * whitespace before the delimiter (comma) */ + in_leading_space = 0; + if (in_trailing_space) { + return "Encountered illegal whitespace in token"; + } + } + else if (c == ' ' || c == '\t') { + /* "Linear whitespace" only includes ASCII CRLF, space, and tab; + * we can't get a CRLF since headers are split on them already, + * so only look for a space or a tab */ + if (in_leading_space) { + /* We're still in leading whitespace */ + ++tok_begin; + } + else { + /* We must be in trailing whitespace */ + ++in_trailing_space; + } + } + else if (c == ',' || c == '\0') { + if (!in_leading_space) { + /* If we're out of the leading space, we know we've read some + * characters of a token */ + if (*tokens == NULL) { + *tokens = apr_array_make(p, 4, sizeof(char *)); + } + APR_ARRAY_PUSH(*tokens, char *) = + apr_pstrmemdup((*tokens)->pool, tok_begin, + (cur - tok_begin) - in_trailing_space); + } + /* We're allowed to have null elements, just don't add them to the + * array */ + + tok_begin = cur + 1; + in_leading_space = 1; + in_trailing_space = 0; + string_end = (c == '\0'); + } + else { + /* Encountered illegal separator char */ + if (skip_invalid) { + /* Skip to the next separator */ + const char *temp; + temp = ap_strchr_c(cur, ','); + if(!temp) { + temp = ap_strchr_c(cur, '\0'); + } + + /* Act like we haven't seen a token so we reset */ + cur = temp - 1; + in_leading_space = 1; + in_trailing_space = 0; + } + else { + return apr_psprintf(p, "Encountered illegal separator " + "'\\x%.2x'", (unsigned int)c); + } + } + + ++cur; + } + + return NULL; +} + /* Retrieve a token, spacing over it and returning a pointer to * the first non-white byte afterwards. Note that these tokens * are delimited by semis and commas; and can also be delimited --