]> granicus.if.org Git - apache/commitdiff
*) SECURITY: CVE-2014-0118 (cve.mitre.org)
authorEric Covener <covener@apache.org>
Mon, 14 Jul 2014 19:56:15 +0000 (19:56 +0000)
committerEric Covener <covener@apache.org>
Mon, 14 Jul 2014 19:56:15 +0000 (19:56 +0000)
     mod_deflate: The DEFLATE input filter (inflates request bodies) now
     limits the length and compression ratio of inflated request bodies to avoid
     denial of sevice via highly compressed bodies.  See directives
     DeflateInflateLimitRequestBody, DeflateInflateRatioLimit,
     and DeflateInflateRatioBurst.

Thanks to Giancarlo Pellegrino and Davide Balzarotti for reporting the issue.

Submitted By: ylavic, covener
Reviewed By: jorton, covener, jim

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1610501 13f79535-47bb-0310-9956-ffa450edef68

docs/manual/mod/mod_deflate.xml
modules/filters/mod_deflate.c

index 6cb4c7ed635cc5ecf3aa67919caced9a6b83437f..b4774099fd1a8fd3bc8cf4a736ac7cba30d4a57d 100644 (file)
@@ -328,6 +328,59 @@ CustomLog logs/deflate_log deflate
 </usage>
 </directivesynopsis>
 
+<directivesynopsis>
+<name>DeflateInflateLimitRequestBody</name>
+<description>Maximum size of inflated request bodies</description>
+<syntax>DeflateInflateLimitRequestBody<var>value</var></syntax>
+<default>None, but LimitRequestBody applies after deflation</default>
+<contextlist><context>server config</context><context>virtual host</context>
+<context>directory</context><context>.htaccess</context></contextlist>
+<compatibility>2.4.10 and later</compatibility>
+
+<usage>
+    <p>The <directive>DeflateInflateLimitRequestBody</directive> directive 
+        specifies the maximum size of an inflated request body. If it is unset,
+        <directive module="core">LimitRequestBody</directive> is applied to the
+        inflated body.</p>
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>DeflateInflateRatioLimit</name>
+<description>Maximum inflation ratio for request bodies</description>
+<syntax>DeflateInflateRatioLimit <var>value</var></syntax>
+<default>200</default>
+<contextlist><context>server config</context><context>virtual host</context>
+<context>directory</context><context>.htaccess</context></contextlist>
+<compatibility>2.4.10 and later</compatibility>
+
+<usage>
+    <p>The <directive>DeflateInflateRatioLimit</directive> directive 
+        specifies the maximum ratio of deflated to inflated size of an 
+        inflated request body. This ratio is checked as the body is
+        streamed in, and if crossed more than 
+        <directive>DeflateInflateRatioBurst</directive> times the request
+        will be terminated.</p>
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>DeflateInflateRatioBurst</name>
+<description>Maximum number of times the inflation ratio for request bodies 
+             can be crossed</description>
+<syntax>DeflateInflateRatioBurst <var>value</var></syntax>
+<default>3</default>
+<contextlist><context>server config</context><context>virtual host</context>
+<context>directory</context><context>.htaccess</context></contextlist>
+<compatibility>2.4.10 and later</compatibility>
+
+<usage>
+    <p>The <directive>DeflateInflateRatioBurst</directive> directive 
+       specifies the maximum number of times the 
+       <directive>DeflateInflateRatioLimit</directive> cab be crossed before 
+       terminating the request.</p>
+</usage>
+</directivesynopsis>
 
 </modulesynopsis>
 
index e61dab52b20062e339bc2ba71abe4721717b3ffd..6eefecbb8a89d8b38d298b0e6b3a8addac7956f5 100644 (file)
@@ -37,6 +37,7 @@
 #include "httpd.h"
 #include "http_config.h"
 #include "http_log.h"
+#include "http_core.h"
 #include "apr_lib.h"
 #include "apr_strings.h"
 #include "apr_general.h"
@@ -58,6 +59,9 @@ module AP_MODULE_DECLARE_DATA deflate_module;
 #define AP_DEFLATE_ETAG_NOCHANGE  1
 #define AP_DEFLATE_ETAG_REMOVE    2
 
+#define AP_INFLATE_RATIO_LIMIT 200
+#define AP_INFLATE_RATIO_BURST 3
+
 typedef struct deflate_filter_config_t
 {
     int windowSize;
@@ -70,6 +74,12 @@ typedef struct deflate_filter_config_t
     int etag_opt;
 } deflate_filter_config;
 
