Commit 15c8bdbc authored by William A. Rowe Jr's avatar William A. Rowe Jr
Browse files

mod_deflate: Handle Zlib header and validation bytes received in multiple

chunks.

PR: 46146, 55666
Submitted by: Yann Ylavic
Reviewed by: ylavic, wrowe, rpluem
Backports: r1572655, r1572663, r1572668, r1572669, r1572670, r1572671, r1573224, r1586745, r1587594, r1587639, r1590509, r1603156, r1604353



git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.2.x@1619753 13f79535-47bb-0310-9956-ffa450edef68
parent 16e241ed
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -21,6 +21,9 @@ Changes with Apache 2.2.28
     Fix a race condition in scoreboard handling, which could lead to
     a heap buffer overflow.  [Joe Orton, Eric Covener, Jeff Trawick]
 
  *) mod_deflate: Handle Zlib header and validation bytes received in multiple
     chunks. PR 46146. [Yann Ylavic]

  *) SECURITY: CVE-2013-5704 (cve.mitre.org)
     core: HTTP trailers could be used to replace HTTP headers
     late during request processing, potentially undoing or
+392 −154
Original line number Diff line number Diff line
@@ -360,9 +360,15 @@ typedef struct deflate_ctx_t
    int (*libz_end_func)(z_streamp);
    unsigned char *validation_buffer;
    apr_size_t validation_buffer_length;
    int inflate_init;
    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;
    unsigned int done:1;
} deflate_ctx;

/* Check whether the (inflate) ratio exceeds the configured limit/burst. */
@@ -749,6 +755,9 @@ static apr_status_t deflate_out_filter(ap_filter_t *f,
            zRC = flush_libz_buffer(ctx, c, f->c->bucket_alloc, deflate,
                                    Z_SYNC_FLUSH, NO_UPDATE_CRC);
            if (zRC != Z_OK) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                              "Zlib error %d flushing zlib output buffer (%s)",
                              zRC, ctx->stream.msg);
                return APR_EGENERAL;
            }

@@ -774,6 +783,14 @@ static apr_status_t deflate_out_filter(ap_filter_t *f,

        /* read */
        apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
        if (!len) {
            apr_bucket_delete(e);
            continue;
        }
        if (len > APR_INT32_MAX) {
            apr_bucket_split(e, APR_INT32_MAX);
            apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
        }

        /* This crc32 function is from zlib. */
        ctx->crc = crc32(ctx->crc, (const Bytef *)data, len);
@@ -805,6 +822,9 @@ static apr_status_t deflate_out_filter(ap_filter_t *f,
            zRC = deflate(&(ctx->stream), Z_NO_FLUSH);

            if (zRC != Z_OK) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                              "Zlib error %d deflating data (%s)", zRC,
                              ctx->stream.msg);
                return APR_EGENERAL;
            }
        }
@@ -816,6 +836,96 @@ static apr_status_t deflate_out_filter(ap_filter_t *f,
    return APR_SUCCESS;
}

