From a8e15212934ace72bff9774972b57920ec5eb74b Mon Sep 17 00:00:00 2001 From: Joe Orton Date: Tue, 10 Oct 2017 17:51:13 +0000 Subject: [PATCH] Merge r1808230 from trunk: * server/protocol.c (ap_content_length_filter): Rewrite the content length filter to avoid arbitrary memory consumption for streaming responses (e.g. large CGI script output). Ensures C-L is still generated in common cases (static content, small CGI script output), but this DOES change behaviour and some responses will end up chunked rather than C-L computed. PR: 61222 Submitted by: jorton, rpluem Reviewed by: jorton, wrowe, ylavic git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1811746 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES | 4 ++ server/protocol.c | 100 +++++++++++++++++++++++++++++----------------- 2 files changed, 67 insertions(+), 37 deletions(-) diff --git a/CHANGES b/CHANGES index c20f69336c..e157c52555 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,10 @@ -*- coding: utf-8 -*- Changes with Apache 2.4.29 + *) core: Rewrite the Content-Length filter to avoid excessive memory + consumption. Chunked responses will be generated in more cases + than in previous releases. PR 61222. [Joe Orton, Ruediger Pluem] + *) mod_ssl: Fix SessionTicket callback return value, which does seem to matter with OpenSSL 1.1. [Yann Ylavic] diff --git a/server/protocol.c b/server/protocol.c index edf3ed91c1..a89e2feb24 100644 --- a/server/protocol.c +++ b/server/protocol.c @@ -1674,62 +1674,88 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_content_length_filter( ctx->tmpbb = apr_brigade_create(r->pool, r->connection->bucket_alloc); } - /* Loop through this set of buckets to compute their length - */ + /* Loop through the brigade to count the length. To avoid + * arbitrary memory consumption with morphing bucket types, this + * loop will stop and pass on the brigade when necessary. */ e = APR_BRIGADE_FIRST(b); while (e != APR_BRIGADE_SENTINEL(b)) { + apr_status_t rv; + if (APR_BUCKET_IS_EOS(e)) { eos = 1; break; } - if (e->length == (apr_size_t)-1) { + /* For a flush bucket, fall through to pass the brigade and + * flush now. */ + else if (APR_BUCKET_IS_FLUSH(e)) { + e = APR_BUCKET_NEXT(e); + } + /* For metadata bucket types other than FLUSH, loop. */ + else if (APR_BUCKET_IS_METADATA(e)) { + e = APR_BUCKET_NEXT(e); + continue; + } + /* For determinate length data buckets, count the length and + * continue. */ + else if (e->length != (apr_size_t)-1) { + r->bytes_sent += e->length; + e = APR_BUCKET_NEXT(e); + continue; + } + /* For indeterminate length data buckets, perform one read. */ + else /* e->length == (apr_size_t)-1 */ { apr_size_t len; const char *ignored; - apr_status_t rv; - - /* This is probably a pipe bucket. Send everything - * prior to this, and then read the data for this bucket. - */ + rv = apr_bucket_read(e, &ignored, &len, eblock); + if ((rv != APR_SUCCESS) && !APR_STATUS_IS_EAGAIN(rv)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00574) + "ap_content_length_filter: " + "apr_bucket_read() failed"); + return rv; + } if (rv == APR_SUCCESS) { - /* Attempt a nonblocking read next time through */ eblock = APR_NONBLOCK_READ; + e = APR_BUCKET_NEXT(e); r->bytes_sent += len; } else if (APR_STATUS_IS_EAGAIN(rv)) { - /* Output everything prior to this bucket, and then - * do a blocking read on the next batch. - */ - if (e != APR_BRIGADE_FIRST(b)) { - apr_bucket *flush; - apr_brigade_split_ex(b, e, ctx->tmpbb); - flush = apr_bucket_flush_create(r->connection->bucket_alloc); - - APR_BRIGADE_INSERT_TAIL(b, flush); - rv = ap_pass_brigade(f->next, b); - if (rv != APR_SUCCESS || f->c->aborted) { - return rv; - } - apr_brigade_cleanup(b); - APR_BRIGADE_CONCAT(b, ctx->tmpbb); - e = APR_BRIGADE_FIRST(b); + apr_bucket *flush; - ctx->data_sent = 1; - } + /* Next read must block. */ eblock = APR_BLOCK_READ; - continue; - } - else { - ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00574) - "ap_content_length_filter: " - "apr_bucket_read() failed"); - return rv; + + /* Ensure the last bucket to pass down is a flush if + * the next read will block. */ + flush = apr_bucket_flush_create(f->c->bucket_alloc); + APR_BUCKET_INSERT_BEFORE(e, flush); } } - else { - r->bytes_sent += e->length; + + /* Optimization: if the next bucket is EOS (directly after a + * bucket morphed to the heap, or a flush), short-cut to + * handle EOS straight away - allowing C-L to be determined + * for content which is already entirely in memory. */ + if (e != APR_BRIGADE_SENTINEL(b) && APR_BUCKET_IS_EOS(e)) { + continue; + } + + /* On reaching here, pass on everything in the brigade up to + * this point. */ + apr_brigade_split_ex(b, e, ctx->tmpbb); + + rv = ap_pass_brigade(f->next, b); + if (rv != APR_SUCCESS) { + return rv; + } + else if (f->c->aborted) { + return APR_ECONNABORTED; } - e = APR_BUCKET_NEXT(e); + apr_brigade_cleanup(b); + APR_BRIGADE_CONCAT(b, ctx->tmpbb); + e = APR_BRIGADE_FIRST(b); + + ctx->data_sent = 1; } /* If we've now seen the entire response and it's otherwise -- 2.40.0