+typedef struct deflate_dirconf_t {
+    apr_off_t inflate_limit;
+    int ratio_limit,
+        ratio_burst;
+} deflate_dirconf_t;
+
 /* RFC 1952 Section 2.3 defines the gzip header:
  *
  * +---+---+---+---+---+---+---+---+---+---+
@@ -212,6 +222,14 @@ static void *create_deflate_server_config(apr_pool_t *p, server_rec *s)
     return c;
 }
 
+static void *create_deflate_dirconf(apr_pool_t *p, char *dummy)
+{
+    deflate_dirconf_t *dc = apr_pcalloc(p, sizeof(*dc));
+    dc->ratio_limit = AP_INFLATE_RATIO_LIMIT;
+    dc->ratio_burst = AP_INFLATE_RATIO_BURST;
+    return dc;
+}
+
 static const char *deflate_set_window_size(cmd_parms *cmd, void *dummy,
                                            const char *arg)
 {
@@ -326,6 +344,55 @@ static const char *deflate_set_compressionlevel(cmd_parms *cmd, void *dummy,
     return NULL;
 }
 
+
+static const char *deflate_set_inflate_limit(cmd_parms *cmd, void *dirconf,
+                                      const char *arg)
+{
+    deflate_dirconf_t *dc = (deflate_dirconf_t*) dirconf;
+    char *errp;
+
+    if (APR_SUCCESS != apr_strtoff(&dc->inflate_limit, arg, &errp, 10)) {
+        return "DeflateInflateLimitRequestBody is not parsable.";
+    }
+    if (*errp || dc->inflate_limit < 0) {
+        return "DeflateInflateLimitRequestBody requires a non-negative integer.";
+    }
+
+    return NULL;
+}
+
+static const char *deflate_set_inflate_ratio_limit(cmd_parms *cmd,
+                                                   void *dirconf,
+                                                   const char *arg)
+{
+    deflate_dirconf_t *dc = (deflate_dirconf_t*) dirconf;
+    int i;
+
+    i = atoi(arg);
+    if (i <= 0)
+        return "DeflateInflateRatioLimit must be positive";
+
+    dc->ratio_limit = i;
+
+    return NULL;
+}
+
+static const char *deflate_set_inflate_ratio_burst(cmd_parms *cmd,
+                                                   void *dirconf,
+                                                   const char *arg)
+{
+    deflate_dirconf_t *dc = (deflate_dirconf_t*) dirconf;
+    int i;
+
+    i = atoi(arg);
+    if (i <= 0)
+        return "DeflateInflateRatioBurst must be positive";
+
+    dc->ratio_burst = i;
+
+    return NULL;
+}
+
 typedef struct deflate_ctx_t
 {
     z_stream stream;
@@ -338,6 +405,8 @@ typedef struct deflate_ctx_t
     char header[10]; /* sizeof(gzip_header) */
     apr_size_t header_len;
     int zlib_flags;
+    int ratio_hits;
+    apr_off_t inflate_total;
     unsigned int consume_pos,
                  consume_len;
     unsigned int filter_init:1;
@@ -462,6 +531,22 @@ static void deflate_check_etag(request_rec *r, const char *transform, int etag_o
     }
 }
 