static apr_status_t consume_zlib_flags(deflate_ctx *ctx,
                                       const char **data, apr_size_t *len)
{
    if ((ctx->zlib_flags & EXTRA_FIELD)) {
        /* Consume 2 bytes length prefixed data. */
        if (ctx->consume_pos == 0) {
            if (!*len) {
                return APR_INCOMPLETE;
            }
            ctx->consume_len = (unsigned int)**data;
            ctx->consume_pos++;
            ++*data;
            --*len;
        }
        if (ctx->consume_pos == 1) {
            if (!*len) {
                return APR_INCOMPLETE;
            }
            ctx->consume_len += ((unsigned int)**data) << 8;
            ctx->consume_pos++;
            ++*data;
            --*len;
        }
        if (*len < ctx->consume_len) {
            ctx->consume_len -= *len;
            *len = 0;
            return APR_INCOMPLETE;
        }
        *data += ctx->consume_len;
        *len -= ctx->consume_len;

        ctx->consume_len = ctx->consume_pos = 0;
        ctx->zlib_flags &= ~EXTRA_FIELD;
    }

    if ((ctx->zlib_flags & ORIG_NAME)) {
        /* Consume nul terminated string. */
        while (*len && **data) {
            ++*data;
            --*len;
        }
        if (!*len) {
            return APR_INCOMPLETE;
        }
        /* .. and nul. */
        ++*data;
        --*len;

        ctx->zlib_flags &= ~ORIG_NAME;
    }

    if ((ctx->zlib_flags & COMMENT)) {
        /* Consume nul terminated string. */
        while (*len && **data) {
            ++*data;
            --*len;
        }
        if (!*len) {
            return APR_INCOMPLETE;
        }
        /* .. and nul. */
        ++*data;
        --*len;

        ctx->zlib_flags &= ~COMMENT;
    }

    if ((ctx->zlib_flags & HEAD_CRC)) {
        /* Consume CRC16 (2 octets). */
        if (ctx->consume_pos == 0) {
            if (!*len) {
                return APR_INCOMPLETE;
            }
            ctx->consume_pos++;
            ++*data;
            --*len;
        }
        if (!*len) {
            return APR_INCOMPLETE;
        }
        ++*data;
        --*len;
        
        ctx->consume_pos = 0;
        ctx->zlib_flags &= ~HEAD_CRC;
    }

    return APR_SUCCESS;
}

