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)