summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorJim Jagielski <jim@apache.org>2011-09-09 14:07:38 +0000
committerJim Jagielski <jim@apache.org>2011-09-09 14:07:38 +0000
commite233227eda5ef73fd76a8823018e8895c5d31f43 (patch)
tree5a23faebe3d030adcc9a34a37c0791249ad129ca /modules
parentea619f7eccf37e2625bf1853c5b565cdc292109b (diff)
downloadhttpd-e233227eda5ef73fd76a8823018e8895c5d31f43.tar.gz
CVE-2011-3192
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.0.x@1167184 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'modules')
-rw-r--r--modules/http/http_protocol.c468
1 files changed, 314 insertions, 154 deletions
diff --git a/modules/http/http_protocol.c b/modules/http/http_protocol.c
index 163a909154..6ba9adb78f 100644
--- a/modules/http/http_protocol.c
+++ b/modules/http/http_protocol.c
@@ -58,6 +58,10 @@
#include <unistd.h>
#endif
+#ifndef AP_DEFAULT_MAX_RANGES
+#define AP_DEFAULT_MAX_RANGES 200
+#endif
+
/* New Apache routine to map status codes into array indicies
* e.g. 100 -> 0, 101 -> 1, 200 -> 2 ...
* The number of status lines must equal the value of RESPONSE_CODES (httpd.h)
@@ -2893,55 +2897,8 @@ AP_DECLARE(void) ap_set_etag(request_rec *r)
apr_table_setn(r->headers_out, "ETag", etag);
}
-static int parse_byterange(char *range, apr_off_t clength,
- apr_off_t *start, apr_off_t *end)
-{
- char *dash = strchr(range, '-');
-
- if (!dash) {
- return 0;
- }
-
- if ((dash == range)) {
- /* In the form "-5" */
- *start = clength - apr_atoi64(dash + 1);
- *end = clength - 1;
- }
- else {
- *dash = '\0';
- dash++;
- *start = apr_atoi64(range);
- if (*dash) {
- *end = apr_atoi64(dash);
- }
- else { /* "5-" */
- *end = clength - 1;
- }
- }
-
- if (*start < 0) {
- *start = 0;
- }
-
- if (*end >= clength) {
- *end = clength - 1;
- }
-
- if (*start > *end) {
- return -1;
- }
-
- return (*start > 0 || *end < clength);
-}
-
-static int ap_set_byterange(request_rec *r);
-
-typedef struct byterange_ctx {
- apr_bucket_brigade *bb;
- int num_ranges;
- char *boundary;
- char *bound_head;
-} byterange_ctx;
+static int ap_set_byterange(request_rec *r, apr_off_t clength,
+ apr_array_header_t **indexes);
/*
* Here we try to be compatible with clients that want multipart/x-byteranges
@@ -2959,27 +2916,150 @@ static int use_range_x(request_rec *r)
}
#define BYTERANGE_FMT "%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT "/%" APR_OFF_T_FMT
-#define PARTITION_ERR_FMT "apr_brigade_partition() failed " \
- "[%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT "]"
+
+static apr_status_t copy_brigade_range(apr_bucket_brigade *bb,
+ apr_bucket_brigade *bbout,
+ apr_off_t start,
+ apr_off_t end)
+{
+ apr_bucket *first = NULL, *last = NULL, *out_first = NULL, *e;
+ apr_uint64_t pos = 0, off_first = 0, off_last = 0;
+ apr_status_t rv;
+ apr_uint64_t start64, end64;
+ apr_off_t pofft = 0;
+
+ /*
+ * Once we know that start and end are >= 0 convert everything to apr_uint64_t.
+ * See the comments in apr_brigade_partition why.
+ * In short apr_off_t (for values >= 0)and apr_size_t fit into apr_uint64_t.
+ */
+ start64 = (apr_uint64_t)start;
+ end64 = (apr_uint64_t)end;
+
+ if (start < 0 || end < 0 || start64 > end64)
+ return APR_EINVAL;
+
+ for (e = APR_BRIGADE_FIRST(bb);
+ e != APR_BRIGADE_SENTINEL(bb);
+ e = APR_BUCKET_NEXT(e))
+ {
+ apr_uint64_t elen64;
+ /* we know that no bucket has undefined length (-1) */
+ AP_DEBUG_ASSERT(e->length != (apr_size_t)(-1));
+ elen64 = (apr_uint64_t)e->length;
+ if (!first && (elen64 + pos > start64)) {
+ first = e;
+ off_first = pos;
+ }
+ if (elen64 + pos > end64) {
+ last = e;
+ off_last = pos;
+ break;
+ }
+ pos += elen64;
+ }
+ if (!first || !last)
+ return APR_EINVAL;
+
+ e = first;
+ while (1)
+ {
+ apr_bucket *copy;
+ AP_DEBUG_ASSERT(e != APR_BRIGADE_SENTINEL(bb));
+ rv = apr_bucket_copy(e, &copy);
+ if (rv != APR_SUCCESS) {
+ apr_brigade_cleanup(bbout);
+ return rv;
+ }
+
+ APR_BRIGADE_INSERT_TAIL(bbout, copy);
+ if (e == first) {
+ if (off_first != start64) {
+ rv = apr_bucket_split(copy, (apr_size_t)(start64 - off_first));
+ if (rv != APR_SUCCESS) {
+ apr_brigade_cleanup(bbout);
+ return rv;
+ }
+ out_first = APR_BUCKET_NEXT(copy);
+ APR_BUCKET_REMOVE(copy);
+ apr_bucket_destroy(copy);
+ }
+ else {
+ out_first = copy;
+ }
+ }
+ if (e == last) {
+ if (e == first) {
+ off_last += start64 - off_first;
+ copy = out_first;
+ }
+ if (end64 - off_last != (apr_uint64_t)e->length) {
+ rv = apr_bucket_split(copy, (apr_size_t)(end64 + 1 - off_last));
+ if (rv != APR_SUCCESS) {
+ apr_brigade_cleanup(bbout);
+ return rv;
+ }
+ copy = APR_BUCKET_NEXT(copy);
+ if (copy != APR_BRIGADE_SENTINEL(bbout)) {
+ APR_BUCKET_REMOVE(copy);
+ apr_bucket_destroy(copy);
+ }
+ }
+ break;
+ }
+ e = APR_BUCKET_NEXT(e);
+ }
+
+ AP_DEBUG_ASSERT(APR_SUCCESS == apr_brigade_length(bbout, 1, &pofft));
+ pos = (apr_uint64_t)pofft;
+ AP_DEBUG_ASSERT(pos == end64 - start64 + 1);
+ return APR_SUCCESS;
+}
+
+typedef struct indexes_t {
+ apr_off_t start;
+ apr_off_t end;
+} indexes_t;
+
+static apr_status_t send_416(ap_filter_t *f, apr_bucket_brigade *tmpbb)
+{
+ apr_bucket *e;
+ conn_rec *c = f->r->connection;
+ ap_remove_output_filter(f);
+ f->r->status = HTTP_OK;
+ e = ap_bucket_error_create(HTTP_RANGE_NOT_SATISFIABLE, NULL,
+ f->r->pool, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(tmpbb, e);
+ e = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(tmpbb, e);
+ return ap_pass_brigade(f->next, tmpbb);
+}
AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f,
apr_bucket_brigade *bb)
{
-#define MIN_LENGTH(len1, len2) ((len1 > len2) ? len2 : len1)
request_rec *r = f->r;
conn_rec *c = r->connection;
- byterange_ctx *ctx;
apr_bucket *e;
apr_bucket_brigade *bsend;
+ apr_bucket_brigade *tmpbb;
apr_off_t range_start;
apr_off_t range_end;
- char *current;
apr_off_t clength = 0;
apr_status_t rv;
int found = 0;
+ int num_ranges;
+ char *boundary = NULL;
+ char *bound_head = NULL;
+ apr_array_header_t *indexes;
+ indexes_t *idx;
+ int original_status;
+ int i;
- /* Iterate through the brigade until reaching EOS or a bucket with
- * unknown length. */
+ /*
+ * Iterate through the brigade until reaching EOS or a bucket with
+ * unknown length.
+ */
for (e = APR_BRIGADE_FIRST(bb);
(e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e)
&& e->length != (apr_size_t)-1);
@@ -2987,84 +3067,75 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f,
clength += e->length;
}
- /* Don't attempt to do byte range work if this brigade doesn't
+ /*
+ * Don't attempt to do byte range work if this brigade doesn't
* contain an EOS, or if any of the buckets has an unknown length;
* this avoids the cases where it is expensive to perform
- * byteranging (i.e. may require arbitrary amounts of memory). */
+ * byteranging (i.e. may require arbitrary amounts of memory).
+ */
if (!APR_BUCKET_IS_EOS(e) || clength <= 0) {
ap_remove_output_filter(f);
return ap_pass_brigade(f->next, bb);
}
- {
- int num_ranges = ap_set_byterange(r);
+ original_status = r->status;
+ num_ranges = ap_set_byterange(r, clength, &indexes);
- /* We have nothing to do, get out of the way. */
- if (num_ranges == 0) {
- ap_remove_output_filter(f);
- return ap_pass_brigade(f->next, bb);
- }
-
- ctx = apr_pcalloc(r->pool, sizeof(*ctx));
- ctx->num_ranges = num_ranges;
- /* create a brigade in case we never call ap_save_brigade() */
- ctx->bb = apr_brigade_create(r->pool, c->bucket_alloc);
-
- if (ctx->num_ranges > 1) {
- /* Is ap_make_content_type required here? */
- const char *orig_ct = ap_make_content_type(r, r->content_type);
- /* need APR_TIME_T_FMT_HEX */
- ctx->boundary = apr_psprintf(r->pool, "%qx%lx",
- r->request_time, (long) getpid());
-
- ap_set_content_type(r, apr_pstrcat(r->pool, "multipart",
- use_range_x(r) ? "/x-" : "/",
- "byteranges; boundary=",
- ctx->boundary, NULL));
-
- ctx->bound_head = apr_pstrcat(r->pool,
- CRLF "--", ctx->boundary,
- CRLF "Content-type: ",
- orig_ct,
- CRLF "Content-range: bytes ",
- NULL);
- ap_xlate_proto_to_ascii(ctx->bound_head, strlen(ctx->bound_head));
- }
+ /* We have nothing to do, get out of the way. */
+ if (num_ranges == 0 || (AP_DEFAULT_MAX_RANGES >= 0 && num_ranges > AP_DEFAULT_MAX_RANGES)) {
+ r->status = original_status;
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, bb);
}
/* this brigade holds what we will be sending */
bsend = apr_brigade_create(r->pool, c->bucket_alloc);
- while ((current = ap_getword(r->pool, &r->range, ','))
- && (rv = parse_byterange(current, clength, &range_start,
- &range_end))) {
- apr_bucket *e2;
- apr_bucket *ec;
+ if (num_ranges < 0)
+ return send_416(f, bsend);
+
+ if (num_ranges > 1) {
+ /* Is ap_make_content_type required here? */
+ const char *orig_ct = ap_make_content_type(r, r->content_type);
+ boundary = apr_psprintf(r->pool, "%" APR_UINT64_T_HEX_FMT "%lx",
+ (apr_uint64_t)r->request_time, c->id);
+
+ ap_set_content_type(r, apr_pstrcat(r->pool, "multipart",
+ use_range_x(r) ? "/x-" : "/",
+ "byteranges; boundary=",
+ boundary, NULL));
+
+ bound_head = apr_pstrcat(r->pool,
+ CRLF "--", boundary,
+ CRLF "Content-type: ",
+ orig_ct,
+ CRLF "Content-range: bytes ",
+ NULL);
+ ap_xlate_proto_to_ascii(bound_head, strlen(bound_head));
+ }
+
+ tmpbb = apr_brigade_create(r->pool, c->bucket_alloc);
- if (rv == -1) {
- continue;
- }
+ idx = (indexes_t *)indexes->elts;
+ for (i = 0; i < indexes->nelts; i++, idx++) {
+ range_start = idx->start;
+ range_end = idx->end;
- /* these calls to apr_brigade_partition() should theoretically
- * never fail because of the above call to apr_brigade_length(),
- * but what the heck, we'll check for an error anyway */
- if ((rv = apr_brigade_partition(bb, range_start, &ec)) != APR_SUCCESS) {
+ rv = copy_brigade_range(bb, tmpbb, range_start, range_end);
+ if (rv != APR_SUCCESS ) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
- PARTITION_ERR_FMT, range_start, clength);
+ "copy_brigade_range() failed [%" APR_OFF_T_FMT
+ "-%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT "]",
+ range_start, range_end, clength);
continue;
}
- if ((rv = apr_brigade_partition(bb, range_end+1, &e2)) != APR_SUCCESS) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
- PARTITION_ERR_FMT, range_end+1, clength);
- continue;
- }
-
found = 1;
- /* For single range requests, we must produce Content-Range header.
+ /*
+ * For single range requests, we must produce Content-Range header.
* Otherwise, we need to produce the multipart boundaries.
*/
- if (ctx->num_ranges == 1) {
+ if (num_ranges == 1) {
apr_table_setn(r->headers_out, "Content-Range",
apr_psprintf(r->pool, "bytes " BYTERANGE_FMT,
range_start, range_end, clength));
@@ -3072,7 +3143,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f,
else {
char *ts;
- e = apr_bucket_pool_create(ctx->bound_head, strlen(ctx->bound_head),
+ e = apr_bucket_pool_create(bound_head, strlen(bound_head),
r->pool, c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bsend, e);
@@ -3084,44 +3155,31 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f,
APR_BRIGADE_INSERT_TAIL(bsend, e);
}
- do {
- apr_bucket *foo;
- const char *str;
- apr_size_t len;
-
- if (apr_bucket_copy(ec, &foo) != APR_SUCCESS) {
- /* this shouldn't ever happen due to the call to
- * apr_brigade_length() above which normalizes
- * indeterminate-length buckets. just to be sure,
- * though, this takes care of uncopyable buckets that
- * do somehow manage to slip through.
- */
- /* XXX: check for failure? */
- apr_bucket_read(ec, &str, &len, APR_BLOCK_READ);
- apr_bucket_copy(ec, &foo);
- }
- APR_BRIGADE_INSERT_TAIL(bsend, foo);
- ec = APR_BUCKET_NEXT(ec);
- } while (ec != e2);
+ APR_BRIGADE_CONCAT(bsend, tmpbb);
+ if (i && !(i & 0x1F)) {
+ /*
+ * Every now and then, pass what we have down the filter chain.
+ * In this case, the content-length filter cannot calculate and
+ * set the content length and we must remove any Content-Length
+ * header already present.
+ */
+ apr_table_unset(r->headers_out, "Content-Length");
+ if ((rv = ap_pass_brigade(f->next, bsend)) != APR_SUCCESS)
+ return rv;
+ apr_brigade_cleanup(bsend);
+ }
}
if (found == 0) {
- ap_remove_output_filter(f);
- r->status = HTTP_OK;
/* bsend is assumed to be empty if we get here. */
- e = ap_bucket_error_create(HTTP_RANGE_NOT_SATISFIABLE, NULL,
- r->pool, c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bsend, e);
- e = apr_bucket_eos_create(c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bsend, e);
- return ap_pass_brigade(f->next, bsend);
+ return send_416(f, bsend);
}
- if (ctx->num_ranges > 1) {
+ if (num_ranges > 1) {
char *end;
/* add the final boundary */
- end = apr_pstrcat(r->pool, CRLF "--", ctx->boundary, "--" CRLF, NULL);
+ end = apr_pstrcat(r->pool, CRLF "--", boundary, "--" CRLF, NULL);
ap_xlate_proto_to_ascii(end, strlen(end));
e = apr_bucket_pool_create(end, strlen(end), r->pool, c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bsend, e);
@@ -3131,25 +3189,49 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f,
APR_BRIGADE_INSERT_TAIL(bsend, e);
/* we're done with the original content - all of our data is in bsend. */
- apr_brigade_destroy(bb);
+ apr_brigade_cleanup(bb);
+ apr_brigade_destroy(tmpbb);
/* send our multipart output */
return ap_pass_brigade(f->next, bsend);
}
-static int ap_set_byterange(request_rec *r)
+/* for consistency with 2.2 code which uses apr_strtoff()
+ * (missing from apr 0.9)
+ */
+static apr_status_t strtoff(apr_off_t *offset, const char *nptr,
+ char **endptr, int base)
+{
+ errno = 0;
+ if (sizeof(apr_off_t) == 4) {
+ *offset = strtol(nptr, endptr, base);
+ }
+ else {
+ *offset = apr_strtoi64(nptr, endptr, base);
+ }
+ return APR_FROM_OS_ERROR(errno);
+}
+
+static int ap_set_byterange(request_rec *r, apr_off_t clength,
+ apr_array_header_t **indexes)
{
const char *range;
const char *if_range;
const char *match;
const char *ct;
- int num_ranges;
+ char *cur;
+ int num_ranges = 0, unsatisfiable = 0;
+ apr_off_t sum_lengths = 0;
+ indexes_t *idx;
+ int ranges = 1;
+ const char *it;
if (r->assbackwards) {
return 0;
}
- /* Check for Range request-header (HTTP/1.1) or Request-Range for
+ /*
+ * Check for Range request-header (HTTP/1.1) or Request-Range for
* backwards-compatibility with second-draft Luotonen/Franks
* byte-ranges (e.g. Netscape Navigator 2-3).
*
@@ -3179,7 +3261,8 @@ static int ap_set_byterange(request_rec *r)
return 0;
}
- /* Check the If-Range header for Etag or Date.
+ /*
+ * Check the If-Range header for Etag or Date.
* Note that this check will return false (as required) if either
* of the two etags are weak.
*/
@@ -3196,17 +3279,94 @@ static int ap_set_byterange(request_rec *r)
}
}
- if (!ap_strchr_c(range, ',')) {
- /* a single range */
- num_ranges = 1;
+ range += 6;
+ it = range;
+ while (*it) {
+ if (*it++ == ',') {
+ ranges++;
+ }
}
- else {
- /* a multiple range */
- num_ranges = 2;
+ it = range;
+ *indexes = apr_array_make(r->pool, ranges, sizeof(indexes_t));
+ while ((cur = ap_getword(r->pool, &range, ','))) {
+ char *dash;
+ char *errp;
+ apr_off_t number, start, end;
+
+ if (!*cur)
+ break;
+
+ /*
+ * Per RFC 2616 14.35.1: If there is at least one syntactically invalid
+ * byte-range-spec, we must ignore the whole header.
+ */
+
+ if (!(dash = strchr(cur, '-'))) {
+ return 0;
+ }
+
+ if (dash == cur) {
+ /* In the form "-5" */
+ if (strtoff(&number, dash+1, &errp, 10) || *errp) {
+ return 0;
+ }
+ if (number < 1) {
+ return 0;
+ }
+ start = clength - number;
+ end = clength - 1;
+ }
+ else {
+ *dash++ = '\0';
+ if (strtoff(&number, cur, &errp, 10) || *errp) {
+ return 0;
+ }
+ start = number;
+ if (*dash) {
+ if (strtoff(&number, dash, &errp, 10) || *errp) {
+ return 0;
+ }
+ end = number;
+ if (start > end) {
+ return 0;
+ }
+ }
+ else { /* "5-" */
+ end = clength - 1;
+ }
+ }
+
+ if (start < 0) {
+ start = 0;
+ }
+ if (start >= clength) {
+ unsatisfiable = 1;
+ continue;
+ }
+ if (end >= clength) {
+ end = clength - 1;
+ }
+
+ idx = (indexes_t *)apr_array_push(*indexes);
+ idx->start = start;
+ idx->end = end;
+ sum_lengths += end - start + 1;
+ /* new set again */
+ num_ranges++;
+ }
+
+ if (num_ranges == 0 && unsatisfiable) {
+ /* If all ranges are unsatisfiable, we should return 416 */
+ return -1;
+ }
+ if (sum_lengths >= clength) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+ "Sum of ranges not smaller than file, ignoring.");
+ return 0;
}
r->status = HTTP_PARTIAL_CONTENT;
- r->range = range + 6;
+ r->range = it;
return num_ranges;
}