/* This is the deflate input filter (inflates).  */
static apr_status_t deflate_in_filter(ap_filter_t *f,
                                      apr_bucket_brigade *bb,
@@ -840,10 +950,10 @@ 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) {
        char deflate_hdr[10];
    if (!ctx || ctx->header_len < sizeof(ctx->header)) {
        apr_size_t len;

        if (!ctx) {
            /* only work on main request/no subrequests */
            if (!ap_is_initial_req(r)) {
                ap_remove_input_filter(f);
@@ -872,32 +982,66 @@ static apr_status_t deflate_in_filter(ap_filter_t *f,
            ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
            ctx->proc_bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
            ctx->buffer = apr_palloc(r->pool, c->bufferSize);
        }

        rv = ap_get_brigade(f->next, ctx->bb, AP_MODE_READBYTES, block, 10);
        if (rv != APR_SUCCESS) {
        do {
            apr_brigade_cleanup(ctx->bb);

            len = sizeof(ctx->header) - ctx->header_len;
            rv = ap_get_brigade(f->next, ctx->bb, AP_MODE_READBYTES, block,
                                len);

            /* ap_get_brigade may return success with an empty brigade for
             * a non-blocking read which would block (an empty brigade for
             * a blocking read is an issue which is simply forwarded here).
             */
            if (rv != APR_SUCCESS || APR_BRIGADE_EMPTY(ctx->bb)) {
                return rv;
            }

        apr_table_unset(r->headers_in, "Content-Length");
        apr_table_unset(r->headers_in, "Content-MD5");
            /* zero length body? step aside */
            bkt = APR_BRIGADE_FIRST(ctx->bb);
            if (APR_BUCKET_IS_EOS(bkt)) {
                if (ctx->header_len) {
                    /* If the header was (partially) read it's an error, this
                     * is not a gzip Content-Encoding, as claimed.
                     */
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                                  "Encountered premature end-of-stream while "
                                  "reading inflate header");
                    return APR_EGENERAL;
                }
                APR_BUCKET_REMOVE(bkt);
                APR_BRIGADE_INSERT_TAIL(bb, bkt);
                ap_remove_input_filter(f);
                return APR_SUCCESS;
            }

        len = 10;
        rv = apr_brigade_flatten(ctx->bb, deflate_hdr, &len);
            rv = apr_brigade_flatten(ctx->bb,
                                     ctx->header + ctx->header_len, &len);
            if (rv != APR_SUCCESS) {
                return rv;
            }
            if (len && !ctx->header_len) {
                apr_table_unset(r->headers_in, "Content-Length");
                apr_table_unset(r->headers_in, "Content-MD5");
            }
            ctx->header_len += len;

        } while (ctx->header_len < sizeof(ctx->header));

        /* We didn't get the magic bytes. */
        if (len != 10 ||
            deflate_hdr[0] != deflate_magic[0] ||
            deflate_hdr[1] != deflate_magic[1]) {
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Failed to inflate input: wrong/partial magic bytes");
        if (ctx->header[0] != deflate_magic[0] ||
            ctx->header[1] != deflate_magic[1]) {
            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
                          "Zlib: Invalid header");
            return APR_EGENERAL;
        }

        /* We can't handle flags for now. */
        if (deflate_hdr[3] != 0) {
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Failed to inflate input: cannot handle deflate flags");
        ctx->zlib_flags = ctx->header[3];
        if ((ctx->zlib_flags & RESERVED)) {
            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
                          "Zlib: Invalid flags %02x", ctx->zlib_flags);
            return APR_EGENERAL;
        }

@@ -930,8 +1074,15 @@ static apr_status_t deflate_in_filter(ap_filter_t *f,
    if (APR_BRIGADE_EMPTY(ctx->proc_bb)) {
        rv = ap_get_brigade(f->next, ctx->bb, mode, block, readbytes);

        /* Don't terminate on EAGAIN (or success with an empty brigade in
         * non-blocking mode), just return focus.
         */
        if (block == APR_NONBLOCK_READ
                && (APR_STATUS_IS_EAGAIN(rv)
                    || (rv == APR_SUCCESS && APR_BRIGADE_EMPTY(ctx->bb)))) {
            return rv;
        }
        if (rv != APR_SUCCESS) {
            /* What about APR_EAGAIN errors? */
            inflateEnd(&ctx->stream);
            return rv;
        }
@@ -945,14 +1096,26 @@ static apr_status_t deflate_in_filter(ap_filter_t *f,

            /* If we actually see the EOS, that means we screwed up! */
            if (APR_BUCKET_IS_EOS(bkt)) {
                if (!ctx->done) {
                    inflateEnd(&ctx->stream);
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                                  "Encountered premature end-of-stream while inflating");
                    return APR_EGENERAL;
                }

                /* Move everything to the returning brigade. */
                APR_BUCKET_REMOVE(bkt);
                APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, bkt);
                break;
            }

            if (APR_BUCKET_IS_FLUSH(bkt)) {
                apr_bucket *tmp_heap;
                apr_bucket *tmp_b;
                zRC = inflate(&(ctx->stream), Z_SYNC_FLUSH);
                if (zRC != Z_OK) {
                    ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
                                  "Zlib error %d inflating data (%s)", zRC,
                                  ctx->stream.msg);
                    inflateEnd(&ctx->stream);
                    return APR_EGENERAL;
                }
@@ -972,26 +1135,55 @@ static apr_status_t deflate_in_filter(ap_filter_t *f,
                }

                ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, len);
                tmp_heap = apr_bucket_heap_create((char *)ctx->buffer, len,
                tmp_b = apr_bucket_heap_create((char *)ctx->buffer, len,
                                                NULL, f->c->bucket_alloc);
                APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
                APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_b);
                ctx->stream.avail_out = c->bufferSize;

                /* Move everything to the returning brigade. */
                /* Flush everything so far in the returning brigade, but continue
                 * reading should EOS/more follow (don't lose them).
                 */
                tmp_b = APR_BUCKET_PREV(bkt);
                APR_BUCKET_REMOVE(bkt);
                APR_BRIGADE_CONCAT(bb, ctx->bb);
                break;
                APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, bkt);
                bkt = tmp_b;
                continue;
            }

            /* sanity check - data after completed compressed body and before eos? */
            if (ctx->done) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                              "Encountered extra data after compressed data");
                return APR_EGENERAL;
            }

            /* read */
            apr_bucket_read(bkt, &data, &len, APR_BLOCK_READ);
            if (!len) {
                continue;
            }
            if (len > APR_INT32_MAX) {
                apr_bucket_split(bkt, APR_INT32_MAX);
                apr_bucket_read(bkt, &data, &len, APR_BLOCK_READ);
            }

            if (ctx->zlib_flags) {
                rv = consume_zlib_flags(ctx, &data, &len);
                if (rv == APR_SUCCESS) {
                    ctx->zlib_flags = 0;
                }
                if (!len) {
                    continue;
                }
            }

            /* pass through zlib inflate. */
            ctx->stream.next_in = (unsigned char *)data;
            ctx->stream.avail_in = len;
            ctx->stream.avail_in = (int)len;

            zRC = Z_OK;

            if (!ctx->validation_buffer) {
                while (ctx->stream.avail_in != 0) {
                    if (ctx->stream.avail_out == 0) {
                        apr_bucket *tmp_heap;
@@ -1028,16 +1220,45 @@ static apr_status_t deflate_in_filter(ap_filter_t *f,
                    zRC = inflate(&ctx->stream, Z_NO_FLUSH);

                    if (zRC == Z_STREAM_END) {
                        ctx->validation_buffer = apr_pcalloc(r->pool,
                                                             VALIDATION_SIZE);
                        ctx->validation_buffer_length = 0;
                        break;
                    }

                    if (zRC != Z_OK) {
                        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
                                      "Zlib error %d inflating data (%s)", zRC,
                                      ctx->stream.msg);
                        inflateEnd(&ctx->stream);
                        return APR_EGENERAL;
                    }
                }
            if (zRC == Z_STREAM_END) {
                apr_bucket *tmp_heap, *eos;
            }

            if (ctx->validation_buffer) {
                apr_bucket *tmp_heap;
                apr_size_t avail, valid;
                unsigned char *buf = ctx->validation_buffer;

                avail = ctx->stream.avail_in;
                valid = (apr_size_t)VALIDATION_SIZE -
                         ctx->validation_buffer_length;

                /*
                 * We have inflated all data. Now try to capture the
                 * validation bytes. We may not have them all available
                 * right now, but capture what is there.
                 */
                if (avail < valid) {
                    memcpy(buf + ctx->validation_buffer_length,
                           ctx->stream.next_in, avail);
                    ctx->validation_buffer_length += avail;
                    continue;
                }
                memcpy(buf + ctx->validation_buffer_length,
                       ctx->stream.next_in, valid);
                ctx->validation_buffer_length += valid;

                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                              "Zlib: Inflated %ld to %ld : URL %s",
@@ -1052,34 +1273,37 @@ static apr_status_t deflate_in_filter(ap_filter_t *f,
                APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, tmp_heap);
                ctx->stream.avail_out = c->bufferSize;

                /* Is the remaining 8 bytes already in the avail stream? */
                if (ctx->stream.avail_in >= 8) {
                {
                    unsigned long compCRC, compLen;
                    compCRC = getLong(ctx->stream.next_in);
                    compCRC = getLong(buf);
                    if (ctx->crc != compCRC) {
                        inflateEnd(&ctx->stream);
                        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
                                      "Zlib: CRC error inflating data");
                        return APR_EGENERAL;
                    }
                    ctx->stream.next_in += 4;
                    compLen = getLong(ctx->stream.next_in);
                    compLen = getLong(buf + VALIDATION_SIZE / 2);
                    /* gzip stores original size only as 4 byte value */
                    if ((ctx->stream.total_out & 0xFFFFFFFF) != compLen) {
                        inflateEnd(&ctx->stream);
                        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
                                      "Zlib: Length %ld of inflated data does "
                                      "not match expected value %ld",
                                      ctx->stream.total_out, compLen);
                        return APR_EGENERAL;
                    }
                }
                else {
                    /* FIXME: We need to grab the 8 verification bytes
                     * from the wire! */
                    inflateEnd(&ctx->stream);
                    return APR_EGENERAL;
                }

                inflateEnd(&ctx->stream);

                eos = apr_bucket_eos_create(f->c->bucket_alloc);
                APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, eos);
                break;
                ctx->done = 1;

                /* Did we have trailing data behind the closing 8 bytes? */
                if (avail > valid) {
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                                  "Encountered extra data after compressed data");
                    return APR_EGENERAL;
                }
            }

        }
