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)