diff options
author | Stefan Eissing <icing@apache.org> | 2017-04-14 15:08:32 +0000 |
---|---|---|
committer | Stefan Eissing <icing@apache.org> | 2017-04-14 15:08:32 +0000 |
commit | c66d4fc74ee76fcbb6c1494ae0dbb95d5bb4179f (patch) | |
tree | 6cae8f8af236367c1e4c7c75cbb3bad73460631c | |
parent | 586ce2b9ea7167197074f6b5ad8d3ff85993ccfd (diff) | |
download | httpd-c66d4fc74ee76fcbb6c1494ae0dbb95d5bb4179f.tar.gz |
On the trunk:
mod_http2: only when 'HttpProtocolOptions Unsafe' is configured, will
control characters in response headers or trailers be forwarded to the
client. Otherwise, in the default configuration, a request will eiher
fail with status 500 or the stream will be reset by a RST_STREAM frame.
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1791377 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r-- | CHANGES | 6 | ||||
-rw-r--r-- | modules/http2/h2.h | 2 | ||||
-rw-r--r-- | modules/http2/h2_headers.c | 16 | ||||
-rw-r--r-- | modules/http2/h2_headers.h | 6 | ||||
-rw-r--r-- | modules/http2/h2_request.c | 14 | ||||
-rw-r--r-- | modules/http2/h2_session.c | 66 | ||||
-rw-r--r-- | modules/http2/h2_util.c | 166 | ||||
-rw-r--r-- | modules/http2/h2_util.h | 16 |
8 files changed, 201 insertions, 91 deletions
@@ -1,6 +1,12 @@ -*- coding: utf-8 -*- Changes with Apache 2.5.0 + *) mod_http2: only when 'HttpProtocolOptions Unsafe' is configured, will + control characters in response headers or trailers be forwarded to the + client. Otherwise, in the default configuration, a request will eiher + fail with status 500 or the stream will be reset by a RST_STREAM frame. + [Stefan Eissing] + *) core: Disallow multiple Listen on the same IP:port when listener buckets are configured (ListenCoresBucketsRatio > 0), consistently with the single bucket case (default), thus avoiding the leak of the corresponding socket diff --git a/modules/http2/h2.h b/modules/http2/h2.h index df809fd411..ad6979c182 100644 --- a/modules/http2/h2.h +++ b/modules/http2/h2.h @@ -154,5 +154,7 @@ typedef int h2_stream_pri_cmp(int stream_id1, int stream_id2, void *ctx); #define H2_TASK_ID_NOTE "http2-task-id" #define H2_FILTER_DEBUG_NOTE "http2-debug" +#define H2_HDR_CONFORMANCE "http2-hdr-conformance" +#define H2_HDR_CONFORMANCE_UNSAFE "unsafe" #endif /* defined(__mod_h2__h2__) */ diff --git a/modules/http2/h2_headers.c b/modules/http2/h2_headers.c index 8add79f507..ce7eaec2b3 100644 --- a/modules/http2/h2_headers.c +++ b/modules/http2/h2_headers.c @@ -32,6 +32,12 @@ #include "h2_headers.h" +static int is_unsafe(server_rec *s) +{ + core_server_config *conf = ap_get_core_module_config(s->module_config); + return (conf->http_conformance == AP_HTTP_CONFORMANCE_UNSAFE); +} + typedef struct { apr_bucket_refcount refcount; h2_headers *headers; @@ -132,9 +138,19 @@ h2_headers *h2_headers_rcreate(request_rec *r, int status, headers->status = H2_ERR_HTTP_1_1_REQUIRED; } } + if (is_unsafe(r->server)) { + apr_table_setn(headers->notes, H2_HDR_CONFORMANCE, + H2_HDR_CONFORMANCE_UNSAFE); + } return headers; } +h2_headers *h2_headers_copy(apr_pool_t *pool, h2_headers *h) +{ + return h2_headers_create(h->status, apr_table_copy(pool, h->headers), + apr_table_copy(pool, h->notes), pool); +} + h2_headers *h2_headers_die(apr_status_t type, const h2_request *req, apr_pool_t *pool) { diff --git a/modules/http2/h2_headers.h b/modules/http2/h2_headers.h index 412e93fae2..95e99ee81a 100644 --- a/modules/http2/h2_headers.h +++ b/modules/http2/h2_headers.h @@ -56,6 +56,12 @@ h2_headers *h2_headers_rcreate(request_rec *r, int status, apr_table_t *header, apr_pool_t *pool); /** + * Clone the headers into another pool. This will not copy any + * header strings. + */ +h2_headers *h2_headers_copy(apr_pool_t *pool, h2_headers *h); + +/** * Create the headers for the given error. * @param stream_id id of the stream to create the headers for * @param type the error code diff --git a/modules/http2/h2_request.c b/modules/http2/h2_request.c index 25dc9711b5..9811a5cb17 100644 --- a/modules/http2/h2_request.c +++ b/modules/http2/h2_request.c @@ -39,16 +39,15 @@ typedef struct { apr_table_t *headers; apr_pool_t *pool; + apr_status_t status; } h1_ctx; static int set_h1_header(void *ctx, const char *key, const char *value) { h1_ctx *x = ctx; - size_t klen = strlen(key); - if (!h2_req_ignore_header(key, klen)) { - h2_headers_add_h1(x->headers, x->pool, key, klen, value, strlen(value)); - } - return 1; + x->status = h2_req_add_header(x->headers, x->pool, key, strlen(key), + value, strlen(value)); + return (x->status == APR_SUCCESS)? 1 : 0; } apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool, @@ -90,10 +89,11 @@ apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool, x.pool = pool; x.headers = req->headers; + x.status = APR_SUCCESS; apr_table_do(set_h1_header, &x, r->headers_in, NULL); *preq = req; - return APR_SUCCESS; + return x.status; } apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool, @@ -143,7 +143,7 @@ apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool, } else { /* non-pseudo header, append to work bucket of stream */ - status = h2_headers_add_h1(req->headers, pool, name, nlen, value, vlen); + status = h2_req_add_header(req->headers, pool, name, nlen, value, vlen); } return status; diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c index 997d6fb00e..185580bf25 100644 --- a/modules/http2/h2_session.c +++ b/modules/http2/h2_session.c @@ -1080,13 +1080,16 @@ struct h2_stream *h2_session_push(h2_session *session, h2_stream *is, { h2_stream *stream; h2_ngheader *ngh; - int nid; + apr_status_t status; + int nid = 0; - ngh = h2_util_ngheader_make_req(is->pool, push->req); - nid = nghttp2_submit_push_promise(session->ngh2, 0, is->id, - ngh->nv, ngh->nvlen, NULL); - if (nid <= 0) { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, + status = h2_req_create_ngheader(&ngh, is->pool, push->req); + if (status == APR_SUCCESS) { + nid = nghttp2_submit_push_promise(session->ngh2, 0, is->id, + ngh->nv, ngh->nvlen, NULL); + } + if (status != APR_SUCCESS || nid <= 0) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, H2_STRM_LOG(APLOGNO(03075), is, "submitting push promise fail: %s"), nghttp2_strerror(nid)); return NULL; @@ -1280,16 +1283,25 @@ static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream, else if (stream->has_response) { h2_ngheader *nh; - nh = h2_util_ngheader_make(stream->pool, headers->headers); - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, - H2_STRM_LOG(APLOGNO(03072), stream, "submit %d trailers"), (int)nh->nvlen); - rv = nghttp2_submit_trailer(session->ngh2, stream->id, nh->nv, nh->nvlen); + status = h2_res_create_ngtrailer(&nh, stream->pool, headers); + + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, + H2_STRM_LOG(APLOGNO(03072), stream, "submit %d trailers"), + (int)nh->nvlen); + if (status == APR_SUCCESS) { + rv = nghttp2_submit_trailer(session->ngh2, stream->id, + nh->nv, nh->nvlen); + } + else { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, + H2_STRM_LOG(APLOGNO(), stream, "invalid trailers")); + h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR); + } goto leave; } else { nghttp2_data_provider provider, *pprovider = NULL; h2_ngheader *ngh; - apr_table_t *hout; const char *note; ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, @@ -1335,17 +1347,16 @@ static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream, } h2_session_set_prio(session, stream, stream->pref_priority); - hout = headers->headers; note = apr_table_get(headers->notes, H2_FILTER_DEBUG_NOTE); if (note && !strcmp("on", note)) { int32_t connFlowIn, connFlowOut; connFlowIn = nghttp2_session_get_effective_local_window_size(session->ngh2); connFlowOut = nghttp2_session_get_remote_window_size(session->ngh2); - hout = apr_table_clone(stream->pool, hout); - apr_table_setn(hout, "conn-flow-in", + headers = h2_headers_copy(stream->pool, headers); + apr_table_setn(headers->headers, "conn-flow-in", apr_itoa(stream->pool, connFlowIn)); - apr_table_setn(hout, "conn-flow-out", + apr_table_setn(headers->headers, "conn-flow-out", apr_itoa(stream->pool, connFlowOut)); } @@ -1357,17 +1368,24 @@ static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream, goto leave; } - ngh = h2_util_ngheader_make_res(stream->pool, headers->status, hout); - rv = nghttp2_submit_response(session->ngh2, stream->id, - ngh->nv, ngh->nvlen, pprovider); - stream->has_response = h2_headers_are_response(headers); - session->have_written = 1; - - if (stream->initiated_on) { - ++session->pushes_submitted; + status = h2_res_create_ngheader(&ngh, stream->pool, headers); + if (status == APR_SUCCESS) { + rv = nghttp2_submit_response(session->ngh2, stream->id, + ngh->nv, ngh->nvlen, pprovider); + stream->has_response = h2_headers_are_response(headers); + session->have_written = 1; + + if (stream->initiated_on) { + ++session->pushes_submitted; + } + else { + ++session->responses_submitted; + } } else { - ++session->responses_submitted; + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, + H2_STRM_LOG(APLOGNO(), stream, "invalid response")); + h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR); } } diff --git a/modules/http2/h2_util.c b/modules/http2/h2_util.c index 0ac65ccf65..f4c3aab3a9 100644 --- a/modules/http2/h2_util.c +++ b/modules/http2/h2_util.c @@ -1382,89 +1382,150 @@ static int count_header(void *ctx, const char *key, const char *value) return 1; } -#define NV_ADD_LIT_CS(nv, k, v) add_header(nv, k, sizeof(k) - 1, v, strlen(v)) -#define NV_ADD_CS_CS(nv, k, v) add_header(nv, k, strlen(k), v, strlen(v)) +static const char *inv_field_name_chr(const char *token) +{ + const char *p = ap_scan_http_token(token); + if (p == token && *p == ':') { + p = ap_scan_http_token(++p); + } + return (p && *p)? p : NULL; +} -static int add_header(h2_ngheader *ngh, - const char *key, size_t key_len, - const char *value, size_t val_len) +static const char *inv_field_value_chr(const char *token) { - nghttp2_nv *nv = &ngh->nv[ngh->nvlen++]; - + const char *p = ap_scan_http_field_content(token); + return (p && *p)? p : NULL; +} + +typedef struct ngh_ctx { + apr_pool_t *p; + int unsafe; + h2_ngheader *ngh; + apr_status_t status; +} ngh_ctx; + +static int add_header(ngh_ctx *ctx, const char *key, const char *value) +{ + nghttp2_nv *nv = &(ctx->ngh)->nv[(ctx->ngh)->nvlen++]; + const char *p; + + if (!ctx->unsafe) { + if ((p = inv_field_name_chr(key))) { + ap_log_perror(APLOG_MARK, APLOG_TRACE1, APR_EINVAL, ctx->p, + "h2_request: head field '%s: %s' has invalid char %s", + key, value, p); + ctx->status = APR_EINVAL; + return 0; + } + if ((p = inv_field_value_chr(value))) { + ap_log_perror(APLOG_MARK, APLOG_TRACE1, APR_EINVAL, ctx->p, + "h2_request: head field '%s: %s' has invalid char %s", + key, value, p); + ctx->status = APR_EINVAL; + return 0; + } + } nv->name = (uint8_t*)key; - nv->namelen = key_len; + nv->namelen = strlen(key); nv->value = (uint8_t*)value; - nv->valuelen = val_len; + nv->valuelen = strlen(value); + return 1; } static int add_table_header(void *ctx, const char *key, const char *value) { if (!h2_util_ignore_header(key)) { - add_header(ctx, key, strlen(key), value, strlen(value)); + add_header(ctx, key, value); } return 1; } - -h2_ngheader *h2_util_ngheader_make(apr_pool_t *p, apr_table_t *header) +static apr_status_t ngheader_create(h2_ngheader **ph, apr_pool_t *p, + int unsafe, size_t key_count, + const char *keys[], const char *values[], + apr_table_t *headers) { - h2_ngheader *ngh; - size_t n; + ngh_ctx ctx; + size_t n, i; - n = 0; - apr_table_do(count_header, &n, header, NULL); + ctx.p = p; + ctx.unsafe = unsafe; - ngh = apr_pcalloc(p, sizeof(h2_ngheader)); - ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv)); - apr_table_do(add_table_header, ngh, header, NULL); + n = key_count; + apr_table_do(count_header, &n, headers, NULL); + + *ph = ctx.ngh = apr_pcalloc(p, sizeof(h2_ngheader)); + if (!ctx.ngh) { + return APR_ENOMEM; + } + + ctx.ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv)); + if (!ctx.ngh->nv) { + return APR_ENOMEM; + } + + ctx.status = APR_SUCCESS; + for (i = 0; i < key_count; ++i) { + if (!add_header(&ctx, keys[i], values[i])) { + return ctx.status; + } + } + + apr_table_do(add_table_header, &ctx, headers, NULL); - return ngh; + return ctx.status; } -h2_ngheader *h2_util_ngheader_make_res(apr_pool_t *p, - int http_status, - apr_table_t *header) +static int is_unsafe(h2_headers *h) { - h2_ngheader *ngh; - size_t n; - - n = 1; - apr_table_do(count_header, &n, header, NULL); - - ngh = apr_pcalloc(p, sizeof(h2_ngheader)); - ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv)); - NV_ADD_LIT_CS(ngh, ":status", apr_psprintf(p, "%d", http_status)); - apr_table_do(add_table_header, ngh, header, NULL); + const char *v = apr_table_get(h->notes, H2_HDR_CONFORMANCE); + return (v && !strcmp(v, H2_HDR_CONFORMANCE_UNSAFE)); +} - return ngh; +apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p, + h2_headers *headers) +{ + return ngheader_create(ph, p, is_unsafe(headers), + 0, NULL, NULL, headers->headers); +} + +apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p, + h2_headers *headers) +{ + const char *keys[] = { + ":status" + }; + const char *values[] = { + apr_psprintf(p, "%d", headers->status) + }; + return ngheader_create(ph, p, is_unsafe(headers), + H2_ALEN(keys), keys, values, headers->headers); } -h2_ngheader *h2_util_ngheader_make_req(apr_pool_t *p, - const struct h2_request *req) +apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p, + const struct h2_request *req) { - h2_ngheader *ngh; - size_t n; + const char *keys[] = { + ":scheme", + ":authority", + ":path", + ":method", + }; + const char *values[] = { + req->scheme, + req->authority, + req->path, + req->method, + }; - ap_assert(req); ap_assert(req->scheme); ap_assert(req->authority); ap_assert(req->path); ap_assert(req->method); - n = 4; - apr_table_do(count_header, &n, req->headers, NULL); - - ngh = apr_pcalloc(p, sizeof(h2_ngheader)); - ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv)); - NV_ADD_LIT_CS(ngh, ":scheme", req->scheme); - NV_ADD_LIT_CS(ngh, ":authority", req->authority); - NV_ADD_LIT_CS(ngh, ":path", req->path); - NV_ADD_LIT_CS(ngh, ":method", req->method); - apr_table_do(add_table_header, ngh, req->headers, NULL); - - return ngh; + return ngheader_create(ph, p, 0, H2_ALEN(keys), keys, values, req->headers); } /******************************************************************************* @@ -1481,7 +1542,6 @@ typedef struct { #define H2_LIT_ARGS(a) (a),H2_ALEN(a) static literal IgnoredRequestHeaders[] = { -/*H2_DEF_LITERAL("expect"),*/ H2_DEF_LITERAL("upgrade"), H2_DEF_LITERAL("connection"), H2_DEF_LITERAL("keep-alive"), @@ -1547,7 +1607,7 @@ int h2_res_ignore_trailer(const char *name, size_t len) return ignore_header(H2_LIT_ARGS(IgnoredResponseTrailers), name, len); } -apr_status_t h2_headers_add_h1(apr_table_t *headers, apr_pool_t *pool, +apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool, const char *name, size_t nlen, const char *value, size_t vlen) { diff --git a/modules/http2/h2_util.h b/modules/http2/h2_util.h index 9b408fad3d..4b901bfe68 100644 --- a/modules/http2/h2_util.h +++ b/modules/http2/h2_util.h @@ -341,19 +341,21 @@ const char *h2_util_base64url_encode(const char *data, int h2_util_ignore_header(const char *name); +struct h2_headers; + typedef struct h2_ngheader { nghttp2_nv *nv; apr_size_t nvlen; } h2_ngheader; -h2_ngheader *h2_util_ngheader_make(apr_pool_t *p, apr_table_t *header); -h2_ngheader *h2_util_ngheader_make_res(apr_pool_t *p, - int http_status, - apr_table_t *header); -h2_ngheader *h2_util_ngheader_make_req(apr_pool_t *p, - const struct h2_request *req); +apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p, + struct h2_headers *headers); +apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p, + struct h2_headers *headers); +apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p, + const struct h2_request *req); -apr_status_t h2_headers_add_h1(apr_table_t *headers, apr_pool_t *pool, +apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool, const char *name, size_t nlen, const char *value, size_t vlen); |