@@ -1113,6 +1337,9 @@ static apr_status_t deflate_in_filter(ap_filter_t *f,
        newbb = apr_brigade_split(ctx->proc_bb, bkt);
        APR_BRIGADE_CONCAT(bb, ctx->proc_bb);
        APR_BRIGADE_CONCAT(ctx->proc_bb, newbb);
        if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) {
            ap_remove_input_filter(f);
        }
    }

    return APR_SUCCESS;
@@ -1123,8 +1350,6 @@ static apr_status_t deflate_in_filter(ap_filter_t *f,
static apr_status_t inflate_out_filter(ap_filter_t *f,
                                      apr_bucket_brigade *bb)
{
    int zlib_method;
    int zlib_flags;
    apr_bucket *e;
    request_rec *r = f->r;
    deflate_ctx *ctx = f->ctx;
@@ -1209,8 +1434,6 @@ static apr_status_t inflate_out_filter(ap_filter_t *f,
        /* initialize inflate output buffer */
        ctx->stream.next_out = ctx->buffer;
        ctx->stream.avail_out = c->bufferSize;

        ctx->inflate_init = 0;
    }

    while (!APR_BRIGADE_EMPTY(bb))
@@ -1320,55 +1543,67 @@ static apr_status_t inflate_out_filter(ap_filter_t *f,

        /* read */
        apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
        if (!len) {
            apr_bucket_delete(e);
            continue;
        }
        if (len > APR_INT32_MAX) {
            apr_bucket_split(e, APR_INT32_MAX);
            apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
        }

        /* first bucket contains zlib header */
        if (!ctx->inflate_init++) {
            if (len < 10) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                              "Insufficient data for inflate");
                return APR_EGENERAL;
        if (ctx->header_len < sizeof(ctx->header)) {
            apr_size_t rem;

            rem = sizeof(ctx->header) - ctx->header_len;
            if (len < rem) {
                memcpy(ctx->header + ctx->header_len, data, len);
                ctx->header_len += len;
                apr_bucket_delete(e);
                continue;
            }
            else  {
                zlib_method = data[2];
                zlib_flags = data[3];
            memcpy(ctx->header + ctx->header_len, data, rem);
            ctx->header_len += rem;
            {
                int zlib_method;
                zlib_method = ctx->header[2];
                if (zlib_method != Z_DEFLATED) {
                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                                  "inflate: data not deflated!");
                    ap_remove_output_filter(f);
                    return ap_pass_brigade(f->next, bb);
                }
                if (data[0] != deflate_magic[0] ||
                    data[1] != deflate_magic[1] ||
                    (zlib_flags & RESERVED) != 0) {
                if (ctx->header[0] != deflate_magic[0] ||
                    ctx->header[1] != deflate_magic[1]) {
                        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                                      "inflate: bad header");
                    return APR_EGENERAL ;
                }
                data += 10 ;
                len -= 10 ;
           }
           if (zlib_flags & EXTRA_FIELD) {
               unsigned int bytes = (unsigned int)(data[0]);
               bytes += ((unsigned int)(data[1])) << 8;
               bytes += 2;
               if (len < bytes) {
                ctx->zlib_flags = ctx->header[3];
                if ((ctx->zlib_flags & RESERVED)) {
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                                 "inflate: extra field too big (not "
                                 "supported)");
                                  "inflate: bad flags %02x",
                                  ctx->zlib_flags);
                    return APR_EGENERAL;
                }
               data += bytes;
               len -= bytes;
            }
           if (zlib_flags & ORIG_NAME) {
               while (len-- && *data++);
            if (len == rem) {
                apr_bucket_delete(e);
                continue;
            }
           if (zlib_flags & COMMENT) {
               while (len-- && *data++);
            data += rem;
            len -= rem;
        }
           if (zlib_flags & HEAD_CRC) {
                len -= 2;
                data += 2;

        if (ctx->zlib_flags) {
            rv = consume_zlib_flags(ctx, &data, &len);
            if (rv == APR_SUCCESS) {
                ctx->zlib_flags = 0;
            }
            if (!len) {
                apr_bucket_delete(e);
                continue;
            }
        }

@@ -1458,6 +1693,9 @@ static apr_status_t inflate_out_filter(ap_filter_t *f,
            }

            if (zRC != Z_OK) {
                ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
                              "Zlib error %d inflating data (%s)", zRC,
                              ctx->stream.msg);
                return APR_EGENERAL;
            }
        }