File httpd-2.4.x-bnc887767-CVE-2014-0117-mod_proxy_crash.diff of Package apache2.openSUSE_13.1_Update
diff -rNU 30 ../httpd-2.4.6-o/include/httpd.h ./include/httpd.h
--- ../httpd-2.4.6-o/include/httpd.h 2014-07-29 17:46:18.000000000 +0200
+++ ./include/httpd.h 2014-07-29 17:49:12.000000000 +0200
@@ -1509,60 +1509,77 @@
/**
* Find an item in canonical form (lowercase, no extra spaces) within
* an HTTP field value list.
* @param p The pool to allocate from
* @param line The field value list to search
* @param tok The token to search for
* @return 1 if found, 0 if not found.
*/
AP_DECLARE(int) ap_find_list_item(apr_pool_t *p, const char *line, const char *tok);
/**
* Do a weak ETag comparison within an HTTP field value list.
* @param p The pool to allocate from
* @param line The field value list to search
* @param tok The token to search for
* @return 1 if found, 0 if not found.
*/
AP_DECLARE(int) ap_find_etag_weak(apr_pool_t *p, const char *line, const char *tok);
/**
* Do a strong ETag comparison within an HTTP field value list.
* @param p The pool to allocate from
* @param line The field value list to search
* @param tok The token to search for
* @return 1 if found, 0 if not found.
*/
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
* by whitespace at the caller's option.
* @param p The pool to allocate from
* @param accept_line The line to retrieve the token from (adjusted afterwards)
* @param accept_white Is it delimited by whitespace
* @return the token
*/
AP_DECLARE(char *) ap_get_token(apr_pool_t *p, const char **accept_line, int accept_white);
/**
* Find http tokens, see the definition of token from RFC2068
* @param p The pool to allocate from
* @param line The line to find the token
* @param tok The token to find
* @return 1 if the token is found, 0 otherwise
*/
AP_DECLARE(int) ap_find_token(apr_pool_t *p, const char *line, const char *tok);
/**
* find http tokens from the end of the line
* @param p The pool to allocate from
* @param line The line to find the token
* @param tok The token to find
* @return 1 if the token is found, 0 otherwise
*/
AP_DECLARE(int) ap_find_last_token(apr_pool_t *p, const char *line, const char *tok);
/**
diff -rNU 30 ../httpd-2.4.6-o/modules/proxy/mod_proxy_http.c ./modules/proxy/mod_proxy_http.c
--- ../httpd-2.4.6-o/modules/proxy/mod_proxy_http.c 2013-07-11 14:21:19.000000000 +0200
+++ ./modules/proxy/mod_proxy_http.c 2014-07-29 17:49:12.000000000 +0200
@@ -1335,60 +1335,61 @@
if (eos == APR_BRIGADE_SENTINEL(bb)) {
APR_BRIGADE_INSERT_TAIL(bb, e);
}
else {
APR_BUCKET_INSERT_BEFORE(eos, e);
}
ap_pass_brigade(r->output_filters, bb);
/* Mark the backend connection for closing */
backend->close = 1;
/* Need to return OK to avoid sending an error message */
return OK;
}
else if (!c->keepalives) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01105)
"NOT Closing connection to client"
" although reading from backend server %s:%d"
" failed.",
backend->hostname, backend->port);
}
return ap_proxyerror(r, HTTP_BAD_GATEWAY,
"Error reading from remote server");
}
/* XXX: Is this a real headers length send from remote? */
backend->worker->s->read += len;
/* Is it an HTTP/1 response?
* This is buggy if we ever see an HTTP/1.10
*/
if (apr_date_checkmask(buffer, "HTTP/#.# ###*")) {
int major, minor;
+ int toclose;
major = buffer[5] - '0';
minor = buffer[7] - '0';
/* If not an HTTP/1 message or
* if the status line was > 8192 bytes
*/
if ((major != 1) || (len >= sizeof(buffer)-1)) {
return ap_proxyerror(r, HTTP_BAD_GATEWAY,
apr_pstrcat(p, "Corrupt status line returned by remote "
"server: ", buffer, NULL));
}
backasswards = 0;
keepchar = buffer[12];
buffer[12] = '\0';
proxy_status = atoi(&buffer[9]);
apr_table_setn(r->notes, "proxy-status",
apr_pstrdup(r->pool, &buffer[9]));
if (keepchar != '\0') {
buffer[12] = keepchar;
} else {
/* 2616 requires the space in Status-Line; the origin
* server may have sent one but ap_rgetline_core will
* have stripped it. */
buffer[12] = ' ';
buffer[13] = '\0';
}
proxy_status_line = apr_pstrdup(p, &buffer[9]);
@@ -1443,61 +1444,66 @@
save_table);
}
/* can't have both Content-Length and Transfer-Encoding */
if (apr_table_get(r->headers_out, "Transfer-Encoding")
&& apr_table_get(r->headers_out, "Content-Length")) {
/*
* 2616 section 4.4, point 3: "if both Transfer-Encoding
* and Content-Length are received, the latter MUST be
* ignored";
*
* To help mitigate HTTP Splitting, unset Content-Length
* and shut down the backend server connection
* XXX: We aught to treat such a response as uncachable
*/
apr_table_unset(r->headers_out, "Content-Length");
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01107)
"server %s:%d returned Transfer-Encoding"
" and Content-Length",
backend->hostname, backend->port);
backend->close = 1;
}
/*
* Save a possible Transfer-Encoding header as we need it later for
* ap_http_filter to know where to end.
*/
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));
}
if (!ap_is_HTTP_INFO(proxy_status)) {
ap_proxy_pre_http_request(origin, backend->r);
}
/* Clear hop-by-hop headers */
for (i=0; hop_by_hop_hdrs[i]; ++i) {
apr_table_unset(r->headers_out, hop_by_hop_hdrs[i]);
}
/* Delete warnings with wrong date */
r->headers_out = ap_proxy_clean_warnings(p, r->headers_out);
/* handle Via header in response */
if (conf->viaopt != via_off && conf->viaopt != via_block) {
const char *server_name = ap_get_server_name(r);
/* If USE_CANONICAL_NAME_OFF was configured for the proxy virtual host,
* then the server name returned by ap_get_server_name() is the
* origin server name (which does make too much sense with Via: headers)
* so we use the proxy vhost's name instead.
*/
if (server_name == r->hostname)
server_name = r->server->server_hostname;
/* create a "Via:" response header entry and merge it */
apr_table_addn(r->headers_out, "Via",
(conf->viaopt == via_full)
? apr_psprintf(p, "%d.%d %s%s (%s)",
diff -rNU 30 ../httpd-2.4.6-o/modules/proxy/proxy_util.c ./modules/proxy/proxy_util.c
--- ../httpd-2.4.6-o/modules/proxy/proxy_util.c 2013-07-11 14:21:19.000000000 +0200
+++ ./modules/proxy/proxy_util.c 2014-07-29 17:49:12.000000000 +0200
@@ -2820,122 +2820,112 @@
return shm;
}
}
return NULL;
}
PROXY_DECLARE(proxy_balancer_shared *) ap_proxy_find_balancershm(ap_slotmem_provider_t *storage,
ap_slotmem_instance_t *slot,
proxy_balancer *balancer,
unsigned int *index)
{
proxy_balancer_shared *shm;
unsigned int i, limit;
limit = storage->num_slots(slot);
for (i = 0; i < limit; i++) {
if (storage->dptr(slot, i, (void *)&shm) != APR_SUCCESS) {
return NULL;
}
if ((balancer->s->hash.def == shm->hash.def) &&
(balancer->s->hash.fnv == shm->hash.fnv)) {
*index = i;
return shm;
}
}
return NULL;
}
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++;
- }
- 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,
apr_bucket_brigade *header_brigade,
request_rec *r,
proxy_conn_rec *p_conn,
proxy_worker *worker,
proxy_server_conf *conf,
apr_uri_t *uri,
char *url, char *server_portstr,
char **old_cl_val,
char **old_te_val)
{
conn_rec *c = r->connection;
int counter;
char *buf;
const apr_array_header_t *headers_in_array;
const apr_table_entry_t *headers_in;
apr_table_t *headers_in_copy;
apr_bucket *e;
int do_100_continue;
conn_rec *origin = p_conn->connection;
proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
/*
* To be compliant, we only use 100-Continue for requests with bodies.
* We also make sure we won't be talking HTTP/1.0 as well.
*/
do_100_continue = (worker->s->ping_timeout_set
&& ap_request_has_body(r)
@@ -3068,61 +3058,63 @@
/* Add X-Forwarded-Host: so that upstream knows what the
* original request hostname was.
*/
if ((buf = apr_table_get(r->headers_in, "Host"))) {
apr_table_mergen(r->headers_in, "X-Forwarded-Host", buf);
}
/* Add X-Forwarded-Server: so that upstream knows what the
* name of this proxy server is (if there are more than one)
* XXX: This duplicates Via: - do we strictly need it?
*/
apr_table_mergen(r->headers_in, "X-Forwarded-Server",
r->server->server_hostname);
}
}
proxy_run_fixups(r);
/*
* Make a copy of the headers_in table before clearing the connection
* headers as we need the connection headers later in the http output
* filter to prepare the correct response headers.
*
* Note: We need to take r->pool for apr_table_copy as the key / value
* pairs in r->headers_in have been created out of r->pool and
* p might be (and actually is) a longer living pool.
* This would trigger the bad pool ancestry abort in apr_table_copy if
* 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;
for (counter = 0; counter < headers_in_array->nelts; counter++) {
if (headers_in[counter].key == NULL
|| headers_in[counter].val == NULL
/* Already sent */
|| !strcasecmp(headers_in[counter].key, "Host")
/* Clear out hop-by-hop request headers not to send
* RFC2616 13.5.1 says we should strip these headers
*/
|| !strcasecmp(headers_in[counter].key, "Keep-Alive")
|| !strcasecmp(headers_in[counter].key, "TE")
|| !strcasecmp(headers_in[counter].key, "Trailer")
|| !strcasecmp(headers_in[counter].key, "Upgrade")
) {
continue;
}
/* Do we want to strip Proxy-Authorization ?
* If we haven't used it, then NO
* If we have used it then MAYBE: RFC2616 says we MAY propagate it.
* So let's make it configurable by env.
*/
if (!strcasecmp(headers_in[counter].key,"Proxy-Authorization")) {
if (r->user != NULL) { /* we've authenticated */
if (!apr_table_get(r->subprocess_env, "Proxy-Chain-Auth")) {
continue;
diff -rNU 30 ../httpd-2.4.6-o/server/util.c ./server/util.c
--- ../httpd-2.4.6-o/server/util.c 2013-05-28 23:17:53.000000000 +0200
+++ ./server/util.c 2014-07-29 17:52:58.000000000 +0200
@@ -1422,60 +1422,151 @@
/* Find an item in canonical form (lowercase, no extra spaces) within
* an HTTP field value list. Returns 1 if found, 0 if not found.
* This would be much more efficient if we stored header fields as
* an array of list items as they are received instead of a plain string.
*/
AP_DECLARE(int) ap_find_list_item(apr_pool_t *p, const char *line,
const char *tok)
{
return find_list_item(p, line, tok, AP_ETAG_NONE);
}
/* Find a strong Etag in canonical form (lowercase, no extra spaces) within
* an HTTP field value list. Returns 1 if found, 0 if not found.
*/
AP_DECLARE(int) ap_find_etag_strong(apr_pool_t *p, const char *line,
const char *tok)
{
return find_list_item(p, line, tok, AP_ETAG_STRONG);
}
/* Find a weak ETag in canonical form (lowercase, no extra spaces) within
* an HTTP field value list. Returns 1 if found, 0 if not found.
*/
AP_DECLARE(int) ap_find_etag_weak(apr_pool_t *p, const char *line,
const char *tok)
{
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
* by whitespace at the caller's option.
*/
AP_DECLARE(char *) ap_get_token(apr_pool_t *p, const char **accept_line,
int accept_white)
{
const char *ptr = *accept_line;
const char *tok_start;
char *token;
int tok_len;
/* Find first non-white byte */
while (apr_isspace(*ptr))
++ptr;
tok_start = ptr;
/* find token end, skipping over quoted strings.
* (comments are already gone).
*/
while (*ptr && (accept_white || !apr_isspace(*ptr))
&& *ptr != ';' && *ptr != ',') {
if (*ptr++ == '"')
while (*ptr)
if (*ptr++ == '"')