diff --git a/.gitignore b/.gitignore index 260a2d6..9969f1d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ SOURCES/httpd-2.4.6.tar.bz2 -SOURCES/centos-noindex.tar.gz diff --git a/.httpd.metadata b/.httpd.metadata index 17ede1b..d335a99 100644 --- a/.httpd.metadata +++ b/.httpd.metadata @@ -1,2 +1 @@ 16d8ec72535ded65d035122b0d944b0e64eaa2a2 SOURCES/httpd-2.4.6.tar.bz2 -6ce5ab3c765b9efeceb2e636e32373bc6e6ed489 SOURCES/centos-noindex.tar.gz diff --git a/SOURCES/httpd-2.4.6-CVE-2015-3183.patch b/SOURCES/httpd-2.4.6-CVE-2015-3183.patch new file mode 100644 index 0000000..da4d4fa --- /dev/null +++ b/SOURCES/httpd-2.4.6-CVE-2015-3183.patch @@ -0,0 +1,982 @@ +diff --git a/include/http_protocol.h b/include/http_protocol.h +index 415270b..67fa02f 100644 +--- a/include/http_protocol.h ++++ b/include/http_protocol.h +@@ -502,6 +502,23 @@ AP_DECLARE(int) ap_should_client_block(request_rec *r); + */ + AP_DECLARE(long) ap_get_client_block(request_rec *r, char *buffer, apr_size_t bufsiz); + ++/* ++ * Map specific APR codes returned by the filter stack to HTTP error ++ * codes, or the default status code provided. Use it as follows: ++ * ++ * return ap_map_http_request_error(rv, HTTP_BAD_REQUEST); ++ * ++ * If the filter has already handled the error, AP_FILTER_ERROR will ++ * be returned, which is cleanly passed through. ++ * ++ * These mappings imply that the filter stack is reading from the ++ * downstream client, the proxy will map these codes differently. ++ * @param rv APR status code ++ * @param status Default HTTP code should the APR code not be recognised ++ * @return Mapped HTTP status code ++ */ ++AP_DECLARE(int) ap_map_http_request_error(apr_status_t rv, int status); ++ + /** + * In HTTP/1.1, any method can have a body. However, most GET handlers + * wouldn't know what to do with a request body if they received one. +diff --git a/modules/http/http_filters.c b/modules/http/http_filters.c +index 1dde402..ed8749f 100644 +--- a/modules/http/http_filters.c ++++ b/modules/http/http_filters.c +@@ -57,24 +57,29 @@ + + APLOG_USE_MODULE(http); + +-#define INVALID_CHAR -2 +- +-static long get_chunk_size(char *); +- +-typedef struct http_filter_ctx { ++typedef struct http_filter_ctx ++{ + apr_off_t remaining; + apr_off_t limit; + apr_off_t limit_used; +- enum { +- BODY_NONE, +- BODY_LENGTH, +- BODY_CHUNK, +- BODY_CHUNK_PART ++ apr_int32_t chunk_used; ++ apr_int32_t chunk_bws; ++ apr_int32_t chunkbits; ++ enum ++ { ++ BODY_NONE, /* streamed data */ ++ BODY_LENGTH, /* data constrained by content length */ ++ BODY_CHUNK, /* chunk expected */ ++ BODY_CHUNK_PART, /* chunk digits */ ++ BODY_CHUNK_EXT, /* chunk extension */ ++ BODY_CHUNK_CR, /* got space(s) after digits, expect [CR]LF or ext */ ++ BODY_CHUNK_LF, /* got CR after digits or ext, expect LF */ ++ BODY_CHUNK_DATA, /* data constrained by chunked encoding */ ++ BODY_CHUNK_END, /* chunked data terminating CRLF */ ++ BODY_CHUNK_END_LF, /* got CR after data, expect LF */ ++ BODY_CHUNK_TRAILER /* trailers */ + } state; +- int eos_sent; +- char chunk_ln[32]; +- char *pos; +- apr_off_t linesize; ++ unsigned int eos_sent :1; + apr_bucket_brigade *bb; + } http_ctx_t; + +@@ -87,6 +92,23 @@ static apr_status_t bail_out_on_error(http_ctx_t *ctx, + apr_bucket_brigade *bb = ctx->bb; + + apr_brigade_cleanup(bb); ++ ++ if (f->r->proxyreq == PROXYREQ_RESPONSE) { ++ switch (http_error) { ++ case HTTP_REQUEST_ENTITY_TOO_LARGE: ++ return APR_ENOSPC; ++ ++ case HTTP_REQUEST_TIME_OUT: ++ return APR_INCOMPLETE; ++ ++ case HTTP_NOT_IMPLEMENTED: ++ return APR_ENOTIMPL; ++ ++ default: ++ return APR_EGENERAL; ++ } ++ } ++ + e = ap_bucket_error_create(http_error, + NULL, f->r->pool, + f->c->bucket_alloc); +@@ -102,117 +124,154 @@ static apr_status_t bail_out_on_error(http_ctx_t *ctx, + return ap_pass_brigade(f->r->output_filters, bb); + } + +-static apr_status_t get_remaining_chunk_line(http_ctx_t *ctx, +- apr_bucket_brigade *b, +- int linelimit) ++/** ++ * Parse a chunk line with optional extension, detect overflow. ++ * There are two error cases: ++ * 1) If the conversion would require too many bits, APR_EGENERAL is returned. ++ * 2) If the conversion used the correct number of bits, but an overflow ++ * caused only the sign bit to flip, then APR_ENOSPC is returned. ++ * In general, any negative number can be considered an overflow error. ++ */ ++static apr_status_t parse_chunk_size(http_ctx_t *ctx, const char *buffer, ++ apr_size_t len, int linelimit) + { +- apr_status_t rv; +- apr_off_t brigade_length; +- apr_bucket *e; +- const char *lineend; +- apr_size_t len = 0; ++ apr_size_t i = 0; + +- /* +- * As the brigade b should have been requested in mode AP_MODE_GETLINE +- * all buckets in this brigade are already some type of memory +- * buckets (due to the needed scanning for LF in mode AP_MODE_GETLINE) +- * or META buckets. +- */ +- rv = apr_brigade_length(b, 0, &brigade_length); +- if (rv != APR_SUCCESS) { +- return rv; +- } +- /* Sanity check. Should never happen. See above. */ +- if (brigade_length == -1) { +- return APR_EGENERAL; +- } +- if (!brigade_length) { +- return APR_EAGAIN; +- } +- ctx->linesize += brigade_length; +- if (ctx->linesize > linelimit) { +- return APR_ENOSPC; +- } +- /* +- * As all buckets are already some type of memory buckets or META buckets +- * (see above), we only need to check the last byte in the last data bucket. +- */ +- for (e = APR_BRIGADE_LAST(b); +- e != APR_BRIGADE_SENTINEL(b); +- e = APR_BUCKET_PREV(e)) { ++ while (i < len) { ++ char c = buffer[i]; ++ ++ ap_xlate_proto_from_ascii(&c, 1); + +- if (APR_BUCKET_IS_METADATA(e)) { ++ /* handle CRLF after the chunk */ ++ if (ctx->state == BODY_CHUNK_END ++ || ctx->state == BODY_CHUNK_END_LF) { ++ if (c == LF) { ++ ctx->state = BODY_CHUNK; ++ } ++ else if (c == CR && ctx->state == BODY_CHUNK_END) { ++ ctx->state = BODY_CHUNK_END_LF; ++ } ++ else { ++ /* ++ * LF expected. ++ */ ++ return APR_EINVAL; ++ } ++ i++; + continue; + } +- rv = apr_bucket_read(e, &lineend, &len, APR_BLOCK_READ); +- if (rv != APR_SUCCESS) { +- return rv; ++ ++ /* handle start of the chunk */ ++ if (ctx->state == BODY_CHUNK) { ++ if (!apr_isxdigit(c)) { ++ /* ++ * Detect invalid character at beginning. This also works for ++ * empty chunk size lines. ++ */ ++ return APR_EINVAL; ++ } ++ else { ++ ctx->state = BODY_CHUNK_PART; ++ } ++ ctx->remaining = 0; ++ ctx->chunkbits = sizeof(apr_off_t) * 8; ++ ctx->chunk_used = 0; ++ ctx->chunk_bws = 0; + } +- if (len > 0) { +- break; /* we got the data we want */ ++ ++ if (c == LF) { ++ if (ctx->remaining) { ++ ctx->state = BODY_CHUNK_DATA; ++ } ++ else { ++ ctx->state = BODY_CHUNK_TRAILER; ++ } + } +- /* If we got a zero-length data bucket, we try the next one */ +- } +- /* We had no data in this brigade */ +- if (!len || e == APR_BRIGADE_SENTINEL(b)) { +- return APR_EAGAIN; +- } +- if (lineend[len - 1] != APR_ASCII_LF) { +- return APR_EAGAIN; +- } +- /* Line is complete. So reset ctx for next round. */ +- ctx->linesize = 0; +- ctx->pos = ctx->chunk_ln; +- return APR_SUCCESS; +-} ++ else if (ctx->state == BODY_CHUNK_LF) { ++ /* ++ * LF expected. ++ */ ++ return APR_EINVAL; ++ } ++ else if (c == CR) { ++ ctx->state = BODY_CHUNK_LF; ++ } ++ else if (c == ';') { ++ ctx->state = BODY_CHUNK_EXT; ++ } ++ else if (ctx->state == BODY_CHUNK_EXT) { ++ /* ++ * Control chars (but tabs) are invalid. ++ */ ++ if (c != '\t' && apr_iscntrl(c)) { ++ return APR_EINVAL; ++ } ++ } ++ else if (c == ' ' || c == '\t') { ++ /* Be lenient up to 10 BWS (term from rfc7230 - 3.2.3). ++ */ ++ ctx->state = BODY_CHUNK_CR; ++ if (++ctx->chunk_bws > 10) { ++ return APR_EINVAL; ++ } ++ } ++ else if (ctx->state == BODY_CHUNK_CR) { ++ /* ++ * ';', CR or LF expected. ++ */ ++ return APR_EINVAL; ++ } ++ else if (ctx->state == BODY_CHUNK_PART) { ++ int xvalue; + +-static apr_status_t get_chunk_line(http_ctx_t *ctx, apr_bucket_brigade *b, +- int linelimit) +-{ +- apr_size_t len; +- int tmp_len; +- apr_status_t rv; ++ /* ignore leading zeros */ ++ if (!ctx->remaining && c == '0') { ++ i++; ++ continue; ++ } + +- tmp_len = sizeof(ctx->chunk_ln) - (ctx->pos - ctx->chunk_ln) - 1; +- /* Saveguard ourselves against underflows */ +- if (tmp_len < 0) { +- len = 0; +- } +- else { +- len = (apr_size_t) tmp_len; +- } +- /* +- * Check if there is space left in ctx->chunk_ln. If not, then either +- * the chunk size is insane or we have chunk-extensions. Ignore both +- * by discarding the remaining part of the line via +- * get_remaining_chunk_line. Only bail out if the line is too long. +- */ +- if (len > 0) { +- rv = apr_brigade_flatten(b, ctx->pos, &len); +- if (rv != APR_SUCCESS) { +- return rv; ++ ctx->chunkbits -= 4; ++ if (ctx->chunkbits < 0) { ++ /* overflow */ ++ return APR_ENOSPC; ++ } ++ ++ if (c >= '0' && c <= '9') { ++ xvalue = c - '0'; ++ } ++ else if (c >= 'A' && c <= 'F') { ++ xvalue = c - 'A' + 0xa; ++ } ++ else if (c >= 'a' && c <= 'f') { ++ xvalue = c - 'a' + 0xa; ++ } ++ else { ++ /* bogus character */ ++ return APR_EINVAL; ++ } ++ ++ ctx->remaining = (ctx->remaining << 4) | xvalue; ++ if (ctx->remaining < 0) { ++ /* overflow */ ++ return APR_ENOSPC; ++ } + } +- ctx->pos += len; +- ctx->linesize += len; +- *(ctx->pos) = '\0'; +- /* +- * Check if we really got a full line. If yes the +- * last char in the just read buffer must be LF. +- * If not advance the buffer and return APR_EAGAIN. +- * We do not start processing until we have the +- * full line. +- */ +- if (ctx->pos[-1] != APR_ASCII_LF) { +- /* Check if the remaining data in the brigade has the LF */ +- return get_remaining_chunk_line(ctx, b, linelimit); ++ else { ++ /* Should not happen */ ++ return APR_EGENERAL; + } +- /* Line is complete. So reset ctx->pos for next round. */ +- ctx->pos = ctx->chunk_ln; +- return APR_SUCCESS; ++ ++ i++; + } +- return get_remaining_chunk_line(ctx, b, linelimit); +-} + ++ /* sanity check */ ++ ctx->chunk_used += len; ++ if (ctx->chunk_used < 0 || ctx->chunk_used > linelimit) { ++ return APR_ENOSPC; ++ } ++ ++ return APR_SUCCESS; ++} + + static apr_status_t read_chunked_trailers(http_ctx_t *ctx, ap_filter_t *f, + apr_bucket_brigade *b, int merge) +@@ -226,7 +285,6 @@ static apr_status_t read_chunked_trailers(http_ctx_t *ctx, ap_filter_t *f, + r->status = HTTP_OK; + r->headers_in = r->trailers_in; + apr_table_clear(r->headers_in); +- ctx->state = BODY_NONE; + ap_get_mime_headers(r); + + if(r->status == HTTP_OK) { +@@ -239,7 +297,7 @@ static apr_status_t read_chunked_trailers(http_ctx_t *ctx, ap_filter_t *f, + else { + const char *error_notes = apr_table_get(r->notes, + "error-notes"); +- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, ++ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02656) + "Error while reading HTTP trailer: %i%s%s", + r->status, error_notes ? ": " : "", + error_notes ? error_notes : ""); +@@ -270,9 +328,9 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, + apr_bucket *e; + http_ctx_t *ctx = f->ctx; + apr_status_t rv; +- apr_off_t totalread; + int http_error = HTTP_REQUEST_ENTITY_TOO_LARGE; + apr_bucket_brigade *bb; ++ int again; + + conf = (core_server_config *) + ap_get_module_config(f->r->server->module_config, &core_module); +@@ -286,7 +344,6 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, + const char *tenc, *lenp; + f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx)); + ctx->state = BODY_NONE; +- ctx->pos = ctx->chunk_ln; + ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc); + bb = ctx->bb; + +@@ -306,25 +363,33 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, + lenp = apr_table_get(f->r->headers_in, "Content-Length"); + + if (tenc) { +- if (!strcasecmp(tenc, "chunked")) { ++ if (strcasecmp(tenc, "chunked") == 0 /* fast path */ ++ || ap_find_last_token(f->r->pool, tenc, "chunked")) { + ctx->state = BODY_CHUNK; + } +- /* test lenp, because it gives another case we can handle */ +- else if (!lenp) { +- /* Something that isn't in HTTP, unless some future ++ else if (f->r->proxyreq == PROXYREQ_RESPONSE) { ++ /* http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-23 ++ * Section 3.3.3.3: "If a Transfer-Encoding header field is ++ * present in a response and the chunked transfer coding is not ++ * the final encoding, the message body length is determined by ++ * reading the connection until it is closed by the server." ++ */ ++ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(02555) ++ "Unknown Transfer-Encoding: %s; " ++ "using read-until-close", tenc); ++ tenc = NULL; ++ } ++ else { ++ /* Something that isn't a HTTP request, unless some future + * edition defines new transfer encodings, is unsupported. + */ + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(01585) + "Unknown Transfer-Encoding: %s", tenc); +- return bail_out_on_error(ctx, f, HTTP_NOT_IMPLEMENTED); +- } +- else { +- ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, f->r, APLOGNO(01586) +- "Unknown Transfer-Encoding: %s; using Content-Length", tenc); +- tenc = NULL; ++ return bail_out_on_error(ctx, f, HTTP_BAD_REQUEST); + } ++ lenp = NULL; + } +- if (lenp && !tenc) { ++ if (lenp) { + char *endstr; + + ctx->state = BODY_LENGTH; +@@ -339,7 +404,7 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(01587) + "Invalid Content-Length"); + +- return bail_out_on_error(ctx, f, HTTP_REQUEST_ENTITY_TOO_LARGE); ++ return bail_out_on_error(ctx, f, HTTP_BAD_REQUEST); + } + + /* If we have a limit in effect and we know the C-L ahead of +@@ -381,7 +446,8 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, + if (!ap_is_HTTP_SUCCESS(f->r->status)) { + ctx->state = BODY_NONE; + ctx->eos_sent = 1; +- } else { ++ } ++ else { + char *tmp; + int len; + +@@ -389,7 +455,7 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, + * in a state of expecting one. + */ + f->r->expecting_100 = 0; +- tmp = apr_pstrcat(f->r->pool, AP_SERVER_PROTOCOL, " ", ++ tmp = apr_pstrcat(f->r->pool, AP_SERVER_PROTOCOL " ", + ap_get_status_line(HTTP_CONTINUE), CRLF CRLF, + NULL); + len = strlen(tmp); +@@ -401,279 +467,205 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, + e = apr_bucket_flush_create(f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, e); + +- ap_pass_brigade(f->c->output_filters, bb); +- } +- } +- +- /* We can't read the chunk until after sending 100 if required. */ +- if (ctx->state == BODY_CHUNK) { +- apr_brigade_cleanup(bb); +- +- rv = ap_get_brigade(f->next, bb, AP_MODE_GETLINE, +- block, 0); +- +- /* for timeout */ +- if (block == APR_NONBLOCK_READ && +- ( (rv == APR_SUCCESS && APR_BRIGADE_EMPTY(bb)) || +- (APR_STATUS_IS_EAGAIN(rv)) )) { +- ctx->state = BODY_CHUNK_PART; +- return APR_EAGAIN; +- } +- +- if (rv == APR_SUCCESS) { +- rv = get_chunk_line(ctx, bb, f->r->server->limit_req_line); +- if (APR_STATUS_IS_EAGAIN(rv)) { +- apr_brigade_cleanup(bb); +- ctx->state = BODY_CHUNK_PART; +- return rv; +- } +- if (rv == APR_SUCCESS) { +- ctx->remaining = get_chunk_size(ctx->chunk_ln); +- if (ctx->remaining == INVALID_CHAR) { +- rv = APR_EGENERAL; +- http_error = HTTP_BAD_REQUEST; +- } ++ rv = ap_pass_brigade(f->c->output_filters, bb); ++ if (rv != APR_SUCCESS) { ++ return AP_FILTER_ERROR; + } + } +- apr_brigade_cleanup(bb); +- +- /* Detect chunksize error (such as overflow) */ +- if (rv != APR_SUCCESS || ctx->remaining < 0) { +- ap_log_rerror(APLOG_MARK, APLOG_INFO, rv, f->r, APLOGNO(01589) "Error reading first chunk %s ", +- (ctx->remaining < 0) ? "(overflow)" : ""); +- ctx->remaining = 0; /* Reset it in case we have to +- * come back here later */ +- if (APR_STATUS_IS_TIMEUP(rv)) { +- http_error = HTTP_REQUEST_TIME_OUT; +- } +- return bail_out_on_error(ctx, f, http_error); +- } +- +- if (!ctx->remaining) { +- return read_chunked_trailers(ctx, f, b, +- conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE); +- } + } + } +- else { +- bb = ctx->bb; +- } + ++ /* sanity check in case we're read twice */ + if (ctx->eos_sent) { + e = apr_bucket_eos_create(f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(b, e); + return APR_SUCCESS; + } + +- if (!ctx->remaining) { ++ do { ++ apr_brigade_cleanup(b); ++ again = 0; /* until further notice */ ++ ++ /* read and handle the brigade */ + switch (ctx->state) { +- case BODY_NONE: +- break; +- case BODY_LENGTH: +- e = apr_bucket_eos_create(f->c->bucket_alloc); +- APR_BRIGADE_INSERT_TAIL(b, e); +- ctx->eos_sent = 1; +- return APR_SUCCESS; + case BODY_CHUNK: + case BODY_CHUNK_PART: +- { +- apr_brigade_cleanup(bb); ++ case BODY_CHUNK_EXT: ++ case BODY_CHUNK_CR: ++ case BODY_CHUNK_LF: ++ case BODY_CHUNK_END: ++ case BODY_CHUNK_END_LF: { + +- /* We need to read the CRLF after the chunk. */ +- if (ctx->state == BODY_CHUNK) { +- rv = ap_get_brigade(f->next, bb, AP_MODE_GETLINE, +- block, 0); +- if (block == APR_NONBLOCK_READ && +- ( (rv == APR_SUCCESS && APR_BRIGADE_EMPTY(bb)) || +- (APR_STATUS_IS_EAGAIN(rv)) )) { +- return APR_EAGAIN; +- } +- /* If we get an error, then leave */ +- if (rv != APR_SUCCESS) { +- return rv; +- } +- /* +- * We really don't care whats on this line. If it is RFC +- * compliant it should be only \r\n. If there is more +- * before we just ignore it as long as we do not get over +- * the limit for request lines. +- */ +- rv = get_remaining_chunk_line(ctx, bb, +- f->r->server->limit_req_line); +- apr_brigade_cleanup(bb); +- if (APR_STATUS_IS_EAGAIN(rv)) { +- return rv; +- } +- } else { +- rv = APR_SUCCESS; +- } ++ rv = ap_get_brigade(f->next, b, AP_MODE_GETLINE, block, 0); ++ ++ /* for timeout */ ++ if (block == APR_NONBLOCK_READ ++ && ((rv == APR_SUCCESS && APR_BRIGADE_EMPTY(b)) ++ || (APR_STATUS_IS_EAGAIN(rv)))) { ++ return APR_EAGAIN; ++ } ++ ++ if (rv == APR_EOF) { ++ return APR_INCOMPLETE; ++ } ++ ++ if (rv != APR_SUCCESS) { ++ return rv; ++ } ++ ++ e = APR_BRIGADE_FIRST(b); ++ while (e != APR_BRIGADE_SENTINEL(b)) { ++ const char *buffer; ++ apr_size_t len; ++ ++ if (!APR_BUCKET_IS_METADATA(e)) { ++ int parsing = 0; ++ ++ rv = apr_bucket_read(e, &buffer, &len, APR_BLOCK_READ); + +- if (rv == APR_SUCCESS) { +- /* Read the real chunk line. */ +- rv = ap_get_brigade(f->next, bb, AP_MODE_GETLINE, +- block, 0); +- /* Test timeout */ +- if (block == APR_NONBLOCK_READ && +- ( (rv == APR_SUCCESS && APR_BRIGADE_EMPTY(bb)) || +- (APR_STATUS_IS_EAGAIN(rv)) )) { +- ctx->state = BODY_CHUNK_PART; +- return APR_EAGAIN; +- } +- ctx->state = BODY_CHUNK; + if (rv == APR_SUCCESS) { +- rv = get_chunk_line(ctx, bb, f->r->server->limit_req_line); +- if (APR_STATUS_IS_EAGAIN(rv)) { +- ctx->state = BODY_CHUNK_PART; +- apr_brigade_cleanup(bb); +- return rv; +- } +- if (rv == APR_SUCCESS) { +- ctx->remaining = get_chunk_size(ctx->chunk_ln); +- if (ctx->remaining == INVALID_CHAR) { +- rv = APR_EGENERAL; ++ parsing = 1; ++ rv = parse_chunk_size(ctx, buffer, len, ++ f->r->server->limit_req_fieldsize); ++ } ++ if (rv != APR_SUCCESS) { ++ ap_log_rerror(APLOG_MARK, APLOG_INFO, rv, f->r, APLOGNO(01590) ++ "Error reading/parsing chunk %s ", ++ (APR_ENOSPC == rv) ? "(overflow)" : ""); ++ if (parsing) { ++ if (rv != APR_ENOSPC) { + http_error = HTTP_BAD_REQUEST; + } ++ return bail_out_on_error(ctx, f, http_error); + } ++ return rv; + } +- apr_brigade_cleanup(bb); + } + +- /* Detect chunksize error (such as overflow) */ +- if (rv != APR_SUCCESS || ctx->remaining < 0) { +- ap_log_rerror(APLOG_MARK, APLOG_INFO, rv, f->r, APLOGNO(01590) "Error reading chunk %s ", +- (ctx->remaining < 0) ? "(overflow)" : ""); +- ctx->remaining = 0; /* Reset it in case we have to +- * come back here later */ +- if (APR_STATUS_IS_TIMEUP(rv)) { +- http_error = HTTP_REQUEST_TIME_OUT; +- } +- return bail_out_on_error(ctx, f, http_error); +- } ++ apr_bucket_delete(e); ++ e = APR_BRIGADE_FIRST(b); ++ } ++ again = 1; /* come around again */ + +- if (!ctx->remaining) { +- return read_chunked_trailers(ctx, f, b, ++ if (ctx->state == BODY_CHUNK_TRAILER) { ++ /* Treat UNSET as DISABLE - trailers aren't merged by default */ ++ return read_chunked_trailers(ctx, f, b, + conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE); +- } + } ++ + break; + } +- } ++ case BODY_NONE: ++ case BODY_LENGTH: ++ case BODY_CHUNK_DATA: { + +- /* Ensure that the caller can not go over our boundary point. */ +- if (ctx->state == BODY_LENGTH || ctx->state == BODY_CHUNK) { +- if (ctx->remaining < readbytes) { +- readbytes = ctx->remaining; +- } +- AP_DEBUG_ASSERT(readbytes > 0); +- } ++ /* Ensure that the caller can not go over our boundary point. */ ++ if (ctx->state != BODY_NONE && ctx->remaining < readbytes) { ++ readbytes = ctx->remaining; ++ } ++ if (readbytes > 0) { ++ apr_off_t totalread; + +- rv = ap_get_brigade(f->next, b, mode, block, readbytes); ++ rv = ap_get_brigade(f->next, b, mode, block, readbytes); + +- if (rv != APR_SUCCESS) { +- return rv; +- } ++ /* for timeout */ ++ if (block == APR_NONBLOCK_READ ++ && ((rv == APR_SUCCESS && APR_BRIGADE_EMPTY(b)) ++ || (APR_STATUS_IS_EAGAIN(rv)))) { ++ return APR_EAGAIN; ++ } + +- /* How many bytes did we just read? */ +- apr_brigade_length(b, 0, &totalread); ++ if (rv == APR_EOF && ctx->state != BODY_NONE ++ && ctx->remaining > 0) { ++ return APR_INCOMPLETE; ++ } + +- /* If this happens, we have a bucket of unknown length. Die because +- * it means our assumptions have changed. */ +- AP_DEBUG_ASSERT(totalread >= 0); ++ if (rv != APR_SUCCESS) { ++ return rv; ++ } + +- if (ctx->state != BODY_NONE) { +- ctx->remaining -= totalread; +- if (ctx->remaining > 0) { +- e = APR_BRIGADE_LAST(b); +- if (APR_BUCKET_IS_EOS(e)) +- return APR_EOF; +- } +- } ++ /* How many bytes did we just read? */ ++ apr_brigade_length(b, 0, &totalread); + +- /* If we have no more bytes remaining on a C-L request, +- * save the callter a roundtrip to discover EOS. +- */ +- if (ctx->state == BODY_LENGTH && ctx->remaining == 0) { +- e = apr_bucket_eos_create(f->c->bucket_alloc); +- APR_BRIGADE_INSERT_TAIL(b, e); +- } ++ /* If this happens, we have a bucket of unknown length. Die because ++ * it means our assumptions have changed. */ ++ AP_DEBUG_ASSERT(totalread >= 0); + +- /* We have a limit in effect. */ +- if (ctx->limit) { +- /* FIXME: Note that we might get slightly confused on chunked inputs +- * as we'd need to compensate for the chunk lengths which may not +- * really count. This seems to be up for interpretation. */ +- ctx->limit_used += totalread; +- if (ctx->limit < ctx->limit_used) { +- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(01591) +- "Read content-length of %" APR_OFF_T_FMT +- " is larger than the configured limit" +- " of %" APR_OFF_T_FMT, ctx->limit_used, ctx->limit); +- apr_brigade_cleanup(bb); +- e = ap_bucket_error_create(HTTP_REQUEST_ENTITY_TOO_LARGE, NULL, +- f->r->pool, +- f->c->bucket_alloc); +- APR_BRIGADE_INSERT_TAIL(bb, e); +- e = apr_bucket_eos_create(f->c->bucket_alloc); +- APR_BRIGADE_INSERT_TAIL(bb, e); +- ctx->eos_sent = 1; +- return ap_pass_brigade(f->r->output_filters, bb); +- } +- } ++ if (ctx->state != BODY_NONE) { ++ ctx->remaining -= totalread; ++ if (ctx->remaining > 0) { ++ e = APR_BRIGADE_LAST(b); ++ if (APR_BUCKET_IS_EOS(e)) { ++ apr_bucket_delete(e); ++ return APR_INCOMPLETE; ++ } ++ } ++ else if (ctx->state == BODY_CHUNK_DATA) { ++ /* next chunk please */ ++ ctx->state = BODY_CHUNK_END; ++ ctx->chunk_used = 0; ++ } ++ } + +- return APR_SUCCESS; +-} ++ /* We have a limit in effect. */ ++ if (ctx->limit) { ++ /* FIXME: Note that we might get slightly confused on ++ * chunked inputs as we'd need to compensate for the chunk ++ * lengths which may not really count. This seems to be up ++ * for interpretation. ++ */ ++ ctx->limit_used += totalread; ++ if (ctx->limit < ctx->limit_used) { ++ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, ++ APLOGNO(01591) "Read content length of " ++ "%" APR_OFF_T_FMT " is larger than the " ++ "configured limit of %" APR_OFF_T_FMT, ++ ctx->limit_used, ctx->limit); ++ return bail_out_on_error(ctx, f, ++ HTTP_REQUEST_ENTITY_TOO_LARGE); ++ } ++ } ++ } + +-/** +- * Parse a chunk extension, detect overflow. +- * There are two error cases: +- * 1) If the conversion would require too many bits, a -1 is returned. +- * 2) If the conversion used the correct number of bits, but an overflow +- * caused only the sign bit to flip, then that negative number is +- * returned. +- * In general, any negative number can be considered an overflow error. +- */ +-static long get_chunk_size(char *b) +-{ +- long chunksize = 0; +- size_t chunkbits = sizeof(long) * 8; ++ /* If we have no more bytes remaining on a C-L request, ++ * save the caller a round trip to discover EOS. ++ */ ++ if (ctx->state == BODY_LENGTH && ctx->remaining == 0) { ++ e = apr_bucket_eos_create(f->c->bucket_alloc); ++ APR_BRIGADE_INSERT_TAIL(b, e); ++ ctx->eos_sent = 1; ++ } + +- ap_xlate_proto_from_ascii(b, strlen(b)); ++ break; ++ } ++ case BODY_CHUNK_TRAILER: { + +- if (!apr_isxdigit(*b)) { +- /* +- * Detect invalid character at beginning. This also works for empty +- * chunk size lines. +- */ +- return INVALID_CHAR; +- } +- /* Skip leading zeros */ +- while (*b == '0') { +- ++b; +- } ++ rv = ap_get_brigade(f->next, b, mode, block, readbytes); + +- while (apr_isxdigit(*b) && (chunkbits > 0)) { +- int xvalue = 0; ++ /* for timeout */ ++ if (block == APR_NONBLOCK_READ ++ && ((rv == APR_SUCCESS && APR_BRIGADE_EMPTY(b)) ++ || (APR_STATUS_IS_EAGAIN(rv)))) { ++ return APR_EAGAIN; ++ } ++ ++ if (rv != APR_SUCCESS) { ++ return rv; ++ } + +- if (*b >= '0' && *b <= '9') { +- xvalue = *b - '0'; ++ break; + } +- else if (*b >= 'A' && *b <= 'F') { +- xvalue = *b - 'A' + 0xa; ++ default: { ++ /* Should not happen */ ++ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, APLOGNO(02901) ++ "Unexpected body state (%i)", (int)ctx->state); ++ return APR_EGENERAL; + } +- else if (*b >= 'a' && *b <= 'f') { +- xvalue = *b - 'a' + 0xa; + } + +- chunksize = (chunksize << 4) | xvalue; +- chunkbits -= 4; +- ++b; +- } +- if (apr_isxdigit(*b)) { +- /* overflow */ +- return -1; +- } ++ } while (again); + +- return chunksize; ++ return APR_SUCCESS; + } + + typedef struct header_struct { +@@ -1385,6 +1377,39 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, + return ap_pass_brigade(f->next, b); + } + ++/* ++ * Map specific APR codes returned by the filter stack to HTTP error ++ * codes, or the default status code provided. Use it as follows: ++ * ++ * return ap_map_http_request_error(rv, HTTP_BAD_REQUEST); ++ * ++ * If the filter has already handled the error, AP_FILTER_ERROR will ++ * be returned, which is cleanly passed through. ++ * ++ * These mappings imply that the filter stack is reading from the ++ * downstream client, the proxy will map these codes differently. ++ */ ++AP_DECLARE(int) ap_map_http_request_error(apr_status_t rv, int status) ++{ ++ switch (rv) { ++ case AP_FILTER_ERROR: { ++ return AP_FILTER_ERROR; ++ } ++ case APR_ENOSPC: { ++ return HTTP_REQUEST_ENTITY_TOO_LARGE; ++ } ++ case APR_ENOTIMPL: { ++ return HTTP_NOT_IMPLEMENTED; ++ } ++ case APR_ETIMEDOUT: { ++ return HTTP_REQUEST_TIME_OUT; ++ } ++ default: { ++ return status; ++ } ++ } ++} ++ + /* In HTTP/1.1, any method can have a body. However, most GET handlers + * wouldn't know what to do with a request body if they received one. + * This helper routine tests for and reads any message body in the request, +@@ -1402,7 +1427,8 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, + AP_DECLARE(int) ap_discard_request_body(request_rec *r) + { + apr_bucket_brigade *bb; +- int rv, seen_eos; ++ int seen_eos; ++ apr_status_t rv; + + /* Sometimes we'll get in a state where the input handling has + * detected an error where we want to drop the connection, so if +@@ -1425,21 +1451,8 @@ AP_DECLARE(int) ap_discard_request_body(request_rec *r) + APR_BLOCK_READ, HUGE_STRING_LEN); + + if (rv != APR_SUCCESS) { +- /* FIXME: If we ever have a mapping from filters (apr_status_t) +- * to HTTP error codes, this would be a good place for them. +- * +- * If we received the special case AP_FILTER_ERROR, it means +- * that the filters have already handled this error. +- * Otherwise, we should assume we have a bad request. +- */ +- if (rv == AP_FILTER_ERROR) { +- apr_brigade_destroy(bb); +- return rv; +- } +- else { +- apr_brigade_destroy(bb); +- return HTTP_BAD_REQUEST; +- } ++ apr_brigade_destroy(bb); ++ return ap_map_http_request_error(rv, HTTP_BAD_REQUEST); + } + + for (bucket = APR_BRIGADE_FIRST(bb); +@@ -1608,6 +1621,13 @@ AP_DECLARE(long) ap_get_client_block(request_rec *r, char *buffer, + /* We lose the failure code here. This is why ap_get_client_block should + * not be used. + */ ++ if (rv == AP_FILTER_ERROR) { ++ /* AP_FILTER_ERROR means a filter has responded already, ++ * we are DONE. ++ */ ++ apr_brigade_destroy(bb); ++ return -1; ++ } + if (rv != APR_SUCCESS) { + /* if we actually fail here, we want to just return and + * stop trying to read data from the client. diff --git a/SOURCES/httpd-2.4.6-CVE-2015-3185.patch b/SOURCES/httpd-2.4.6-CVE-2015-3185.patch new file mode 100644 index 0000000..e8bb688 --- /dev/null +++ b/SOURCES/httpd-2.4.6-CVE-2015-3185.patch @@ -0,0 +1,175 @@ +Index: server/request.c +=================================================================== +--- a/server/request.c (revision 1684524) ++++ b/server/request.c (revision 1684525) +@@ -71,6 +71,7 @@ + APR_HOOK_LINK(create_request) + APR_HOOK_LINK(post_perdir_config) + APR_HOOK_LINK(dirwalk_stat) ++ APR_HOOK_LINK(force_authn) + ) + + AP_IMPLEMENT_HOOK_RUN_FIRST(int,translate_name, +@@ -97,6 +98,8 @@ + AP_IMPLEMENT_HOOK_RUN_FIRST(apr_status_t,dirwalk_stat, + (apr_finfo_t *finfo, request_rec *r, apr_int32_t wanted), + (finfo, r, wanted), AP_DECLINED) ++AP_IMPLEMENT_HOOK_RUN_FIRST(int,force_authn, ++ (request_rec *r), (r), DECLINED) + + static int auth_internal_per_conf = 0; + static int auth_internal_per_conf_hooks = 0; +@@ -118,6 +121,39 @@ + } + } + ++AP_DECLARE(int) ap_some_authn_required(request_rec *r) ++{ ++ int access_status; ++ ++ switch (ap_satisfies(r)) { ++ case SATISFY_ALL: ++ case SATISFY_NOSPEC: ++ if ((access_status = ap_run_access_checker(r)) != OK) { ++ break; ++ } ++ ++ access_status = ap_run_access_checker_ex(r); ++ if (access_status == DECLINED) { ++ return TRUE; ++ } ++ ++ break; ++ case SATISFY_ANY: ++ if ((access_status = ap_run_access_checker(r)) == OK) { ++ break; ++ } ++ ++ access_status = ap_run_access_checker_ex(r); ++ if (access_status == DECLINED) { ++ return TRUE; ++ } ++ ++ break; ++ } ++ ++ return FALSE; ++} ++ + /* This is the master logic for processing requests. Do NOT duplicate + * this logic elsewhere, or the security model will be broken by future + * API changes. Each phase must be individually optimized to pick up +@@ -232,15 +268,8 @@ + } + + access_status = ap_run_access_checker_ex(r); +- if (access_status == OK) { +- ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, +- "request authorized without authentication by " +- "access_checker_ex hook: %s", r->uri); +- } +- else if (access_status != DECLINED) { +- return decl_die(access_status, "check access", r); +- } +- else { ++ if (access_status == DECLINED ++ || (access_status == OK && ap_run_force_authn(r) == OK)) { + if ((access_status = ap_run_check_user_id(r)) != OK) { + return decl_die(access_status, "check user", r); + } +@@ -258,6 +287,14 @@ + return decl_die(access_status, "check authorization", r); + } + } ++ else if (access_status == OK) { ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, ++ "request authorized without authentication by " ++ "access_checker_ex hook: %s", r->uri); ++ } ++ else { ++ return decl_die(access_status, "check access", r); ++ } + break; + case SATISFY_ANY: + if ((access_status = ap_run_access_checker(r)) == OK) { +@@ -269,15 +306,8 @@ + } + + access_status = ap_run_access_checker_ex(r); +- if (access_status == OK) { +- ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, +- "request authorized without authentication by " +- "access_checker_ex hook: %s", r->uri); +- } +- else if (access_status != DECLINED) { +- return decl_die(access_status, "check access", r); +- } +- else { ++ if (access_status == DECLINED ++ || (access_status == OK && ap_run_force_authn(r) == OK)) { + if ((access_status = ap_run_check_user_id(r)) != OK) { + return decl_die(access_status, "check user", r); + } +@@ -295,6 +325,14 @@ + return decl_die(access_status, "check authorization", r); + } + } ++ else if (access_status == OK) { ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, ++ "request authorized without authentication by " ++ "access_checker_ex hook: %s", r->uri); ++ } ++ else { ++ return decl_die(access_status, "check access", r); ++ } + break; + } + } +Index: include/http_request.h +=================================================================== +--- a/include/http_request.h (revision 1684524) ++++ b/include/http_request.h (revision 1684525) +@@ -185,6 +185,8 @@ + * is required for the current request + * @param r The current request + * @return 1 if authentication is required, 0 otherwise ++ * @bug Behavior changed in 2.4.x refactoring, API no longer usable ++ * @deprecated @see ap_some_authn_required() + */ + AP_DECLARE(int) ap_some_auth_required(request_rec *r); + +@@ -539,6 +541,16 @@ + AP_DECLARE_HOOK(int,post_perdir_config,(request_rec *r)) + + /** ++ * This hook allows a module to force authn to be required when ++ * processing a request. ++ * This hook should be registered with ap_hook_force_authn(). ++ * @param r The current request ++ * @return OK (force authn), DECLINED (let later modules decide) ++ * @ingroup hooks ++ */ ++AP_DECLARE_HOOK(int,force_authn,(request_rec *r)) ++ ++/** + * This hook allows modules to handle/emulate the apr_stat() calls + * needed for directory walk. + * @param r The current request +@@ -584,6 +596,17 @@ + AP_DECLARE(apr_bucket *) ap_bucket_eor_create(apr_bucket_alloc_t *list, + request_rec *r); + ++/** ++ * Can be used within any handler to determine if any authentication ++ * is required for the current request. Note that if used with an ++ * access_checker hook, an access_checker_ex hook or an authz provider; the ++ * caller should take steps to avoid a loop since this function is ++ * implemented by calling these hooks. ++ * @param r The current request ++ * @return TRUE if authentication is required, FALSE otherwise ++ */ ++AP_DECLARE(int) ap_some_authn_required(request_rec *r); ++ + #ifdef __cplusplus + } + #endif diff --git a/SOURCES/welcome.conf b/SOURCES/welcome.conf index c1b6c11..5d1e452 100644 --- a/SOURCES/welcome.conf +++ b/SOURCES/welcome.conf @@ -16,7 +16,3 @@ Alias /.noindex.html /usr/share/httpd/noindex/index.html -Alias /noindex/css/bootstrap.min.css /usr/share/httpd/noindex/css/bootstrap.min.css -Alias /noindex/css/open-sans.css /usr/share/httpd/noindex/css/open-sans.css -Alias /images/apache_pb.gif /usr/share/httpd/noindex/images/apache_pb.gif -Alias /images/poweredby.png /usr/share/httpd/noindex/images/poweredby.png diff --git a/SPECS/httpd.spec b/SPECS/httpd.spec index 79e935e..f7e46b5 100644 --- a/SPECS/httpd.spec +++ b/SPECS/httpd.spec @@ -4,7 +4,7 @@ %define mmn 20120211 %define oldmmnisa %{mmn}-%{__isa_name}-%{__isa_bits} %define mmnisa %{mmn}%{__isa_name}%{__isa_bits} -%define vstring CentOS +%define vstring %(source /etc/os-release; echo ${REDHAT_SUPPORT_PRODUCT}) # Drop automatic provides for module DSOs %{?filter_setup: @@ -15,10 +15,10 @@ Summary: Apache HTTP Server Name: httpd Version: 2.4.6 -Release: 31%{?dist} +Release: 31%{?dist}.1 URL: http://httpd.apache.org/ Source0: http://www.apache.org/dist/httpd/httpd-%{version}.tar.bz2 -Source1: centos-noindex.tar.gz +Source1: index.html Source2: httpd.logrotate Source3: httpd.sysconf Source4: httpd-ssl-pass-dialog @@ -94,6 +94,8 @@ Patch205: httpd-2.4.6-CVE-2014-0226.patch Patch206: httpd-2.4.6-CVE-2013-4352.patch Patch207: httpd-2.4.6-CVE-2013-5704.patch Patch208: httpd-2.4.6-CVE-2014-3581.patch +Patch209: httpd-2.4.6-CVE-2015-3185.patch +Patch210: httpd-2.4.6-CVE-2015-3183.patch License: ASL 2.0 Group: System Environment/Daemons BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root @@ -249,6 +251,8 @@ rm modules/ssl/ssl_engine_dh.c %patch206 -p1 -b .cve4352 %patch207 -p1 -b .cve5704 %patch208 -p1 -b .cve3581 +%patch209 -p1 -b .cve3185 +%patch210 -p1 -b .cve3183 # Patch in the vendor string and the release string sed -i '/^#define PLATFORM/s/Unix/%{vstring}/' os/unix/os.h @@ -402,10 +406,8 @@ EOF # Handle contentdir mkdir $RPM_BUILD_ROOT%{contentdir}/noindex -tar xzf $RPM_SOURCE_DIR/centos-noindex.tar.gz \ - -C $RPM_BUILD_ROOT%{contentdir}/noindex/ \ - --strip-components=1 - +install -m 644 -p $RPM_SOURCE_DIR/index.html \ + $RPM_BUILD_ROOT%{contentdir}/noindex/index.html rm -rf %{contentdir}/htdocs # remove manual sources @@ -428,7 +430,7 @@ rm -v $RPM_BUILD_ROOT%{docroot}/html/*.html \ $RPM_BUILD_ROOT%{docroot}/cgi-bin/* # Symlink for the powered-by-$DISTRO image: -ln -s ../noindex/images/poweredby.png \ +ln -s ../../pixmaps/poweredby.png \ $RPM_BUILD_ROOT%{contentdir}/icons/poweredby.png # symlinks for /etc/httpd @@ -613,7 +615,7 @@ rm -rf $RPM_BUILD_ROOT %{contentdir}/error/README %{contentdir}/error/*.var %{contentdir}/error/include/*.html -%{contentdir}/noindex/* +%{contentdir}/noindex/index.html %dir %{docroot} %dir %{docroot}/cgi-bin @@ -679,11 +681,10 @@ rm -rf $RPM_BUILD_ROOT %{_sysconfdir}/rpm/macros.httpd %changelog -* Thu Mar 05 2015 CentOS Sources - 2.4.6-31.el7.centos -- Remove index.html, add centos-noindex.tar.gz -- change vstring -- change symlink for poweredby.png -- update welcome.conf with proper aliases +* Mon Aug 10 2015 Jan Kaluza - 2.4.6-31.1 +- core: fix chunk header parsing defect (CVE-2015-3183) +- core: replace of ap_some_auth_required with ap_some_authn_required + and ap_force_authn hook (CVE-2015-3185) * Tue Dec 02 2014 Jan Kaluza - 2.4.6-31 - mod_proxy_fcgi: determine if FCGI_CONN_CLOSE should be enabled