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++ == '"')
openSUSE Build Service is sponsored by