+/* Check whether the (inflate) ratio exceeds the configured limit/burst. */
+static int check_ratio(request_rec *r, deflate_ctx *ctx,
+                       const deflate_dirconf_t *dc)
+{
+    if (ctx->stream.total_in) {
+        int ratio = ctx->stream.total_out / ctx->stream.total_in;
+        if (ratio < dc->ratio_limit) {
+            ctx->ratio_hits = 0;
+        }
+        else if (++ctx->ratio_hits > dc->ratio_burst) {
+            return 0;
+        }
+    }
+    return 1;
+}
+
 static int have_ssl_compression(request_rec *r)
 {
     const char *comp;
@@ -1036,6 +1121,8 @@ static apr_status_t deflate_in_filter(ap_filter_t *f,
     int zRC;
     apr_status_t rv;
     deflate_filter_config *c;
+    deflate_dirconf_t *dc;
+    apr_off_t inflate_limit;
 
     /* just get out of the way of things we don't want. */
     if (mode != AP_MODE_READBYTES) {
@@ -1043,6 +1130,7 @@ static apr_status_t deflate_in_filter(ap_filter_t *f,
     }
 
     c = ap_get_module_config(r->server->module_config, &deflate_module);
+    dc = ap_get_module_config(r->per_dir_config, &deflate_module);
 
     if (!ctx || ctx->header_len < sizeof(ctx->header)) {
         apr_size_t len;
@@ -1159,6 +1247,12 @@ static apr_status_t deflate_in_filter(ap_filter_t *f,
         apr_brigade_cleanup(ctx->bb);
     }
 
+    inflate_limit = dc->inflate_limit; 
+    if (inflate_limit == 0) { 
+        /* The core is checking the deflated body, we'll check the inflated */
+        inflate_limit = ap_get_limit_req_body(f->r);
+    }
+
     if (APR_BRIGADE_EMPTY(ctx->proc_bb)) {
         rv = ap_get_brigade(f->next, ctx->bb, mode, block, readbytes);
 
@@ -1209,6 +1303,17 @@ static apr_status_t deflate_in_filter(ap_filter_t *f,
 
                 ctx->stream.next_out = ctx->buffer;
                 len = c->bufferSize - ctx->stream.avail_out;
+                ctx->inflate_total += len;
+                if (inflate_limit && ctx->inflate_total > inflate_limit) { 
+                    inflateEnd(&ctx->stream);
+                    ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO()
+                            "Inflated content length of %" APR_OFF_T_FMT
+                            " is larger than the configured limit"
+                            " of %" APR_OFF_T_FMT, 
+                            ctx->inflate_total, inflate_limit);
+                    return APR_ENOSPC;
+                }
 
                 ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
                 tmp_b = apr_bucket_heap_create((char *)ctx->buffer, len,
@@ -1263,9 +1368,30 @@ static apr_status_t deflate_in_filter(ap_filter_t *f,
                 while (ctx->stream.avail_in != 0) {
                     if (ctx->stream.avail_out == 0) {
                         apr_bucket *tmp_heap;
+
                         ctx->stream.next_out = ctx->buffer;
                         len = c->bufferSize - ctx->stream.avail_out;
 
+                        ctx->inflate_total += len;
+                        if (inflate_limit && ctx->inflate_total > inflate_limit) { 
+                            inflateEnd(&ctx->stream);
+                            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO()
+                                    "Inflated content length of %" APR_OFF_T_FMT
+                                    " is larger than the configured limit"
+                                    " of %" APR_OFF_T_FMT, 
+                                    ctx->inflate_total, inflate_limit);
+                            return APR_ENOSPC;
+                        }
+
+                        if (!check_ratio(r, ctx, dc)) {
+                            inflateEnd(&ctx->stream);
+                            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO()
+                                    "Inflated content ratio is larger than the "
+                                    "configured limit %i by %i time(s)",
+                                    dc->ratio_limit, dc->ratio_burst);
+                            return APR_EINVAL;
+                        }
+
                         ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
                         tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
                                                           NULL, f->c->bucket_alloc);
@@ -1411,6 +1537,7 @@ static apr_status_t inflate_out_filter(ap_filter_t *f,
     int zRC;
     apr_status_t rv;
     deflate_filter_config *c;
+    deflate_dirconf_t *dc;
 
     /* Do nothing if asked to filter nothing. */
     if (APR_BRIGADE_EMPTY(bb)) {
@@ -1418,6 +1545,7 @@ static apr_status_t inflate_out_filter(ap_filter_t *f,
     }
 
     c = ap_get_module_config(r->server->module_config, &deflate_module);
+    dc = ap_get_module_config(r->per_dir_config, &deflate_module);
 
     if (!ctx) {
 
@@ -1698,6 +1826,14 @@ static apr_status_t inflate_out_filter(ap_filter_t *f,
         while (ctx->stream.avail_in != 0) {
             if (ctx->stream.avail_out == 0) {
 
+                if (!check_ratio(r, ctx, dc)) {
+                    ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO()
+                            "Inflated content ratio is larger than the "
+                            "configured limit %i by %i time(s)",
+                            dc->ratio_limit, dc->ratio_burst);
+                    return APR_EINVAL;
+                }
+
                 ctx->stream.next_out = ctx->buffer;
                 len = c->bufferSize - ctx->stream.avail_out;
 
@@ -1785,7 +1921,14 @@ static const command_rec deflate_filter_cmds[] = {
                   "Set the Deflate Compression Level (1-9)"),
     AP_INIT_TAKE1("DeflateAlterEtag", deflate_set_etag, NULL, RSRC_CONF,
                   "Set how mod_deflate should modify ETAG response headers: 'AddSuffix' (default), 'NoChange' (2.2.x behavior), 'Remove'"),
+    AP_INIT_TAKE1("DeflateInflateLimitRequestBody", deflate_set_inflate_limit, NULL, OR_ALL,
+                  "Set a limit on size of inflated input"),
+    AP_INIT_TAKE1("DeflateInflateRatioLimit", deflate_set_inflate_ratio_limit, NULL, OR_ALL,
+                  "Set the inflate ratio limit above which inflation is "
+                  "aborted (default: " APR_STRINGIFY(AP_INFLATE_RATIO_LIMIT) ")"),
+    AP_INIT_TAKE1("DeflateInflateRatioBurst", deflate_set_inflate_ratio_burst, NULL, OR_ALL,
+                  "Set the maximum number of following inflate ratios above limit "
+                  "(default: " APR_STRINGIFY(AP_INFLATE_RATIO_BURST) ")"),
     {NULL}
 };
 
@@ -1795,7 +1938,7 @@ static const command_rec deflate_filter_cmds[] = {
 #endif
 AP_DECLARE_MODULE(deflate) = {
     STANDARD20_MODULE_STUFF,
-    NULL,                         /* dir config creater */
+    create_deflate_dirconf,       /* dir config creater */
     NULL,                         /* dir merger --- default is to override */
     create_deflate_server_config, /* server config */
     NULL,                         /* merge server config */