dgaudet     98/08/09 09:52:32

  Modified:    src      CHANGES
               src/main http_protocol.c
  Log:
  - fix ben's fix to roy's patch (sizeof(l) and sizeof(field) are meaningless)
  - put my qsort fix to get_mime_headers into the repository so I don't have
  to worry about someone else screwing around in the same routine.
  
  Revision  Changes    Path
  1.1013    +3 -0      apache-1.3/src/CHANGES
  
  Index: CHANGES
  ===================================================================
  RCS file: /export/home/cvs/apache-1.3/src/CHANGES,v
  retrieving revision 1.1012
  retrieving revision 1.1013
  diff -u -r1.1012 -r1.1013
  --- CHANGES   1998/08/09 06:37:12     1.1012
  +++ CHANGES   1998/08/09 16:52:29     1.1013
  @@ -1,5 +1,8 @@
   Changes with Apache 1.3.2
   
  +  *) SECURITY: Eliminate O(n^2) space DoS attacks (and other O(n^2)
  +     cpu time attacks) in header parsing.  [Dean Gaudet]
  +
     *) SECURITY: Added default limits for various aspects of reading a
        client request to avoid some simple denial of service attacks,
        including limits on maximum request-line size, number of header
  
  
  
  1.233     +139 -9    apache-1.3/src/main/http_protocol.c
  
  Index: http_protocol.c
  ===================================================================
  RCS file: /export/home/cvs/apache-1.3/src/main/http_protocol.c,v
  retrieving revision 1.232
  retrieving revision 1.233
  diff -u -r1.232 -r1.233
  --- http_protocol.c   1998/08/09 14:33:11     1.232
  +++ http_protocol.c   1998/08/09 16:52:31     1.233
  @@ -626,12 +626,18 @@
   
   static int read_request_line(request_rec *r)
   {
  -    char *l=alloca(r->server->limit_req_line + 2);
  -    const char *ll = l, *uri;
  +    char *l;
  +    const char *ll;
  +    const char *uri;
       conn_rec *conn = r->connection;
       int major = 1, minor = 0;   /* Assume HTTP/1.0 if non-"HTTP" protocol */
       int len;
  +    pool *tmp;
   
  +    tmp = ap_make_sub_pool(r->pool);
  +    l = ap_palloc(tmp, r->server->limit_req_line + 2);
  +    ll = l;
  +
       /* Read past empty lines until we get a real request line,
        * a read error, the connection closes (EOF), or we timeout.
        *
  @@ -647,9 +653,10 @@
        * have to block during a read.
        */
       ap_bsetflag(conn->client, B_SAFEREAD, 1);
  -    while ((len = getline(l, sizeof(l), conn->client, 0)) <= 0) {
  +    while ((len = getline(l, r->server->limit_req_line + 2, conn->client, 
0)) <= 0) {
           if ((len < 0) || ap_bgetflag(conn->client, B_EOF)) {
               ap_bsetflag(conn->client, B_SAFEREAD, 0);
  +         ap_destroy_pool(tmp);
               return 0;
           }
       }
  @@ -689,10 +696,11 @@
   
       ap_parse_uri(r, uri);
   
  -    if (len >= sizeof(l) - 1) {
  +    if (len >= r->server->limit_req_line - 1) {
           r->status    = HTTP_REQUEST_URI_TOO_LARGE;
           r->proto_num = HTTP_VERSION(1,0);
           r->protocol  = ap_pstrdup(r->pool, "HTTP/1.0");
  +     ap_destroy_pool(tmp);
           return 0;
       }
   
  @@ -705,34 +713,103 @@
       else
        r->proto_num = HTTP_VERSION(1,0);
   
  +    ap_destroy_pool(tmp);
       return 1;
   }
   
  +/* Curse libc and the fact that it doesn't guarantee a stable sort.  We
  + * have to enforce stability ourselves by using the order field. -djg
  + */
  +typedef struct {
  +    char *key;
  +    char *val;
  +    unsigned order;
  +} mime_key;
  +
  +static int sort_mime_headers(const void *va, const void *vb)
  +{
  +    const mime_key *a = va;
  +    const mime_key *b = vb;
  +    int r;
  +
  +    r = strcasecmp(a->key, b->key);
  +    if (r) {
  +     return r;
  +    }
  +    return (signed)a->order - (signed)b->order;
  +}
  +
   static void get_mime_headers(request_rec *r)
   {
       conn_rec *c = r->connection;
  -    char *value, *copy;
  +    char *copy;
       int len;
  +    char *value;
       unsigned int fields_read = 0;
  -    char *field=alloca(r->server->limit_req_fieldsize + 2);
  +    char *field;
  +    array_header *arr;
  +    pool *tmp;
  +    mime_key *new_key;
  +    unsigned order;
  +    mime_key *first;
  +    mime_key *last;
  +    mime_key *end;
  +    char *strp;
  +
  +    /* The array will store the headers in a way that we can merge them
  +     * later in O(n*lg(n))... rather than deal with various O(n^2)
  +     * operations.
  +     */
  +    tmp = ap_make_sub_pool(r->pool);
  +    arr = ap_make_array(tmp, 50, sizeof(mime_key));
  +    order = 0;
  +
  +    field = ap_palloc(tmp, r->server->limit_req_fieldsize + 2);
  +
  +    /* If headers_in is non-empty (i.e. we're parsing a trailer) then
  +     * we have to merge.  Have I mentioned that I think this is a lame part
  +     * of the HTTP standard?  Anyhow, we'll cheat, and just pre-seed our
  +     * array with the existing headers... and take advantage of the much
  +     * faster merging here. -djg
  +     */
  +    if (!ap_is_empty_table(r->headers_in)) {
  +     array_header *t_arr;
  +     table_entry *t;
  +     table_entry *t_end;
  +
  +     t_arr = ap_table_elts(r->headers_in);
  +     t = (table_entry *)t_arr->elts;
  +     t_end = t + t_arr->nelts;
  +     while (t < t_end) {
  +         new_key = ap_push_array(arr);
  +         new_key->order = order++;
  +         new_key->key = t->key;
  +         new_key->val = t->val;
  +         ++t;
  +     }
  +     ap_clear_table(r->headers_in);
  +    }
   
       /*
        * Read header lines until we get the empty separator line, a read error,
        * the connection closes (EOF), reach the server limit, or we timeout.
        */
  -    while ((len = getline(field, sizeof(field), c->client, 1)) > 0) {
  +    while ((len = getline(field, r->server->limit_req_fieldsize + 2,
  +                     c->client, 1)) > 0) {
   
           if (++fields_read > r->server->limit_req_fields) {
               r->status = HTTP_BAD_REQUEST;
               ap_table_setn(r->notes, "error-notes",
                   "Number of request header fields exceeds server 
limit.<P>\n");
  +         ap_destroy_pool(tmp);
               return;
           }
  -        if (len >= sizeof(field) - 1) { 
  +        if (len >= r->server->limit_req_fieldsize + 1) { 
               r->status = HTTP_BAD_REQUEST;
               ap_table_setn(r->notes, "error-notes", ap_pstrcat(r->pool,
                   "Size of a request header field exceeds server limit.<P>\n"
                   "<PRE>\n", field, "</PRE>\n", NULL));
  +         ap_destroy_pool(tmp);
               return;
           }
           copy = ap_palloc(r->pool, len + 1);
  @@ -743,6 +820,7 @@
               ap_table_setn(r->notes, "error-notes", ap_pstrcat(r->pool,
                   "Request header field is missing colon separator.<P>\n"
                   "<PRE>\n", copy, "</PRE>\n", NULL));
  +         ap_destroy_pool(tmp);
               return;
           }
   
  @@ -752,9 +830,61 @@
               ++value;            /* Skip to start of value   */
   
        /* XXX: should strip trailing whitespace as well */
  +
  +     /* Notice that key and val are actually in r->pool... this is a slight
  +      * optimization to handle the normal case, where we don't have twits
  +      * trying to exploit the server.  In the abnormal case where twits are
  +      * trying to exploit the server by causing it to do header merging
  +      * and other such nonsense we consume twice as much memory as we
  +      * could optimally.  Oh well.  -djg
  +      */
  +     new_key = ap_push_array(arr);
  +     new_key->order = order++;
  +     new_key->key = copy;
  +     new_key->val = value;
  +    }
  +
  +    /* Now we have to merge headers. */
  +    qsort(arr->elts, arr->nelts, sizeof(mime_key), sort_mime_headers);
   
  -        ap_table_mergen(r->headers_in, copy, value);
  +    /* Now iterate over the array and build r->headers_in. */
  +    first = (mime_key *)arr->elts;
  +    end = first + arr->nelts;
  +    while (first < end) {
  +     last = first + 1;
  +     if (last == end
  +         || strcasecmp(first->key, last->key)) {
  +         ap_table_addn(r->headers_in, first->key, first->val);
  +         first = last;
  +     }
  +     else {
  +         /* Have to merge some headers.  Let's re-use the order field,
  +          * since it's handy... we'll store the length of val there.
  +          */
  +         first->order = strlen(first->val);
  +         len = first->order;
  +         do {
  +             last->order = strlen(last->val);
  +             len += 2 + last->order;
  +             ++last;
  +         } while (last < end
  +                 && !strcasecmp(first->key, last->key));
  +         /* last points one past the last header to merge */
  +         value = ap_palloc(r->pool, len + 1);
  +         strp = value;
  +         for (;;) {
  +             memcpy(strp, first->val, first->order);
  +             strp += first->order;
  +             ++first;
  +             if (first == last) break;
  +             *strp++ = ',';
  +             *strp++ = ' ';
  +         }
  +         *strp = 0;
  +         ap_table_addn(r->headers_in, (first-1)->key, value);
  +     }
       }
  +    ap_destroy_pool(tmp);
   }
   
   request_rec *ap_read_request(conn_rec *conn)
  
  
  

Reply via email to