You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2124 lines
84 KiB
2124 lines
84 KiB
|
|
https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2016-8743 |
|
|
|
diff -uap httpd-2.4.6/docs/manual/mod/core.html.en.cve8743 httpd-2.4.6/docs/manual/mod/core.html.en |
|
--- httpd-2.4.6/docs/manual/mod/core.html.en.cve8743 |
|
+++ httpd-2.4.6/docs/manual/mod/core.html.en |
|
@@ -67,6 +67,7 @@ |
|
<li><img alt="" src="../images/down.gif" /> <a href="#forcetype">ForceType</a></li> |
|
<li><img alt="" src="../images/down.gif" /> <a href="#gprofdir">GprofDir</a></li> |
|
<li><img alt="" src="../images/down.gif" /> <a href="#hostnamelookups">HostnameLookups</a></li> |
|
+<li><img alt="" src="../images/down.gif" /> <a href="#httpprotocoloptions">HttpProtocolOptions</a></li> |
|
<li><img alt="" src="../images/down.gif" /> <a href="#if"><If></a></li> |
|
<li><img alt="" src="../images/down.gif" /> <a href="#ifdefine"><IfDefine></a></li> |
|
<li><img alt="" src="../images/down.gif" /> <a href="#ifmodule"><IfModule></a></li> |
|
@@ -93,6 +94,7 @@ |
|
<li><img alt="" src="../images/down.gif" /> <a href="#namevirtualhost">NameVirtualHost</a></li> |
|
<li><img alt="" src="../images/down.gif" /> <a href="#options">Options</a></li> |
|
<li><img alt="" src="../images/down.gif" /> <a href="#protocol">Protocol</a></li> |
|
+<li><img alt="" src="../images/down.gif" /> <a href="#registerhttpmethod">RegisterHttpMethod</a></li> |
|
<li><img alt="" src="../images/down.gif" /> <a href="#rlimitcpu">RLimitCPU</a></li> |
|
<li><img alt="" src="../images/down.gif" /> <a href="#rlimitmem">RLimitMEM</a></li> |
|
<li><img alt="" src="../images/down.gif" /> <a href="#rlimitnproc">RLimitNPROC</a></li> |
|
@@ -1918,6 +1920,74 @@ |
|
|
|
</div> |
|
<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div> |
|
+<div class="directive-section"><h2><a name="HttpProtocolOptions" id="HttpProtocolOptions">HttpProtocolOptions</a> <a name="httpprotocoloptions" id="httpprotocoloptions">Directive</a></h2> |
|
+<table class="directive"> |
|
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Modify restrictions on HTTP Request Messages</td></tr> |
|
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>HttpProtocolOptions [Strict|Unsafe] [RegisteredMethods|LenientMethods] |
|
+ [Allow0.9|Require1.0]</code></td></tr> |
|
+<tr><th><a href="directive-dict.html#Default">Default:</a></th><td><code>HttpProtocolOptions Strict LenientMethods Allow0.9</code></td></tr> |
|
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config, virtual host</td></tr> |
|
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Core</td></tr> |
|
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>core</td></tr> |
|
+<tr><th><a href="directive-dict.html#Compatibility">Compatibility:</a></th><td>2.2.32 or 2.4.24 and later</td></tr> |
|
+</table> |
|
+ <p>This directive changes the rules applied to the HTTP Request Line |
|
+ (<a href="https://tools.ietf.org/html/rfc7230#section-3.1.1">RFC 7230 §3.1.1</a>) and the HTTP Request Header Fields |
|
+ (<a href="https://tools.ietf.org/html/rfc7230#section-3.2">RFC 7230 §3.2</a>), which are now applied by default or using |
|
+ the <code>Strict</code> option. Due to legacy modules, applications or |
|
+ custom user-agents which must be deperecated the <code>Unsafe</code> |
|
+ option has been added to revert to the legacy behaviors. These rules |
|
+ are applied prior to request processing, so must be configured at the |
|
+ global or default (first) matching virtual host section, by IP/port |
|
+ interface (and not by name) to be honored.</p> |
|
+ |
|
+ <p>Prior to the introduction of this directive, the Apache HTTP Server |
|
+ request message parsers were tolerant of a number of forms of input |
|
+ which did not conform to the protocol. |
|
+ <a href="https://tools.ietf.org/html/rfc7230#section-9.4">RFC 7230 §9.4 Request Splitting</a> and |
|
+ <a href="https://tools.ietf.org/html/rfc7230#section-9.5">§9.5 Response Smuggling</a> call out only two of the potential |
|
+ risks of accepting non-conformant request messages, while |
|
+ <a href="https://tools.ietf.org/html/rfc7230#section-3.5">RFC 7230 §3.5</a> "Message Parsing Robustness" identify the |
|
+ risks of accepting obscure whitespace and request message formatting. |
|
+ As of the introduction of this directive, all grammer rules of the |
|
+ specification are enforced in the default <code>Strict</code> operating |
|
+ mode, and the strict whitespace suggested by section 3.5 is enforced |
|
+ and cannot be relaxed.</p> |
|
+ |
|
+ <p>Users are strongly cautioned against toggling the <code>Unsafe</code> |
|
+ mode of operation, particularly on outward-facing, publicly accessible |
|
+ server deployments. If an interface is required for faulty monitoring |
|
+ or other custom service consumers running on an intranet, users should |
|
+ toggle the Unsafe option only on a specific virtual host configured |
|
+ to service their internal private network.</p> |
|
+ |
|
+ <p>Reviewing the messages logged to the <code class="directive">ErrorLog</code>, |
|
+ configured with <code class="directive">LogLevel</code> <code>debug</code> level, |
|
+ can help identify such faulty requests along with their origin. |
|
+ Users should pay particular attention to the 400 responses in the access |
|
+ log for invalid requests which were unexpectedly rejected.</p> |
|
+ |
|
+ <p><a href="https://tools.ietf.org/html/rfc7231#section-4.1">RFC 7231 §4.1</a> "Request Methods" "Overview" requires that |
|
+ origin servers shall respond with an error when an unsupported method |
|
+ is encountered in the request line. This already happens when the |
|
+ <code>LenientMethods</code> option is used, but administrators may wish |
|
+ to toggle the <code>RegisteredMethods</code> option and register any |
|
+ non-standard methods using the <code class="directive">RegisterHttpMethod</code> |
|
+ directive, particularly if the <code>Unsafe</code> option has been toggled. |
|
+ The <code>RegisteredMethods</code> option should <strong>not</strong> |
|
+ be toggled for forward proxy hosts, as the methods supported by the |
|
+ origin servers are unknown to the proxy server.</p> |
|
+ |
|
+ <p><a href="https://tools.ietf.org/html/rfc2616#section-19.6">RFC 2616 §19.6</a> "Compatibility With Previous Versions" had |
|
+ encouraged HTTP servers to support legacy HTTP/0.9 requests. RFC 7230 |
|
+ superceeds this with "The expectation to support HTTP/0.9 requests has |
|
+ been removed" and offers additional comments in |
|
+ <a href="https://tools.ietf.org/html/rfc7230#appendix-A">RFC 7230 Appendix A</a>. The <code>Require1.0</code> option allows |
|
+ the user to remove support of the default <code>Allow0.9</code> option's |
|
+ behavior.</p> |
|
+ |
|
+</div> |
|
+<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div> |
|
<div class="directive-section"><h2><a name="If" id="If"><If></a> <a name="if" id="if">Directive</a></h2> |
|
<table class="directive"> |
|
<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Contains directives that apply only if a condition is |
|
@@ -3541,6 +3611,23 @@ |
|
</ul> |
|
</div> |
|
<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div> |
|
+<div class="directive-section"><h2><a name="RegisterHttpMethod" id="RegisterHttpMethod">RegisterHttpMethod</a> <a name="registerhttpmethod" id="registerhttpmethod">Directive</a></h2> |
|
+<table class="directive"> |
|
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Register non-standard HTTP methods</td></tr> |
|
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>RegisterHttpMethod <var>method</var> [<var>method</var> [...]]</code></td></tr> |
|
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config</td></tr> |
|
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Core</td></tr> |
|
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>core</td></tr> |
|
+</table> |
|
+<p>HTTP Methods that are not conforming to the relvant RFCs are normally |
|
+rejected by request processing in Apache HTTPD. To avoid this, modules |
|
+can register non-standard HTTP methods they support. |
|
+The <code class="directive">RegisterHttpMethod</code> allows to register such |
|
+methods manually. This can be useful for if such methods are forwared |
|
+for external processing, e.g. to a CGI script.</p> |
|
+ |
|
+</div> |
|
+<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div> |
|
<div class="directive-section"><h2><a name="RLimitCPU" id="RLimitCPU">RLimitCPU</a> <a name="rlimitcpu" id="rlimitcpu">Directive</a></h2> |
|
<table class="directive"> |
|
<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Limits the CPU consumption of processes launched |
|
diff -uap httpd-2.4.6/include/http_core.h.cve8743 httpd-2.4.6/include/http_core.h |
|
--- httpd-2.4.6/include/http_core.h.cve8743 |
|
+++ httpd-2.4.6/include/http_core.h |
|
@@ -668,6 +668,21 @@ |
|
#define AP_MERGE_TRAILERS_DISABLE 2 |
|
int merge_trailers; |
|
|
|
+#define AP_HTTP09_UNSET 0 |
|
+#define AP_HTTP09_ENABLE 1 |
|
+#define AP_HTTP09_DISABLE 2 |
|
+ char http09_enable; |
|
+ |
|
+#define AP_HTTP_CONFORMANCE_UNSET 0 |
|
+#define AP_HTTP_CONFORMANCE_UNSAFE 1 |
|
+#define AP_HTTP_CONFORMANCE_STRICT 2 |
|
+ char http_conformance; |
|
+ |
|
+#define AP_HTTP_METHODS_UNSET 0 |
|
+#define AP_HTTP_METHODS_LENIENT 1 |
|
+#define AP_HTTP_METHODS_REGISTERED 2 |
|
+ char http_methods; |
|
+ |
|
} core_server_config; |
|
|
|
/* for AddOutputFiltersByType in core.c */ |
|
diff -uap httpd-2.4.6/include/httpd.h.cve8743 httpd-2.4.6/include/httpd.h |
|
--- httpd-2.4.6/include/httpd.h.cve8743 |
|
+++ httpd-2.4.6/include/httpd.h |
|
@@ -1584,6 +1584,28 @@ |
|
*/ |
|
AP_DECLARE(int) ap_unescape_url(char *url); |
|
|
|
+/* Scan a string for field content chars, as defined by RFC7230 section 3.2 |
|
+ * including VCHAR/obs-text, as well as HT and SP |
|
+ * @param ptr The string to scan |
|
+ * @return A pointer to the first (non-HT) ASCII ctrl character. |
|
+ * @note lws and trailing whitespace are scanned, the caller is responsible |
|
+ * for trimming leading and trailing whitespace |
|
+ */ |
|
+AP_DECLARE(const char *) ap_scan_http_field_content(const char *ptr); |
|
+ |
|
+/* Scan a string for token characters, as defined by RFC7230 section 3.2.6 |
|
+ * @param ptr The string to scan |
|
+ * @return A pointer to the first non-token character. |
|
+ */ |
|
+AP_DECLARE(const char *) ap_scan_http_token(const char *ptr); |
|
+ |
|
+/* Scan a string for visible ASCII (0x21-0x7E) or obstext (0x80+) |
|
+ * and return a pointer to the first SP/CTL/NUL character encountered. |
|
+ * @param ptr The string to scan |
|
+ * @return A pointer to the first SP/CTL character. |
|
+ */ |
|
+AP_DECLARE(const char *) ap_scan_vchar_obstext(const char *ptr); |
|
+ |
|
/** |
|
* Unescape a URL, but leaving %2f (slashes) escaped |
|
* @param url The url to unescape |
|
diff -uap httpd-2.4.6/include/http_protocol.h.cve8743 httpd-2.4.6/include/http_protocol.h |
|
--- httpd-2.4.6/include/http_protocol.h.cve8743 |
|
+++ httpd-2.4.6/include/http_protocol.h |
|
@@ -582,17 +582,22 @@ |
|
*/ |
|
AP_CORE_DECLARE(void) ap_parse_uri(request_rec *r, const char *uri); |
|
|
|
+#define AP_GETLINE_FOLD 1 /* Whether to merge continuation lines */ |
|
+#define AP_GETLINE_CRLF 2 /*Whether line ends must be in the form CR LF */ |
|
+ |
|
/** |
|
* Get the next line of input for the request |
|
* @param s The buffer into which to read the line |
|
* @param n The size of the buffer |
|
* @param r The request |
|
- * @param fold Whether to merge continuation lines |
|
+ * @param flags Bit flag of multiple parsing options |
|
+ * AP_GETLINE_FOLD Whether to merge continuation lines |
|
+ * AP_GETLINE_CRLF Whether line ends must be in the form CR LF |
|
* @return The length of the line, if successful |
|
* n, if the line is too big to fit in the buffer |
|
* -1 for miscellaneous errors |
|
*/ |
|
-AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int fold); |
|
+AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int flags); |
|
|
|
/** |
|
* Get the next line of input for the request |
|
@@ -610,7 +615,9 @@ |
|
* @param n The size of the buffer |
|
* @param read The length of the line. |
|
* @param r The request |
|
- * @param fold Whether to merge continuation lines |
|
+ * @param flags Bit flag of multiple parsing options |
|
+ * AP_GETLINE_FOLD Whether to merge continuation lines |
|
+ * AP_GETLINE_CRLF Whether line ends must be in the form CR LF |
|
* @param bb Working brigade to use when reading buckets |
|
* @return APR_SUCCESS, if successful |
|
* APR_ENOSPC, if the line is too big to fit in the buffer |
|
@@ -619,7 +626,7 @@ |
|
#if APR_CHARSET_EBCDIC |
|
AP_DECLARE(apr_status_t) ap_rgetline(char **s, apr_size_t n, |
|
apr_size_t *read, |
|
- request_rec *r, int fold, |
|
+ request_rec *r, int flags, |
|
apr_bucket_brigade *bb); |
|
#else /* ASCII box */ |
|
#define ap_rgetline(s, n, read, r, fold, bb) \ |
|
@@ -629,7 +636,7 @@ |
|
/** @see ap_rgetline */ |
|
AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, |
|
apr_size_t *read, |
|
- request_rec *r, int fold, |
|
+ request_rec *r, int flags, |
|
apr_bucket_brigade *bb); |
|
|
|
/** |
|
diff -uap httpd-2.4.6/modules/http/http_filters.c.cve8743 httpd-2.4.6/modules/http/http_filters.c |
|
--- httpd-2.4.6/modules/http/http_filters.c.cve8743 |
|
+++ httpd-2.4.6/modules/http/http_filters.c |
|
@@ -126,14 +126,15 @@ |
|
|
|
/** |
|
* 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 |
|
+ * There are several error cases: |
|
+ * 1) If the chunk link is misformatted, APR_EINVAL is returned. |
|
+ * 2) If the conversion would require too many bits, APR_EGENERAL is returned. |
|
+ * 3) 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. |
|
+ * A negative chunk length always indicates an overflow error. |
|
*/ |
|
static apr_status_t parse_chunk_size(http_ctx_t *ctx, const char *buffer, |
|
- apr_size_t len, int linelimit) |
|
+ apr_size_t len, int linelimit, int strict) |
|
{ |
|
apr_size_t i = 0; |
|
|
|
@@ -146,6 +147,12 @@ |
|
if (ctx->state == BODY_CHUNK_END |
|
|| ctx->state == BODY_CHUNK_END_LF) { |
|
if (c == LF) { |
|
+ if (strict && (ctx->state != BODY_CHUNK_END_LF)) { |
|
+ /* |
|
+ * CR missing before LF. |
|
+ */ |
|
+ return APR_EINVAL; |
|
+ } |
|
ctx->state = BODY_CHUNK; |
|
} |
|
else if (c == CR && ctx->state == BODY_CHUNK_END) { |
|
@@ -153,7 +160,7 @@ |
|
} |
|
else { |
|
/* |
|
- * LF expected. |
|
+ * CRLF expected. |
|
*/ |
|
return APR_EINVAL; |
|
} |
|
@@ -180,6 +187,12 @@ |
|
} |
|
|
|
if (c == LF) { |
|
+ if (strict && (ctx->state != BODY_CHUNK_LF)) { |
|
+ /* |
|
+ * CR missing before LF. |
|
+ */ |
|
+ return APR_EINVAL; |
|
+ } |
|
if (ctx->remaining) { |
|
ctx->state = BODY_CHUNK_DATA; |
|
} |
|
@@ -201,14 +214,17 @@ |
|
} |
|
else if (ctx->state == BODY_CHUNK_EXT) { |
|
/* |
|
- * Control chars (but tabs) are invalid. |
|
+ * Control chars (excluding tabs) are invalid. |
|
+ * TODO: more precisely limit input |
|
*/ |
|
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). |
|
+ /* Be lenient up to 10 implied *LWS, a legacy of RFC 2616, |
|
+ * and noted as errata to RFC7230; |
|
+ * https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4667 |
|
*/ |
|
ctx->state = BODY_CHUNK_CR; |
|
if (++ctx->chunk_bws > 10) { |
|
@@ -324,7 +340,10 @@ |
|
ap_input_mode_t mode, apr_read_type_e block, |
|
apr_off_t readbytes) |
|
{ |
|
- core_server_config *conf; |
|
+ core_server_config *conf = |
|
+ (core_server_config *) ap_get_module_config(f->r->server->module_config, |
|
+ &core_module); |
|
+ int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); |
|
apr_bucket *e; |
|
http_ctx_t *ctx = f->ctx; |
|
apr_status_t rv; |
|
@@ -332,9 +351,6 @@ |
|
apr_bucket_brigade *bb; |
|
int again; |
|
|
|
- conf = (core_server_config *) |
|
- ap_get_module_config(f->r->server->module_config, &core_module); |
|
- |
|
/* just get out of the way of things we don't want. */ |
|
if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE) { |
|
return ap_get_brigade(f->next, b, mode, block, readbytes); |
|
@@ -526,7 +542,7 @@ |
|
if (rv == APR_SUCCESS) { |
|
parsing = 1; |
|
rv = parse_chunk_size(ctx, buffer, len, |
|
- f->r->server->limit_req_fieldsize); |
|
+ f->r->server->limit_req_fieldsize, strict); |
|
} |
|
if (rv != APR_SUCCESS) { |
|
ap_log_rerror(APLOG_MARK, APLOG_INFO, rv, f->r, APLOGNO(01590) |
|
@@ -668,14 +684,121 @@ |
|
return APR_SUCCESS; |
|
} |
|
|
|
+struct check_header_ctx { |
|
+ request_rec *r; |
|
+ int strict; |
|
+}; |
|
+ |
|
+/* check a single header, to be used with apr_table_do() */ |
|
+static int check_header(struct check_header_ctx *ctx, |
|
+ const char *name, const char **val) |
|
+{ |
|
+ const char *pos, *end; |
|
+ char *dst = NULL; |
|
+ |
|
+ if (name[0] == '\0') { |
|
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(02428) |
|
+ "Empty response header name, aborting request"); |
|
+ return 0; |
|
+ } |
|
+ |
|
+ if (ctx->strict) { |
|
+ end = ap_scan_http_token(name); |
|
+ } |
|
+ else { |
|
+ end = ap_scan_vchar_obstext(name); |
|
+ } |
|
+ if (*end) { |
|
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(02429) |
|
+ "Response header name '%s' contains invalid " |
|
+ "characters, aborting request", |
|
+ name); |
|
+ return 0; |
|
+ } |
|
+ |
|
+ for (pos = *val; *pos; pos = end) { |
|
+ end = ap_scan_http_field_content(pos); |
|
+ if (*end) { |
|
+ if (end[0] != CR || end[1] != LF || (end[2] != ' ' && |
|
+ end[2] != '\t')) { |
|
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(02430) |
|
+ "Response header '%s' value of '%s' contains " |
|
+ "invalid characters, aborting request", |
|
+ name, pos); |
|
+ return 0; |
|
+ } |
|
+ if (!dst) { |
|
+ *val = dst = apr_palloc(ctx->r->pool, strlen(*val) + 1); |
|
+ } |
|
+ } |
|
+ if (dst) { |
|
+ memcpy(dst, pos, end - pos); |
|
+ dst += end - pos; |
|
+ if (*end) { |
|
+ /* skip folding and replace with a single space */ |
|
+ end += 3 + strspn(end + 3, "\t "); |
|
+ *dst++ = ' '; |
|
+ } |
|
+ } |
|
+ } |
|
+ if (dst) { |
|
+ *dst = '\0'; |
|
+ } |
|
+ return 1; |
|
+} |
|
+ |
|
+static int check_headers_table(apr_table_t *t, struct check_header_ctx *ctx) |
|
+{ |
|
+ const apr_array_header_t *headers = apr_table_elts(t); |
|
+ apr_table_entry_t *header; |
|
+ int i; |
|
+ |
|
+ for (i = 0; i < headers->nelts; ++i) { |
|
+ header = &APR_ARRAY_IDX(headers, i, apr_table_entry_t); |
|
+ if (!header->key) { |
|
+ continue; |
|
+ } |
|
+ if (!check_header(ctx, header->key, (const char **)&header->val)) { |
|
+ return 0; |
|
+ } |
|
+ } |
|
+ return 1; |
|
+} |
|
+ |
|
+/** |
|
+ * Check headers for HTTP conformance |
|
+ * @return 1 if ok, 0 if bad |
|
+ */ |
|
+static APR_INLINE int check_headers(request_rec *r) |
|
+{ |
|
+ struct check_header_ctx ctx; |
|
+ core_server_config *conf = |
|
+ ap_get_core_module_config(r->server->module_config); |
|
+ |
|
+ ctx.r = r; |
|
+ ctx.strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); |
|
+ return check_headers_table(r->headers_out, &ctx) && |
|
+ check_headers_table(r->err_headers_out, &ctx); |
|
+} |
|
+ |
|
+static int check_headers_recursion(request_rec *r) |
|
+{ |
|
+ void *check = NULL; |
|
+ apr_pool_userdata_get(&check, "check_headers_recursion", r->pool); |
|
+ if (check) { |
|
+ return 1; |
|
+ } |
|
+ apr_pool_userdata_setn("true", "check_headers_recursion", NULL, r->pool); |
|
+ return 0; |
|
+} |
|
+ |
|
typedef struct header_struct { |
|
apr_pool_t *pool; |
|
apr_bucket_brigade *bb; |
|
} header_struct; |
|
|
|
/* Send a single HTTP header field to the client. Note that this function |
|
- * is used in calls to table_do(), so their interfaces are co-dependent. |
|
- * In other words, don't change this one without checking table_do in alloc.c. |
|
+ * is used in calls to apr_table_do(), so don't change its interface. |
|
* It returns true unless there was a write error of some kind. |
|
*/ |
|
static int form_header_field(header_struct *h, |
|
@@ -1160,6 +1283,7 @@ |
|
|
|
typedef struct header_filter_ctx { |
|
int headers_sent; |
|
+ int headers_error; |
|
} header_filter_ctx; |
|
|
|
AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, |
|
@@ -1175,19 +1299,23 @@ |
|
header_filter_ctx *ctx = f->ctx; |
|
const char *ctype; |
|
ap_bucket_error *eb = NULL; |
|
+ apr_bucket *eos = NULL; |
|
|
|
AP_DEBUG_ASSERT(!r->main); |
|
|
|
- if (r->header_only) { |
|
- if (!ctx) { |
|
- ctx = f->ctx = apr_pcalloc(r->pool, sizeof(header_filter_ctx)); |
|
- } |
|
- else if (ctx->headers_sent) { |
|
+ if (!ctx) { |
|
+ ctx = f->ctx = apr_pcalloc(r->pool, sizeof(header_filter_ctx)); |
|
+ } |
|
+ if (ctx->headers_sent) { |
|
+ /* Eat body if response must not have one. */ |
|
+ if (r->header_only || r->status == HTTP_NO_CONTENT) { |
|
apr_brigade_cleanup(b); |
|
- return OK; |
|
+ return APR_SUCCESS; |
|
} |
|
} |
|
- |
|
+ else if (!ctx->headers_error && !check_headers(r)) { |
|
+ ctx->headers_error = 1; |
|
+ } |
|
for (e = APR_BRIGADE_FIRST(b); |
|
e != APR_BRIGADE_SENTINEL(b); |
|
e = APR_BUCKET_NEXT(e)) |
|
@@ -1204,10 +1332,44 @@ |
|
ap_remove_output_filter(f); |
|
return ap_pass_brigade(f->next, b); |
|
} |
|
+ if (ctx->headers_error && APR_BUCKET_IS_EOS(e)) { |
|
+ eos = e; |
|
+ } |
|
} |
|
- if (eb) { |
|
- int status; |
|
+ if (ctx->headers_error) { |
|
+ if (!eos) { |
|
+ /* Eat body until EOS */ |
|
+ apr_brigade_cleanup(b); |
|
+ return APR_SUCCESS; |
|
+ } |
|
|
|
+ /* We may come back here from ap_die() below, |
|
+ * so clear anything from this response. |
|
+ */ |
|
+ ctx->headers_error = 0; |
|
+ apr_table_clear(r->headers_out); |
|
+ apr_table_clear(r->err_headers_out); |
|
+ |
|
+ /* Don't recall ap_die() if we come back here (from its own internal |
|
+ * redirect or error response), otherwise we can end up in infinite |
|
+ * recursion; better fall through with 500, minimal headers and an |
|
+ * empty body (EOS only). |
|
+ */ |
|
+ if (!check_headers_recursion(r)) { |
|
+ apr_brigade_cleanup(b); |
|
+ ap_die(HTTP_INTERNAL_SERVER_ERROR, r); |
|
+ return AP_FILTER_ERROR; |
|
+ } |
|
+ APR_BUCKET_REMOVE(eos); |
|
+ apr_brigade_cleanup(b); |
|
+ APR_BRIGADE_INSERT_TAIL(b, eos); |
|
+ r->status = HTTP_INTERNAL_SERVER_ERROR; |
|
+ r->content_type = r->content_encoding = NULL; |
|
+ r->content_languages = NULL; |
|
+ ap_set_content_length(r, 0); |
|
+ } |
|
+ else if (eb) { |
|
+ int status; |
|
status = eb->status; |
|
apr_brigade_cleanup(b); |
|
ap_die(status, r); |
|
@@ -1264,6 +1426,10 @@ |
|
apr_table_unset(r->headers_out, "Content-Length"); |
|
} |
|
|
|
+ if (r->status == HTTP_NO_CONTENT) { |
|
+ apr_table_unset(r->headers_out, "Content-Length"); |
|
+ } |
|
+ |
|
ctype = ap_make_content_type(r, r->content_type); |
|
if (ctype) { |
|
apr_table_setn(r->headers_out, "Content-Type", ctype); |
|
@@ -1352,11 +1518,11 @@ |
|
terminate_header(b2); |
|
|
|
ap_pass_brigade(f->next, b2); |
|
+ ctx->headers_sent = 1; |
|
|
|
- if (r->header_only) { |
|
+ if (r->header_only || r->status == HTTP_NO_CONTENT) { |
|
apr_brigade_cleanup(b); |
|
- ctx->headers_sent = 1; |
|
- return OK; |
|
+ return APR_SUCCESS; |
|
} |
|
|
|
r->sent_bodyct = 1; /* Whatever follows is real body stuff... */ |
|
diff -uap httpd-2.4.6/server/core.c.cve8743 httpd-2.4.6/server/core.c |
|
--- httpd-2.4.6/server/core.c.cve8743 |
|
+++ httpd-2.4.6/server/core.c |
|
@@ -506,6 +506,15 @@ |
|
if (virt->trace_enable != AP_TRACE_UNSET) |
|
conf->trace_enable = virt->trace_enable; |
|
|
|
+ if (virt->http09_enable != AP_HTTP09_UNSET) |
|
+ conf->http09_enable = virt->http09_enable; |
|
+ |
|
+ if (virt->http_conformance != AP_HTTP_CONFORMANCE_UNSET) |
|
+ conf->http_conformance = virt->http_conformance; |
|
+ |
|
+ if (virt->http_methods != AP_HTTP_METHODS_UNSET) |
|
+ conf->http_methods = virt->http_methods; |
|
+ |
|
/* no action for virt->accf_map, not allowed per-vhost */ |
|
|
|
if (virt->protocol) |
|
@@ -3632,6 +3641,57 @@ |
|
return NULL; |
|
} |
|
|
|
+static const char *set_http_protocol_options(cmd_parms *cmd, void *dummy, |
|
+ const char *arg) |
|
+{ |
|
+ core_server_config *conf = |
|
+ ap_get_core_module_config(cmd->server->module_config); |
|
+ |
|
+ if (strcasecmp(arg, "allow0.9") == 0) |
|
+ conf->http09_enable |= AP_HTTP09_ENABLE; |
|
+ else if (strcasecmp(arg, "require1.0") == 0) |
|
+ conf->http09_enable |= AP_HTTP09_DISABLE; |
|
+ else if (strcasecmp(arg, "strict") == 0) |
|
+ conf->http_conformance |= AP_HTTP_CONFORMANCE_STRICT; |
|
+ else if (strcasecmp(arg, "unsafe") == 0) |
|
+ conf->http_conformance |= AP_HTTP_CONFORMANCE_UNSAFE; |
|
+ else if (strcasecmp(arg, "registeredmethods") == 0) |
|
+ conf->http_methods |= AP_HTTP_METHODS_REGISTERED; |
|
+ else if (strcasecmp(arg, "lenientmethods") == 0) |
|
+ conf->http_methods |= AP_HTTP_METHODS_LENIENT; |
|
+ else |
|
+ return "HttpProtocolOptions accepts " |
|
+ "'Unsafe' or 'Strict' (default), " |
|
+ "'RegisteredMethods' or 'LenientMethods' (default), and " |
|
+ "'Require1.0' or 'Allow0.9' (default)"; |
|
+ |
|
+ if ((conf->http09_enable & AP_HTTP09_ENABLE) |
|
+ && (conf->http09_enable & AP_HTTP09_DISABLE)) |
|
+ return "HttpProtocolOptions 'Allow0.9' and 'Require1.0'" |
|
+ " are mutually exclusive"; |
|
+ |
|
+ if ((conf->http_conformance & AP_HTTP_CONFORMANCE_STRICT) |
|
+ && (conf->http_conformance & AP_HTTP_CONFORMANCE_UNSAFE)) |
|
+ return "HttpProtocolOptions 'Strict' and 'Unsafe'" |
|
+ " are mutually exclusive"; |
|
+ |
|
+ if ((conf->http_methods & AP_HTTP_METHODS_REGISTERED) |
|
+ && (conf->http_methods & AP_HTTP_METHODS_LENIENT)) |
|
+ return "HttpProtocolOptions 'RegisteredMethods' and 'LenientMethods'" |
|
+ " are mutually exclusive"; |
|
+ |
|
+ return NULL; |
|
+} |
|
+ |
|
+static const char *set_http_method(cmd_parms *cmd, void *conf, const char *arg) |
|
+{ |
|
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); |
|
+ if (err != NULL) |
|
+ return err; |
|
+ ap_method_register(cmd->pool, arg); |
|
+ return NULL; |
|
+} |
|
+ |
|
static apr_hash_t *errorlog_hash; |
|
|
|
static int log_constant_item(const ap_errorlog_info *info, const char *arg, |
|
@@ -4143,6 +4203,13 @@ |
|
"'on' (default), 'off' or 'extended' to trace request body content"), |
|
AP_INIT_FLAG("MergeTrailers", set_merge_trailers, NULL, RSRC_CONF, |
|
"merge request trailers into request headers or not"), |
|
+AP_INIT_ITERATE("HttpProtocolOptions", set_http_protocol_options, NULL, RSRC_CONF, |
|
+ "'Allow0.9' or 'Require1.0' (default); " |
|
+ "'RegisteredMethods' or 'LenientMethods' (default); " |
|
+ "'Unsafe' or 'Strict' (default). Sets HTTP acceptance rules") |
|
+, |
|
+AP_INIT_ITERATE("RegisterHttpMethod", set_http_method, NULL, RSRC_CONF, |
|
+ "Registers non-standard HTTP methods"), |
|
{ NULL } |
|
}; |
|
|
|
diff -uap httpd-2.4.6/server/gen_test_char.c.cve8743 httpd-2.4.6/server/gen_test_char.c |
|
--- httpd-2.4.6/server/gen_test_char.c.cve8743 |
|
+++ httpd-2.4.6/server/gen_test_char.c |
|
@@ -16,11 +16,11 @@ |
|
|
|
#ifdef CROSS_COMPILE |
|
|
|
+#include <ctype.h> |
|
#define apr_isalnum(c) (isalnum(((unsigned char)(c)))) |
|
#define apr_isalpha(c) (isalpha(((unsigned char)(c)))) |
|
#define apr_iscntrl(c) (iscntrl(((unsigned char)(c)))) |
|
#define apr_isprint(c) (isprint(((unsigned char)(c)))) |
|
-#include <ctype.h> |
|
#define APR_HAVE_STDIO_H 1 |
|
#define APR_HAVE_STRING_H 1 |
|
|
|
@@ -52,11 +52,13 @@ |
|
#define T_ESCAPE_LOGITEM (0x10) |
|
#define T_ESCAPE_FORENSIC (0x20) |
|
#define T_ESCAPE_URLENCODED (0x40) |
|
+#define T_HTTP_CTRLS (0x80) |
|
+#define T_VCHAR_OBSTEXT (0x100) |
|
|
|
int main(int argc, char *argv[]) |
|
{ |
|
unsigned c; |
|
- unsigned char flags; |
|
+ unsigned short flags; |
|
|
|
printf("/* this file is automatically generated by gen_test_char, " |
|
"do not edit */\n" |
|
@@ -67,19 +69,23 @@ |
|
"#define T_ESCAPE_LOGITEM (%u)\n" |
|
"#define T_ESCAPE_FORENSIC (%u)\n" |
|
"#define T_ESCAPE_URLENCODED (%u)\n" |
|
+ "#define T_HTTP_CTRLS (%u)\n" |
|
+ "#define T_VCHAR_OBSTEXT (%u)\n" |
|
"\n" |
|
- "static const unsigned char test_char_table[256] = {", |
|
+ "static const unsigned short test_char_table[256] = {", |
|
T_ESCAPE_SHELL_CMD, |
|
T_ESCAPE_PATH_SEGMENT, |
|
T_OS_ESCAPE_PATH, |
|
T_HTTP_TOKEN_STOP, |
|
T_ESCAPE_LOGITEM, |
|
T_ESCAPE_FORENSIC, |
|
- T_ESCAPE_URLENCODED); |
|
+ T_ESCAPE_URLENCODED, |
|
+ T_HTTP_CTRLS, |
|
+ T_VCHAR_OBSTEXT); |
|
|
|
for (c = 0; c < 256; ++c) { |
|
flags = 0; |
|
- if (c % 20 == 0) |
|
+ if (c % 8 == 0) |
|
printf("\n "); |
|
|
|
/* escape_shell_cmd */ |
|
@@ -107,7 +113,7 @@ |
|
flags |= T_ESCAPE_PATH_SEGMENT; |
|
} |
|
|
|
- if (!apr_isalnum(c) && !strchr("$-_.+!*'(),:@&=/~", c)) { |
|
+ if (!apr_isalnum(c) && !strchr("$-_.+!*'(),:;@&=/~", c)) { |
|
flags |= T_OS_ESCAPE_PATH; |
|
} |
|
|
|
@@ -115,11 +121,32 @@ |
|
flags |= T_ESCAPE_URLENCODED; |
|
} |
|
|
|
- /* these are the "tspecials" (RFC2068) or "separators" (RFC2616) */ |
|
- if (c && (apr_iscntrl(c) || strchr(" \t()<>@,;:\\\"/[]?={}", c))) { |
|
+ /* Stop for any non-'token' character, including ctrls, obs-text, |
|
+ * and "tspecials" (RFC2068) a.k.a. "separators" (RFC2616), which |
|
+ * is easer to express as characters remaining in the ASCII token set |
|
+ */ |
|
+ if (!c || !(apr_isalnum(c) || strchr("!#$%&'*+-.^_`|~", c))) { |
|
flags |= T_HTTP_TOKEN_STOP; |
|
} |
|
|
|
+ /* Catch CTRLs other than VCHAR, HT and SP, and obs-text (RFC7230 3.2) |
|
+ * This includes only the C0 plane, not C1 (which is obs-text itself.) |
|
+ * XXX: We should verify that all ASCII C0 ctrls/DEL corresponding to |
|
+ * the current EBCDIC translation are captured, and ASCII C1 ctrls |
|
+ * corresponding are all permitted (as they fall under obs-text rule) |
|
+ */ |
|
+ if (!c || (apr_iscntrl(c) && c != '\t')) { |
|
+ flags |= T_HTTP_CTRLS; |
|
+ } |
|
+ |
|
+ /* From RFC3986, the specific sets of gen-delims, sub-delims (2.2), |
|
+ * and unreserved (2.3) that are possible somewhere within a URI. |
|
+ * Spec requires all others to be %XX encoded, including obs-text. |
|
+ */ |
|
+ if (c && !apr_iscntrl(c) && c != ' ') { |
|
+ flags |= T_VCHAR_OBSTEXT; |
|
+ } |
|
+ |
|
/* For logging, escape all control characters, |
|
* double quotes (because they delimit the request in the log file) |
|
* backslashes (because we use backslash for escaping) |
|
@@ -137,7 +164,7 @@ |
|
flags |= T_ESCAPE_FORENSIC; |
|
} |
|
|
|
- printf("%u%c", flags, (c < 255) ? ',' : ' '); |
|
+ printf("0x%03x%c", flags, (c < 255) ? ',' : ' '); |
|
} |
|
|
|
printf("\n};\n"); |
|
diff -uap httpd-2.4.6/server/protocol.c.cve8743 httpd-2.4.6/server/protocol.c |
|
--- httpd-2.4.6/server/protocol.c.cve8743 |
|
+++ httpd-2.4.6/server/protocol.c |
|
@@ -189,6 +189,10 @@ |
|
* caused by MIME folding (or broken clients) if fold != 0, and place it |
|
* in the buffer s, of size n bytes, without the ending newline. |
|
* |
|
+ * Pulls from r->proto_input_filters instead of r->input_filters for |
|
+ * stricter protocol adherence and better input filter behavior during |
|
+ * chunked trailer processing (for http). |
|
+ * |
|
* If s is NULL, ap_rgetline_core will allocate necessary memory from r->pool. |
|
* |
|
* Returns APR_SUCCESS if there are no problems and sets *read to be |
|
@@ -197,7 +201,7 @@ |
|
* APR_ENOSPC is returned if there is not enough buffer space. |
|
* Other errors may be returned on other errors. |
|
* |
|
- * The LF is *not* returned in the buffer. Therefore, a *read of 0 |
|
+ * The [CR]LF are *not* returned in the buffer. Therefore, a *read of 0 |
|
* indicates that an empty line was read. |
|
* |
|
* Notes: Because the buffer uses 1 char for NUL, the most we can return is |
|
@@ -208,13 +212,15 @@ |
|
*/ |
|
AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, |
|
apr_size_t *read, request_rec *r, |
|
- int fold, apr_bucket_brigade *bb) |
|
+ int flags, apr_bucket_brigade *bb) |
|
{ |
|
apr_status_t rv; |
|
apr_bucket *e; |
|
apr_size_t bytes_handled = 0, current_alloc = 0; |
|
char *pos, *last_char = *s; |
|
int do_alloc = (*s == NULL), saw_eos = 0; |
|
+ int fold = flags & AP_GETLINE_FOLD; |
|
+ int crlf = flags & AP_GETLINE_CRLF; |
|
|
|
/* |
|
* Initialize last_char as otherwise a random value will be compared |
|
@@ -226,13 +232,15 @@ |
|
|
|
for (;;) { |
|
apr_brigade_cleanup(bb); |
|
- rv = ap_get_brigade(r->input_filters, bb, AP_MODE_GETLINE, |
|
+ rv = ap_get_brigade(r->proto_input_filters, bb, AP_MODE_GETLINE, |
|
APR_BLOCK_READ, 0); |
|
if (rv != APR_SUCCESS) { |
|
return rv; |
|
} |
|
|
|
- /* Something horribly wrong happened. Someone didn't block! */ |
|
+ /* Something horribly wrong happened. Someone didn't block! |
|
+ * (this also happens at the end of each keepalive connection) |
|
+ */ |
|
if (APR_BRIGADE_EMPTY(bb)) { |
|
return APR_EGENERAL; |
|
} |
|
@@ -318,6 +326,13 @@ |
|
} |
|
} |
|
|
|
+ if (crlf && (last_char <= *s || last_char[-1] != APR_ASCII_CR)) { |
|
+ *last_char = '\0'; |
|
+ bytes_handled = last_char - *s; |
|
+ *read = bytes_handled; |
|
+ return APR_EINVAL; |
|
+ } |
|
+ |
|
/* Now NUL-terminate the string at the end of the line; |
|
* if the last-but-one character is a CR, terminate there */ |
|
if (last_char > *s && last_char[-1] == APR_ASCII_CR) { |
|
@@ -340,7 +355,7 @@ |
|
apr_brigade_cleanup(bb); |
|
|
|
/* We only care about the first byte. */ |
|
- rv = ap_get_brigade(r->input_filters, bb, AP_MODE_SPECULATIVE, |
|
+ rv = ap_get_brigade(r->proto_input_filters, bb, AP_MODE_SPECULATIVE, |
|
APR_BLOCK_READ, 1); |
|
if (rv != APR_SUCCESS) { |
|
return rv; |
|
@@ -391,7 +406,8 @@ |
|
*/ |
|
if (do_alloc) { |
|
tmp = NULL; |
|
- } else { |
|
+ } |
|
+ else { |
|
/* We're null terminated. */ |
|
tmp = last_char; |
|
} |
|
@@ -461,7 +477,7 @@ |
|
} |
|
#endif |
|
|
|
-AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int fold) |
|
+AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int flags) |
|
{ |
|
char *tmp_s = s; |
|
apr_status_t rv; |
|
@@ -469,7 +485,7 @@ |
|
apr_bucket_brigade *tmp_bb; |
|
|
|
tmp_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); |
|
- rv = ap_rgetline(&tmp_s, n, &len, r, fold, tmp_bb); |
|
+ rv = ap_rgetline(&tmp_s, n, &len, r, flags, tmp_bb); |
|
apr_brigade_destroy(tmp_bb); |
|
|
|
/* Map the out-of-space condition to the old API. */ |
|
@@ -549,16 +565,29 @@ |
|
} |
|
} |
|
|
|
-static int read_request_line(request_rec *r, apr_bucket_brigade *bb) |
|
+/* get the length of the field name for logging, but no more than 80 bytes */ |
|
+#define LOG_NAME_MAX_LEN 80 |
|
+static int field_name_len(const char *field) |
|
{ |
|
- const char *ll; |
|
- const char *uri; |
|
- const char *pro; |
|
+ const char *end = ap_strchr_c(field, ':'); |
|
+ if (end == NULL || end - field > LOG_NAME_MAX_LEN) |
|
+ return LOG_NAME_MAX_LEN; |
|
+ return end - field; |
|
+} |
|
|
|
- int major = 1, minor = 0; /* Assume HTTP/1.0 if non-"HTTP" protocol */ |
|
- char http[5]; |
|
+static int read_request_line(request_rec *r, apr_bucket_brigade *bb) |
|
+{ |
|
+ enum { |
|
+ rrl_none, rrl_badmethod, rrl_badwhitespace, rrl_excesswhitespace, |
|
+ rrl_missinguri, rrl_baduri, rrl_badprotocol, rrl_trailingtext, |
|
+ rrl_badmethod09, rrl_reject09 |
|
+ } deferred_error = rrl_none; |
|
+ char *ll; |
|
+ char *uri; |
|
apr_size_t len; |
|
int num_blank_lines = 0; |
|
+ core_server_config *conf = ap_get_core_module_config(r->server->module_config); |
|
+ int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); |
|
int max_blank_lines = r->server->limit_req_fields; |
|
|
|
if (max_blank_lines <= 0) { |
|
@@ -588,7 +617,7 @@ |
|
*/ |
|
r->the_request = NULL; |
|
rv = ap_rgetline(&(r->the_request), (apr_size_t)(r->server->limit_req_line + 2), |
|
- &len, r, 0, bb); |
|
+ &len, r, strict ? AP_GETLINE_CRLF : 0, bb); |
|
|
|
if (rv != APR_SUCCESS) { |
|
r->request_time = apr_time_now(); |
|
@@ -599,8 +628,6 @@ |
|
*/ |
|
if (APR_STATUS_IS_ENOSPC(rv)) { |
|
r->status = HTTP_REQUEST_URI_TOO_LARGE; |
|
- r->proto_num = HTTP_VERSION(1,0); |
|
- r->protocol = apr_pstrdup(r->pool, "HTTP/1.0"); |
|
} |
|
else if (APR_STATUS_IS_TIMEUP(rv)) { |
|
r->status = HTTP_REQUEST_TIME_OUT; |
|
@@ -608,6 +635,8 @@ |
|
else if (APR_STATUS_IS_EINVAL(rv)) { |
|
r->status = HTTP_BAD_REQUEST; |
|
} |
|
+ r->proto_num = HTTP_VERSION(1,0); |
|
+ r->protocol = apr_pstrdup(r->pool, "HTTP/1.0"); |
|
return 0; |
|
} |
|
} while ((len <= 0) && (++num_blank_lines < max_blank_lines)); |
|
@@ -619,46 +648,263 @@ |
|
} |
|
|
|
r->request_time = apr_time_now(); |
|
- ll = r->the_request; |
|
- r->method = ap_getword_white(r->pool, &ll); |
|
+ r->method = r->the_request; |
|
+ |
|
+ /* If there is whitespace before a method, skip it and mark in error */ |
|
+ if (apr_isspace(*r->method)) { |
|
+ deferred_error = rrl_badwhitespace; |
|
+ for ( ; apr_isspace(*r->method); ++r->method) |
|
+ ; |
|
+ } |
|
|
|
- uri = ap_getword_white(r->pool, &ll); |
|
+ /* Scan the method up to the next whitespace, ensure it contains only |
|
+ * valid http-token characters, otherwise mark in error |
|
+ */ |
|
+ if (strict) { |
|
+ ll = (char*) ap_scan_http_token(r->method); |
|
+ } |
|
+ else { |
|
+ ll = (char*) ap_scan_vchar_obstext(r->method); |
|
+ } |
|
|
|
- /* Provide quick information about the request method as soon as known */ |
|
+ if (((ll == r->method) || (*ll && !apr_isspace(*ll))) |
|
+ && deferred_error == rrl_none) { |
|
+ deferred_error = rrl_badmethod; |
|
+ ll = strpbrk(ll, "\t\n\v\f\r "); |
|
+ } |
|
|
|
- r->method_number = ap_method_number_of(r->method); |
|
- if (r->method_number == M_GET && r->method[0] == 'H') { |
|
- r->header_only = 1; |
|
+ /* Verify method terminated with a single SP, or mark as specific error */ |
|
+ if (!ll) { |
|
+ if (deferred_error == rrl_none) |
|
+ deferred_error = rrl_missinguri; |
|
+ r->protocol = uri = ""; |
|
+ len = 0; |
|
+ goto rrl_done; |
|
+ } |
|
+ else if (strict && ll[0] && apr_isspace(ll[1]) |
|
+ && deferred_error == rrl_none) { |
|
+ deferred_error = rrl_excesswhitespace; |
|
} |
|
|
|
- ap_parse_uri(r, uri); |
|
+ /* Advance uri pointer over leading whitespace, NUL terminate the method |
|
+ * If non-SP whitespace is encountered, mark as specific error |
|
+ */ |
|
+ for (uri = ll; apr_isspace(*uri); ++uri) |
|
+ if (*uri != ' ' && deferred_error == rrl_none) |
|
+ deferred_error = rrl_badwhitespace; |
|
+ *ll = '\0'; |
|
+ |
|
+ if (!*uri && deferred_error == rrl_none) |
|
+ deferred_error = rrl_missinguri; |
|
+ |
|
+ /* Scan the URI up to the next whitespace, ensure it contains no raw |
|
+ * control characters, otherwise mark in error |
|
+ */ |
|
+ ll = (char*) ap_scan_vchar_obstext(uri); |
|
+ if (ll == uri || (*ll && !apr_isspace(*ll))) { |
|
+ deferred_error = rrl_baduri; |
|
+ ll = strpbrk(ll, "\t\n\v\f\r "); |
|
+ } |
|
|
|
- if (ll[0]) { |
|
+ /* Verify URI terminated with a single SP, or mark as specific error */ |
|
+ if (!ll) { |
|
+ r->protocol = ""; |
|
+ len = 0; |
|
+ goto rrl_done; |
|
+ } |
|
+ else if (strict && ll[0] && apr_isspace(ll[1]) |
|
+ && deferred_error == rrl_none) { |
|
+ deferred_error = rrl_excesswhitespace; |
|
+ } |
|
+ |
|
+ /* Advance protocol pointer over leading whitespace, NUL terminate the uri |
|
+ * If non-SP whitespace is encountered, mark as specific error |
|
+ */ |
|
+ for (r->protocol = ll; apr_isspace(*r->protocol); ++r->protocol) |
|
+ if (*r->protocol != ' ' && deferred_error == rrl_none) |
|
+ deferred_error = rrl_badwhitespace; |
|
+ *ll = '\0'; |
|
+ |
|
+ /* Scan the protocol up to the next whitespace, validation comes later */ |
|
+ if (!(ll = (char*) ap_scan_vchar_obstext(r->protocol))) { |
|
+ len = strlen(r->protocol); |
|
+ goto rrl_done; |
|
+ } |
|
+ len = ll - r->protocol; |
|
+ |
|
+ /* Advance over trailing whitespace, if found mark in error, |
|
+ * determine if trailing text is found, unconditionally mark in error, |
|
+ * finally NUL terminate the protocol string |
|
+ */ |
|
+ if (*ll && !apr_isspace(*ll)) { |
|
+ deferred_error = rrl_badprotocol; |
|
+ } |
|
+ else if (strict && *ll) { |
|
+ deferred_error = rrl_excesswhitespace; |
|
+ } |
|
+ else { |
|
+ for ( ; apr_isspace(*ll); ++ll) |
|
+ if (*ll != ' ' && deferred_error == rrl_none) |
|
+ deferred_error = rrl_badwhitespace; |
|
+ if (*ll && deferred_error == rrl_none) |
|
+ deferred_error = rrl_trailingtext; |
|
+ } |
|
+ *((char *)r->protocol + len) = '\0'; |
|
+ |
|
+rrl_done: |
|
+ /* For internal integrety and palloc efficiency, reconstruct the_request |
|
+ * in one palloc, using only single SP characters, per spec. |
|
+ */ |
|
+ r->the_request = apr_pstrcat(r->pool, r->method, *uri ? " " : NULL, uri, |
|
+ *r->protocol ? " " : NULL, r->protocol, NULL); |
|
+ |
|
+ if (len == 8 |
|
+ && r->protocol[0] == 'H' && r->protocol[1] == 'T' |
|
+ && r->protocol[2] == 'T' && r->protocol[3] == 'P' |
|
+ && r->protocol[4] == '/' && apr_isdigit(r->protocol[5]) |
|
+ && r->protocol[6] == '.' && apr_isdigit(r->protocol[7]) |
|
+ && r->protocol[5] != '0') { |
|
+ r->assbackwards = 0; |
|
+ r->proto_num = HTTP_VERSION(r->protocol[5] - '0', r->protocol[7] - '0'); |
|
+ } |
|
+ else if (len == 8 |
|
+ && (r->protocol[0] == 'H' || r->protocol[0] == 'h') |
|
+ && (r->protocol[1] == 'T' || r->protocol[1] == 't') |
|
+ && (r->protocol[2] == 'T' || r->protocol[2] == 't') |
|
+ && (r->protocol[3] == 'P' || r->protocol[3] == 'p') |
|
+ && r->protocol[4] == '/' && apr_isdigit(r->protocol[5]) |
|
+ && r->protocol[6] == '.' && apr_isdigit(r->protocol[7]) |
|
+ && r->protocol[5] != '0') { |
|
r->assbackwards = 0; |
|
- pro = ll; |
|
- len = strlen(ll); |
|
- } else { |
|
+ r->proto_num = HTTP_VERSION(r->protocol[5] - '0', r->protocol[7] - '0'); |
|
+ if (strict && deferred_error == rrl_none) |
|
+ deferred_error = rrl_badprotocol; |
|
+ else |
|
+ memcpy((char*)r->protocol, "HTTP", 4); |
|
+ } else if (r->protocol[0]) { |
|
+ r->proto_num = HTTP_VERSION(0, 9); |
|
+ /* Defer setting the r->protocol string till error msg is composed */ |
|
+ if (deferred_error == rrl_none) |
|
+ deferred_error = rrl_badprotocol; |
|
+ } |
|
+ else { |
|
r->assbackwards = 1; |
|
- pro = "HTTP/0.9"; |
|
- len = 8; |
|
+ r->protocol = apr_pstrdup(r->pool, "HTTP/0.9"); |
|
+ r->proto_num = HTTP_VERSION(0, 9); |
|
} |
|
- r->protocol = apr_pstrmemdup(r->pool, pro, len); |
|
|
|
- /* Avoid sscanf in the common case */ |
|
- if (len == 8 |
|
- && pro[0] == 'H' && pro[1] == 'T' && pro[2] == 'T' && pro[3] == 'P' |
|
- && pro[4] == '/' && apr_isdigit(pro[5]) && pro[6] == '.' |
|
- && apr_isdigit(pro[7])) { |
|
- r->proto_num = HTTP_VERSION(pro[5] - '0', pro[7] - '0'); |
|
- } |
|
- else if (3 == sscanf(r->protocol, "%4s/%u.%u", http, &major, &minor) |
|
- && (strcasecmp("http", http) == 0) |
|
- && (minor < HTTP_VERSION(1, 0)) ) /* don't allow HTTP/0.1000 */ |
|
- r->proto_num = HTTP_VERSION(major, minor); |
|
- else |
|
- r->proto_num = HTTP_VERSION(1, 0); |
|
+ /* Determine the method_number and parse the uri prior to invoking error |
|
+ * handling, such that these fields are available for subsitution |
|
+ */ |
|
+ r->method_number = ap_method_number_of(r->method); |
|
+ if (r->method_number == M_GET && r->method[0] == 'H') |
|
+ r->header_only = 1; |
|
+ |
|
+ ap_parse_uri(r, uri); |
|
+ |
|
+ /* With the request understood, we can consider HTTP/0.9 specific errors */ |
|
+ if (r->proto_num == HTTP_VERSION(0, 9) && deferred_error == rrl_none) { |
|
+ if (conf->http09_enable == AP_HTTP09_DISABLE) |
|
+ deferred_error = rrl_reject09; |
|
+ else if (strict && (r->method_number != M_GET || r->header_only)) |
|
+ deferred_error = rrl_badmethod09; |
|
+ } |
|
+ |
|
+ /* Now that the method, uri and protocol are all processed, |
|
+ * we can safely resume any deferred error reporting |
|
+ */ |
|
+ if (deferred_error != rrl_none) { |
|
+ if (deferred_error == rrl_badmethod) |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03445) |
|
+ "HTTP Request Line; Invalid method token: '%.*s'", |
|
+ field_name_len(r->method), r->method); |
|
+ else if (deferred_error == rrl_badmethod09) |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03444) |
|
+ "HTTP Request Line; Invalid method token: '%.*s'" |
|
+ " (only GET is allowed for HTTP/0.9 requests)", |
|
+ field_name_len(r->method), r->method); |
|
+ else if (deferred_error == rrl_missinguri) |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03446) |
|
+ "HTTP Request Line; Missing URI"); |
|
+ else if (deferred_error == rrl_baduri) |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03454) |
|
+ "HTTP Request Line; URI incorrectly encoded: '%.*s'", |
|
+ field_name_len(r->uri), r->uri); |
|
+ else if (deferred_error == rrl_badwhitespace) |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03447) |
|
+ "HTTP Request Line; Invalid whitespace"); |
|
+ else if (deferred_error == rrl_excesswhitespace) |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03448) |
|
+ "HTTP Request Line; Excess whitespace " |
|
+ "(disallowed by HttpProtocolOptions Strict"); |
|
+ else if (deferred_error == rrl_trailingtext) |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03449) |
|
+ "HTTP Request Line; Extraneous text found '%.*s' " |
|
+ "(perhaps whitespace was injected?)", |
|
+ field_name_len(ll), ll); |
|
+ else if (deferred_error == rrl_reject09) |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02401) |
|
+ "HTTP Request Line; Rejected HTTP/0.9 request"); |
|
+ else if (deferred_error == rrl_badprotocol) |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02418) |
|
+ "HTTP Request Line; Unrecognized protocol '%.*s' " |
|
+ "(perhaps whitespace was injected?)", |
|
+ field_name_len(r->protocol), r->protocol); |
|
+ r->status = HTTP_BAD_REQUEST; |
|
+ goto rrl_failed; |
|
+ } |
|
+ |
|
+ if (conf->http_methods == AP_HTTP_METHODS_REGISTERED |
|
+ && r->method_number == M_INVALID) { |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02423) |
|
+ "HTTP Request Line; Unrecognized HTTP method: '%.*s' " |
|
+ "(disallowed by RegisteredMethods)", |
|
+ field_name_len(r->method), r->method); |
|
+ r->status = HTTP_NOT_IMPLEMENTED; |
|
+ /* This can't happen in an HTTP/0.9 request, we verified GET above */ |
|
+ return 0; |
|
+ } |
|
+ |
|
+ if (r->status != HTTP_OK) { |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03450) |
|
+ "HTTP Request Line; Unable to parse URI: '%.*s'", |
|
+ field_name_len(r->uri), r->uri); |
|
+ goto rrl_failed; |
|
+ } |
|
+ |
|
+ if (strict) { |
|
+ if (r->parsed_uri.fragment) { |
|
+ /* RFC3986 3.5: no fragment */ |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02421) |
|
+ "HTTP Request Line; URI must not contain a fragment"); |
|
+ r->status = HTTP_BAD_REQUEST; |
|
+ goto rrl_failed; |
|
+ } |
|
+ if (r->parsed_uri.user || r->parsed_uri.password) { |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02422) |
|
+ "HTTP Request Line; URI must not contain a " |
|
+ "username/password"); |
|
+ r->status = HTTP_BAD_REQUEST; |
|
+ goto rrl_failed; |
|
+ } |
|
+ } |
|
|
|
return 1; |
|
+rrl_failed: |
|
+ if (r->proto_num == HTTP_VERSION(0, 9)) { |
|
+ /* Send all parsing and protocol error response with 1.x behavior, |
|
+ * and reserve 505 errors for actual HTTP protocols presented. |
|
+ * As called out in RFC7230 3.5, any errors parsing the protocol |
|
+ * from the request line are nearly always misencoded HTTP/1.x |
|
+ * requests. Only a valid 0.9 request with no parsing errors |
|
+ * at all may be treated as a simple request, if allowed. |
|
+ */ |
|
+ r->assbackwards = 0; |
|
+ r->connection->keepalive = AP_CONN_CLOSE; |
|
+ r->proto_num = HTTP_VERSION(1, 0); |
|
+ r->protocol = apr_pstrdup(r->pool, "HTTP/1.0"); |
|
+ } |
|
+ return 0; |
|
} |
|
|
|
static int table_do_fn_check_lengths(void *r_, const char *key, |
|
@@ -670,26 +916,13 @@ |
|
|
|
r->status = HTTP_BAD_REQUEST; |
|
apr_table_setn(r->notes, "error-notes", |
|
- apr_pstrcat(r->pool, "Size of a request header field " |
|
- "after merging exceeds server limit.<br />" |
|
- "\n<pre>\n", |
|
- ap_escape_html(r->pool, key), |
|
- "</pre>\n", NULL)); |
|
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00560) "Request header " |
|
- "exceeds LimitRequestFieldSize after merging: %s", key); |
|
+ "Size of a request header field exceeds server limit."); |
|
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00560) "Request " |
|
+ "header exceeds LimitRequestFieldSize after merging: %.*s", |
|
+ field_name_len(key), key); |
|
return 0; |
|
} |
|
|
|
-/* get the length of the field name for logging, but no more than 80 bytes */ |
|
-#define LOG_NAME_MAX_LEN 80 |
|
-static int field_name_len(const char *field) |
|
-{ |
|
- const char *end = ap_strchr_c(field, ':'); |
|
- if (end == NULL || end - field > LOG_NAME_MAX_LEN) |
|
- return LOG_NAME_MAX_LEN; |
|
- return end - field; |
|
-} |
|
- |
|
AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb) |
|
{ |
|
char *last_field = NULL; |
|
@@ -700,6 +933,8 @@ |
|
apr_size_t len; |
|
int fields_read = 0; |
|
char *tmp_field; |
|
+ core_server_config *conf = ap_get_core_module_config(r->server->module_config); |
|
+ int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); |
|
|
|
/* |
|
* Read header lines until we get the empty separator line, a read error, |
|
@@ -707,11 +942,10 @@ |
|
*/ |
|
while(1) { |
|
apr_status_t rv; |
|
- int folded = 0; |
|
|
|
field = NULL; |
|
rv = ap_rgetline(&field, r->server->limit_req_fieldsize + 2, |
|
- &len, r, 0, bb); |
|
+ &len, r, strict ? AP_GETLINE_CRLF : 0, bb); |
|
|
|
if (rv != APR_SUCCESS) { |
|
if (APR_STATUS_IS_TIMEUP(rv)) { |
|
@@ -728,153 +962,217 @@ |
|
* exceeds the configured limit for a field size. |
|
*/ |
|
if (rv == APR_ENOSPC) { |
|
- const char *field_escaped; |
|
- if (field) { |
|
- /* ensure ap_escape_html will terminate correctly */ |
|
- field[len - 1] = '\0'; |
|
- field_escaped = ap_escape_html(r->pool, field); |
|
- } |
|
- else { |
|
- field_escaped = field = ""; |
|
- } |
|
- |
|
apr_table_setn(r->notes, "error-notes", |
|
- apr_psprintf(r->pool, |
|
- "Size of a request header field " |
|
- "exceeds server limit.<br />\n" |
|
- "<pre>\n%.*s\n</pre>\n", |
|
- field_name_len(field_escaped), |
|
- field_escaped)); |
|
+ "Size of a request header field " |
|
+ "exceeds server limit."); |
|
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00561) |
|
"Request header exceeds LimitRequestFieldSize%s" |
|
"%.*s", |
|
- *field ? ": " : "", |
|
- field_name_len(field), field); |
|
+ (field && *field) ? ": " : "", |
|
+ (field) ? field_name_len(field) : 0, |
|
+ (field) ? field : ""); |
|
} |
|
return; |
|
} |
|
|
|
- if (last_field != NULL) { |
|
- if ((len > 0) && ((*field == '\t') || *field == ' ')) { |
|
- /* This line is a continuation of the preceding line(s), |
|
- * so append it to the line that we've set aside. |
|
- * Note: this uses a power-of-two allocator to avoid |
|
- * doing O(n) allocs and using O(n^2) space for |
|
- * continuations that span many many lines. |
|
- */ |
|
- apr_size_t fold_len = last_len + len + 1; /* trailing null */ |
|
|
|
- if (fold_len >= (apr_size_t)(r->server->limit_req_fieldsize)) { |
|
- r->status = HTTP_BAD_REQUEST; |
|
- /* report what we have accumulated so far before the |
|
- * overflow (last_field) as the field with the problem |
|
- */ |
|
- apr_table_setn(r->notes, "error-notes", |
|
- apr_psprintf(r->pool, |
|
- "Size of a request header field " |
|
- "after folding " |
|
- "exceeds server limit.<br />\n" |
|
- "<pre>\n%.*s\n</pre>\n", |
|
- field_name_len(last_field), |
|
- ap_escape_html(r->pool, last_field))); |
|
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00562) |
|
- "Request header exceeds LimitRequestFieldSize " |
|
- "after folding: %.*s", |
|
- field_name_len(last_field), last_field); |
|
- return; |
|
- } |
|
+ /* For all header values, and all obs-fold lines, the presence of |
|
+ * additional whitespace is a no-op, so collapse trailing whitespace |
|
+ * to save buffer allocation and optimize copy operations. |
|
+ * Do not remove the last single whitespace under any condition. |
|
+ */ |
|
+ while (len > 1 && (field[len-1] == '\t' || field[len-1] == ' ')) { |
|
+ field[--len] = '\0'; |
|
+ } |
|
+ |
|
+ if (*field == '\t' || *field == ' ') { |
|
+ /* Append any newly-read obs-fold line onto the preceding |
|
+ * last_field line we are processing |
|
+ */ |
|
+ apr_size_t fold_len; |
|
|
|
+ if (last_field == NULL) { |
|
+ r->status = HTTP_BAD_REQUEST; |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03442) |
|
+ "Line folding encountered before first" |
|
+ " header line"); |
|
+ return; |
|
+ } |
|
+ |
|
+ if (field[1] == '\0') { |
|
+ r->status = HTTP_BAD_REQUEST; |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03443) |
|
+ "Empty folded line encountered"); |
|
+ return; |
|
+ } |
|
+ |
|
+ /* Leading whitespace on an obs-fold line can be |
|
+ * similarly discarded */ |
|
+ while (field[1] == '\t' || field[1] == ' ') { |
|
+ ++field; --len; |
|
+ } |
|
+ |
|
+ /* This line is a continuation of the preceding line(s), |
|
+ * so append it to the line that we've set aside. |
|
+ * Note: this uses a power-of-two allocator to avoid |
|
+ * doing O(n) allocs and using O(n^2) space for |
|
+ * continuations that span many many lines. |
|
+ */ |
|
+ fold_len = last_len + len + 1; /* trailing null */ |
|
+ |
|
+ if (fold_len >= (apr_size_t)(r->server->limit_req_fieldsize)) { |
|
+ r->status = HTTP_BAD_REQUEST; |
|
+ /* report what we have accumulated so far before the |
|
+ * overflow (last_field) as the field with the problem |
|
+ */ |
|
+ apr_table_setn(r->notes, "error-notes", |
|
+ "Size of a request header field " |
|
+ "exceeds server limit."); |
|
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00562) |
|
+ "Request header exceeds LimitRequestFieldSize " |
|
+ "after folding: %.*s", |
|
+ field_name_len(last_field), last_field); |
|
+ return; |
|
+ } |
|
+ |
|
+ if (fold_len > alloc_len) { |
|
+ char *fold_buf; |
|
+ alloc_len += alloc_len; |
|
if (fold_len > alloc_len) { |
|
- char *fold_buf; |
|
- alloc_len += alloc_len; |
|
- if (fold_len > alloc_len) { |
|
- alloc_len = fold_len; |
|
- } |
|
- fold_buf = (char *)apr_palloc(r->pool, alloc_len); |
|
- memcpy(fold_buf, last_field, last_len); |
|
- last_field = fold_buf; |
|
+ alloc_len = fold_len; |
|
} |
|
- memcpy(last_field + last_len, field, len +1); /* +1 for nul */ |
|
- last_len += len; |
|
- folded = 1; |
|
- } |
|
- else /* not a continuation line */ { |
|
+ fold_buf = (char *)apr_palloc(r->pool, alloc_len); |
|
+ memcpy(fold_buf, last_field, last_len); |
|
+ last_field = fold_buf; |
|
+ } |
|
+ memcpy(last_field + last_len, field, len +1); /* +1 for nul */ |
|
+ /* Replace obs-fold w/ SP per RFC 7230 3.2.4 */ |
|
+ last_field[last_len] = ' '; |
|
+ last_len += len; |
|
|
|
- if (r->server->limit_req_fields |
|
+ /* We've appended this obs-fold line to last_len, proceed to |
|
+ * read the next input line |
|
+ */ |
|
+ continue; |
|
+ } |
|
+ else if (last_field != NULL) { |
|
+ /* Process the previous last_field header line with all obs-folded |
|
+ * segments already concatinated (this is not operating on the |
|
+ * most recently read input line). |
|
+ */ |
|
+ if (r->server->limit_req_fields |
|
&& (++fields_read > r->server->limit_req_fields)) { |
|
- r->status = HTTP_BAD_REQUEST; |
|
- apr_table_setn(r->notes, "error-notes", |
|
- "The number of request header fields " |
|
- "exceeds this server's limit."); |
|
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00563) |
|
- "Number of request headers exceeds " |
|
- "LimitRequestFields"); |
|
- return; |
|
- } |
|
+ r->status = HTTP_BAD_REQUEST; |
|
+ apr_table_setn(r->notes, "error-notes", |
|
+ "The number of request header fields " |
|
+ "exceeds this server's limit."); |
|
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00563) |
|
+ "Number of request headers exceeds " |
|
+ "LimitRequestFields"); |
|
+ return; |
|
+ } |
|
|
|
- if (!(value = strchr(last_field, ':'))) { /* Find ':' or */ |
|
- r->status = HTTP_BAD_REQUEST; /* abort bad request */ |
|
- apr_table_setn(r->notes, "error-notes", |
|
- apr_psprintf(r->pool, |
|
- "Request header field is " |
|
- "missing ':' separator.<br />\n" |
|
- "<pre>\n%.*s</pre>\n", |
|
- (int)LOG_NAME_MAX_LEN, |
|
- ap_escape_html(r->pool, |
|
- last_field))); |
|
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00564) |
|
+ if (!strict) |
|
+ { |
|
+ /* Not Strict ('Unsafe' mode), using the legacy parser */ |
|
+ |
|
+ if (!(value = strchr(last_field, ':'))) { /* Find ':' or */ |
|
+ r->status = HTTP_BAD_REQUEST; /* abort bad request */ |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00564) |
|
"Request header field is missing ':' " |
|
"separator: %.*s", (int)LOG_NAME_MAX_LEN, |
|
last_field); |
|
+ |
|
return; |
|
} |
|
|
|
- tmp_field = value - 1; /* last character of field-name */ |
|
+ /* last character of field-name */ |
|
+ tmp_field = value - (value > last_field ? 1 : 0); |
|
|
|
*value++ = '\0'; /* NUL-terminate at colon */ |
|
|
|
+ if (strpbrk(last_field, "\t\n\v\f\r ")) { |
|
+ r->status = HTTP_BAD_REQUEST; |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03452) |
|
+ "Request header field name presented" |
|
+ " invalid whitespace"); |
|
+ return; |
|
+ } |
|
+ |
|
while (*value == ' ' || *value == '\t') { |
|
- ++value; /* Skip to start of value */ |
|
+ ++value; /* Skip to start of value */ |
|
+ } |
|
+ |
|
+ if (strpbrk(value, "\n\v\f\r")) { |
|
+ r->status = HTTP_BAD_REQUEST; |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03451) |
|
+ "Request header field value presented" |
|
+ " bad whitespace"); |
|
+ return; |
|
} |
|
|
|
- /* Strip LWS after field-name: */ |
|
- while (tmp_field > last_field |
|
- && (*tmp_field == ' ' || *tmp_field == '\t')) { |
|
- *tmp_field-- = '\0'; |
|
+ if (tmp_field == last_field) { |
|
+ r->status = HTTP_BAD_REQUEST; |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03453) |
|
+ "Request header field name was empty"); |
|
+ return; |
|
+ } |
|
+ } |
|
+ else /* Using strict RFC7230 parsing */ |
|
+ { |
|
+ /* Ensure valid token chars before ':' per RFC 7230 3.2.4 */ |
|
+ value = (char *)ap_scan_http_token(last_field); |
|
+ if ((value == last_field) || *value != ':') { |
|
+ r->status = HTTP_BAD_REQUEST; |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02426) |
|
+ "Request header field name is malformed: " |
|
+ "%.*s", (int)LOG_NAME_MAX_LEN, last_field); |
|
+ return; |
|
} |
|
|
|
- /* Strip LWS after field-value: */ |
|
- tmp_field = last_field + last_len - 1; |
|
- while (tmp_field > value |
|
- && (*tmp_field == ' ' || *tmp_field == '\t')) { |
|
- *tmp_field-- = '\0'; |
|
+ *value++ = '\0'; /* NUL-terminate last_field name at ':' */ |
|
+ |
|
+ while (*value == ' ' || *value == '\t') { |
|
+ ++value; /* Skip LWS of value */ |
|
} |
|
|
|
- apr_table_addn(r->headers_in, last_field, value); |
|
+ /* Find invalid, non-HT ctrl char, or the trailing NULL */ |
|
+ tmp_field = (char *)ap_scan_http_field_content(value); |
|
|
|
- /* reset the alloc_len so that we'll allocate a new |
|
- * buffer if we have to do any more folding: we can't |
|
- * use the previous buffer because its contents are |
|
- * now part of r->headers_in |
|
+ /* Reject value for all garbage input (CTRLs excluding HT) |
|
+ * e.g. only VCHAR / SP / HT / obs-text are allowed per |
|
+ * RFC7230 3.2.6 - leave all more explicit rule enforcement |
|
+ * for specific header handler logic later in the cycle |
|
*/ |
|
- alloc_len = 0; |
|
+ if (*tmp_field != '\0') { |
|
+ r->status = HTTP_BAD_REQUEST; |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02427) |
|
+ "Request header value is malformed: " |
|
+ "%.*s", (int)LOG_NAME_MAX_LEN, value); |
|
+ return; |
|
+ } |
|
+ } |
|
+ |
|
+ apr_table_addn(r->headers_in, last_field, value); |
|
|
|
- } /* end if current line is not a continuation starting with tab */ |
|
+ /* This last_field header is now stored in headers_in, |
|
+ * resume processing of the current input line. |
|
+ */ |
|
} |
|
|
|
- /* Found a blank line, stop. */ |
|
+ /* Found the terminating empty end-of-headers line, stop. */ |
|
if (len == 0) { |
|
break; |
|
} |
|
|
|
- /* Keep track of this line so that we can parse it on |
|
- * the next loop iteration. (In the folded case, last_field |
|
- * has been updated already.) |
|
+ /* Keep track of this new header line so that we can extend it across |
|
+ * any obs-fold or parse it on the next loop iteration. We referenced |
|
+ * our previously allocated buffer in r->headers_in, |
|
+ * so allocate a fresh buffer if required. |
|
*/ |
|
- if (!folded) { |
|
- last_field = field; |
|
- last_len = len; |
|
- } |
|
+ alloc_len = 0; |
|
+ last_field = field; |
|
+ last_len = len; |
|
} |
|
|
|
/* Combine multiple message-header fields with the same |
|
@@ -899,7 +1197,7 @@ |
|
request_rec *r; |
|
apr_pool_t *p; |
|
const char *expect; |
|
- int access_status = HTTP_OK; |
|
+ int access_status; |
|
apr_bucket_brigade *tmp_bb; |
|
apr_socket_t *csd; |
|
apr_interval_time_t cur_timeout; |
|
@@ -958,35 +1256,39 @@ |
|
|
|
/* Get the request... */ |
|
if (!read_request_line(r, tmp_bb)) { |
|
- if (r->status == HTTP_REQUEST_URI_TOO_LARGE |
|
- || r->status == HTTP_BAD_REQUEST) { |
|
+ switch (r->status) { |
|
+ case HTTP_REQUEST_URI_TOO_LARGE: |
|
+ case HTTP_BAD_REQUEST: |
|
+ case HTTP_VERSION_NOT_SUPPORTED: |
|
+ case HTTP_NOT_IMPLEMENTED: |
|
if (r->status == HTTP_REQUEST_URI_TOO_LARGE) { |
|
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00565) |
|
"request failed: client's request-line exceeds LimitRequestLine (longer than %d)", |
|
r->server->limit_req_line); |
|
} |
|
else if (r->method == NULL) { |
|
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00566) |
|
- "request failed: invalid characters in URI"); |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00566) |
|
+ "request failed: malformed request line"); |
|
} |
|
- ap_send_error_response(r, 0); |
|
+ access_status = r->status; |
|
+ r->status = HTTP_OK; |
|
+ ap_die(access_status, r); |
|
ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); |
|
ap_run_log_transaction(r); |
|
+ r = NULL; |
|
apr_brigade_destroy(tmp_bb); |
|
goto traceout; |
|
- } |
|
- else if (r->status == HTTP_REQUEST_TIME_OUT) { |
|
- ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); |
|
- if (!r->connection->keepalives) { |
|
+ case HTTP_REQUEST_TIME_OUT: |
|
+ ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, NULL); |
|
+ if (!r->connection->keepalives) |
|
ap_run_log_transaction(r); |
|
- } |
|
apr_brigade_destroy(tmp_bb); |
|
goto traceout; |
|
+ default: |
|
+ apr_brigade_destroy(tmp_bb); |
|
+ r = NULL; |
|
+ goto traceout; |
|
} |
|
- |
|
- apr_brigade_destroy(tmp_bb); |
|
- r = NULL; |
|
- goto traceout; |
|
} |
|
|
|
/* We may have been in keep_alive_timeout mode, so toggle back |
|
@@ -1003,7 +1305,7 @@ |
|
if (!r->assbackwards) { |
|
ap_get_mime_headers_core(r, tmp_bb); |
|
if (r->status != HTTP_OK) { |
|
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00567) |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00567) |
|
"request failed: error reading the headers"); |
|
ap_send_error_response(r, 0); |
|
ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); |
|
@@ -1021,25 +1323,6 @@ |
|
apr_table_unset(r->headers_in, "Content-Length"); |
|
} |
|
} |
|
- else { |
|
- if (r->header_only) { |
|
- /* |
|
- * Client asked for headers only with HTTP/0.9, which doesn't send |
|
- * headers! Have to dink things just to make sure the error message |
|
- * comes through... |
|
- */ |
|
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00568) |
|
- "client sent invalid HTTP/0.9 request: HEAD %s", |
|
- r->uri); |
|
- r->header_only = 0; |
|
- r->status = HTTP_BAD_REQUEST; |
|
- ap_send_error_response(r, 0); |
|
- ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); |
|
- ap_run_log_transaction(r); |
|
- apr_brigade_destroy(tmp_bb); |
|
- goto traceout; |
|
- } |
|
- } |
|
|
|
apr_brigade_destroy(tmp_bb); |
|
|
|
@@ -1071,7 +1354,7 @@ |
|
* a Host: header, and the server MUST respond with 400 if it doesn't. |
|
*/ |
|
access_status = HTTP_BAD_REQUEST; |
|
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00569) |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00569) |
|
"client sent HTTP/1.1 request without hostname " |
|
"(see RFC2616 section 14.23): %s", r->uri); |
|
} |
|
diff -uap httpd-2.4.6/server/util.c.cve8743 httpd-2.4.6/server/util.c |
|
--- httpd-2.4.6/server/util.c.cve8743 |
|
+++ httpd-2.4.6/server/util.c |
|
@@ -79,7 +79,7 @@ |
|
* char in here and get it to work, because if char is signed then it |
|
* will first be sign extended. |
|
*/ |
|
-#define TEST_CHAR(c, f) (test_char_table[(unsigned)(c)] & (f)) |
|
+#define TEST_CHAR(c, f) (test_char_table[(unsigned char)(c)] & (f)) |
|
|
|
/* Win32/NetWare/OS2 need to check for both forward and back slashes |
|
* in ap_getparents() and ap_escape_url. |
|
@@ -1449,6 +1449,37 @@ |
|
return find_list_item(p, line, tok, AP_ETAG_WEAK); |
|
} |
|
|
|
+/* Scan a string for HTTP VCHAR/obs-text characters including HT and SP |
|
+ * (as used in header values, for example, in RFC 7230 section 3.2) |
|
+ * returning the pointer to the first non-HT ASCII ctrl character. |
|
+ */ |
|
+AP_DECLARE(const char *) ap_scan_http_field_content(const char *ptr) |
|
+{ |
|
+ for ( ; !TEST_CHAR(*ptr, T_HTTP_CTRLS); ++ptr) ; |
|
+ |
|
+ return ptr; |
|
+} |
|
+ |
|
+/* Scan a string for HTTP token characters, returning the pointer to |
|
+ * the first non-token character. |
|
+ */ |
|
+AP_DECLARE(const char *) ap_scan_http_token(const char *ptr) |
|
+{ |
|
+ for ( ; !TEST_CHAR(*ptr, T_HTTP_TOKEN_STOP); ++ptr) ; |
|
+ |
|
+ return ptr; |
|
+} |
|
+ |
|
+/* Scan a string for visible ASCII (0x21-0x7E) or obstext (0x80+) |
|
+ * and return a pointer to the first ctrl/space character encountered. |
|
+ */ |
|
+AP_DECLARE(const char *) ap_scan_vchar_obstext(const char *ptr) |
|
+{ |
|
+ for ( ; TEST_CHAR(*ptr, T_VCHAR_OBSTEXT); ++ptr) ; |
|
+ |
|
+ return ptr; |
|
+} |
|
+ |
|
/* Retrieve a token, spacing over it and returning a pointer to |
|
* the first non-white byte afterwards. Note that these tokens |
|
* are delimited by semis and commas; and can also be delimited |
|
diff -uap httpd-2.4.6/server/vhost.c.cve8743 httpd-2.4.6/server/vhost.c |
|
--- httpd-2.4.6/server/vhost.c.cve8743 |
|
+++ httpd-2.4.6/server/vhost.c |
|
@@ -685,6 +685,116 @@ |
|
* run-time vhost matching functions |
|
*/ |
|
|
|
+static apr_status_t fix_hostname_v6_literal(request_rec *r, char *host) |
|
+{ |
|
+ char *dst; |
|
+ int double_colon = 0; |
|
+ |
|
+ for (dst = host; *dst; dst++) { |
|
+ if (apr_isxdigit(*dst)) { |
|
+ if (apr_isupper(*dst)) { |
|
+ *dst = apr_tolower(*dst); |
|
+ } |
|
+ } |
|
+ else if (*dst == ':') { |
|
+ if (*(dst + 1) == ':') { |
|
+ if (double_colon) |
|
+ return APR_EINVAL; |
|
+ double_colon = 1; |
|
+ } |
|
+ else if (*(dst + 1) == '.') { |
|
+ return APR_EINVAL; |
|
+ } |
|
+ } |
|
+ else if (*dst == '.') { |
|
+ /* For IPv4-mapped IPv6 addresses like ::FFFF:129.144.52.38 */ |
|
+ if (*(dst + 1) == ':' || *(dst + 1) == '.') |
|
+ return APR_EINVAL; |
|
+ } |
|
+ else { |
|
+ return APR_EINVAL; |
|
+ } |
|
+ } |
|
+ return APR_SUCCESS; |
|
+} |
|
+ |
|
+static apr_status_t fix_hostname_non_v6(request_rec *r, char *host) |
|
+{ |
|
+ char *dst; |
|
+ |
|
+ for (dst = host; *dst; dst++) { |
|
+ if (apr_islower(*dst)) { |
|
+ /* leave char unchanged */ |
|
+ } |
|
+ else if (*dst == '.') { |
|
+ if (*(dst + 1) == '.') { |
|
+ return APR_EINVAL; |
|
+ } |
|
+ } |
|
+ else if (apr_isupper(*dst)) { |
|
+ *dst = apr_tolower(*dst); |
|
+ } |
|
+ else if (*dst == '/' || *dst == '\\') { |
|
+ return APR_EINVAL; |
|
+ } |
|
+ } |
|
+ /* strip trailing gubbins */ |
|
+ if (dst > host && dst[-1] == '.') { |
|
+ dst[-1] = '\0'; |
|
+ } |
|
+ return APR_SUCCESS; |
|
+} |
|
+ |
|
+/* |
|
+ * If strict mode ever becomes the default, this should be folded into |
|
+ * fix_hostname_non_v6() |
|
+ */ |
|
+static apr_status_t strict_hostname_check(request_rec *r, char *host) |
|
+{ |
|
+ char *ch; |
|
+ int is_dotted_decimal = 1, leading_zeroes = 0, dots = 0; |
|
+ |
|
+ for (ch = host; *ch; ch++) { |
|
+ if (!apr_isascii(*ch)) { |
|
+ goto bad; |
|
+ } |
|
+ else if (apr_isalpha(*ch) || *ch == '-') { |
|
+ is_dotted_decimal = 0; |
|
+ } |
|
+ else if (ch[0] == '.') { |
|
+ dots++; |
|
+ if (ch[1] == '0' && apr_isdigit(ch[2])) |
|
+ leading_zeroes = 1; |
|
+ } |
|
+ else if (!apr_isdigit(*ch)) { |
|
+ /* also takes care of multiple Host headers by denying commas */ |
|
+ goto bad; |
|
+ } |
|
+ } |
|
+ if (is_dotted_decimal) { |
|
+ if (host[0] == '.' || (host[0] == '0' && apr_isdigit(host[1]))) |
|
+ leading_zeroes = 1; |
|
+ if (leading_zeroes || dots != 3) { |
|
+ /* RFC 3986 7.4 */ |
|
+ goto bad; |
|
+ } |
|
+ } |
|
+ else { |
|
+ /* The top-level domain must start with a letter (RFC 1123 2.1) */ |
|
+ while (ch > host && *ch != '.') |
|
+ ch--; |
|
+ if (ch[0] == '.' && ch[1] != '\0' && !apr_isalpha(ch[1])) |
|
+ goto bad; |
|
+ } |
|
+ return APR_SUCCESS; |
|
+ |
|
+bad: |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02415) |
|
+ "[strict] Invalid host name '%s'%s%.6s", |
|
+ host, *ch ? ", problem near: " : "", ch); |
|
+ return APR_EINVAL; |
|
+} |
|
+ |
|
/* Lowercase and remove any trailing dot and/or :port from the hostname, |
|
* and check that it is sane. |
|
* |
|
@@ -698,79 +808,90 @@ |
|
* Instead we just check for filesystem metacharacters: directory |
|
* separators / and \ and sequences of more than one dot. |
|
*/ |
|
-static void fix_hostname(request_rec *r) |
|
+static int fix_hostname(request_rec *r, const char *host_header, |
|
+ unsigned http_conformance) |
|
{ |
|
+ const char *src; |
|
char *host, *scope_id; |
|
- char *dst; |
|
apr_port_t port; |
|
apr_status_t rv; |
|
const char *c; |
|
+ int is_v6literal = 0; |
|
+ int strict = (http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); |
|
|
|
- /* According to RFC 2616, Host header field CAN be blank. */ |
|
- if (!*r->hostname) { |
|
- return; |
|
+ src = host_header ? host_header : r->hostname; |
|
+ |
|
+ /* According to RFC 2616, Host header field CAN be blank */ |
|
+ if (!*src) { |
|
+ return is_v6literal; |
|
} |
|
|
|
/* apr_parse_addr_port will interpret a bare integer as a port |
|
* which is incorrect in this context. So treat it separately. |
|
*/ |
|
- for (c = r->hostname; apr_isdigit(*c); ++c); |
|
- if (!*c) { /* pure integer */ |
|
- return; |
|
+ for (c = src; apr_isdigit(*c); ++c); |
|
+ if (!*c) { |
|
+ /* pure integer */ |
|
+ if (strict) { |
|
+ /* RFC 3986 7.4 */ |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02416) |
|
+ "[strict] purely numeric host names not allowed: %s", |
|
+ src); |
|
+ goto bad_nolog; |
|
+ } |
|
+ r->hostname = src; |
|
+ return is_v6literal; |
|
+ } |
|
+ |
|
+ if (host_header) { |
|
+ rv = apr_parse_addr_port(&host, &scope_id, &port, src, r->pool); |
|
+ if (rv != APR_SUCCESS || scope_id) |
|
+ goto bad; |
|
+ if (port) { |
|
+ /* Don't throw the Host: header's port number away: |
|
+ save it in parsed_uri -- ap_get_server_port() needs it! */ |
|
+ /* @@@ XXX there should be a better way to pass the port. |
|
+ * Like r->hostname, there should be a r->portno |
|
+ */ |
|
+ r->parsed_uri.port = port; |
|
+ r->parsed_uri.port_str = apr_itoa(r->pool, (int)port); |
|
+ } |
|
+ if (host_header[0] == '[') |
|
+ is_v6literal = 1; |
|
+ } |
|
+ else { |
|
+ /* |
|
+ * Already parsed, surrounding [ ] (if IPv6 literal) and :port have |
|
+ * already been removed. |
|
+ */ |
|
+ host = apr_pstrdup(r->pool, r->hostname); |
|
+ if (ap_strchr(host, ':') != NULL) |
|
+ is_v6literal = 1; |
|
} |
|
|
|
- rv = apr_parse_addr_port(&host, &scope_id, &port, r->hostname, r->pool); |
|
- if (rv != APR_SUCCESS || scope_id) { |
|
- goto bad; |
|
+ if (is_v6literal) { |
|
+ rv = fix_hostname_v6_literal(r, host); |
|
} |
|
- |
|
- if (port) { |
|
- /* Don't throw the Host: header's port number away: |
|
- save it in parsed_uri -- ap_get_server_port() needs it! */ |
|
- /* @@@ XXX there should be a better way to pass the port. |
|
- * Like r->hostname, there should be a r->portno |
|
- */ |
|
- r->parsed_uri.port = port; |
|
- r->parsed_uri.port_str = apr_itoa(r->pool, (int)port); |
|
+ else { |
|
+ rv = fix_hostname_non_v6(r, host); |
|
+ if (strict && rv == APR_SUCCESS) |
|
+ rv = strict_hostname_check(r, host); |
|
} |
|
+ if (rv != APR_SUCCESS) |
|
+ goto bad; |
|
|
|
- /* if the hostname is an IPv6 numeric address string, it was validated |
|
- * already; otherwise, further validation is needed |
|
- */ |
|
- if (r->hostname[0] != '[') { |
|
- for (dst = host; *dst; dst++) { |
|
- if (apr_islower(*dst)) { |
|
- /* leave char unchanged */ |
|
- } |
|
- else if (*dst == '.') { |
|
- if (*(dst + 1) == '.') { |
|
- goto bad; |
|
- } |
|
- } |
|
- else if (apr_isupper(*dst)) { |
|
- *dst = apr_tolower(*dst); |
|
- } |
|
- else if (*dst == '/' || *dst == '\\') { |
|
- goto bad; |
|
- } |
|
- } |
|
- /* strip trailing gubbins */ |
|
- if (dst > host && dst[-1] == '.') { |
|
- dst[-1] = '\0'; |
|
- } |
|
- } |
|
r->hostname = host; |
|
- return; |
|
+ return is_v6literal; |
|
|
|
bad: |
|
- r->status = HTTP_BAD_REQUEST; |
|
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00550) |
|
"Client sent malformed Host header: %s", |
|
- r->hostname); |
|
- return; |
|
+ src); |
|
+bad_nolog: |
|
+ r->status = HTTP_BAD_REQUEST; |
|
+ return is_v6literal; |
|
} |
|
|
|
- |
|
/* return 1 if host matches ServerName or ServerAliases */ |
|
static int matches_aliases(server_rec *s, const char *host) |
|
{ |
|
@@ -980,15 +1101,76 @@ |
|
} |
|
} |
|
|
|
+static APR_INLINE const char *construct_host_header(request_rec *r, |
|
+ int is_v6literal) |
|
+{ |
|
+ struct iovec iov[5]; |
|
+ apr_size_t nvec = 0; |
|
+ /* |
|
+ * We cannot use ap_get_server_name/port here, because we must |
|
+ * ignore UseCanonicalName/Port. |
|
+ */ |
|
+ if (is_v6literal) { |
|
+ iov[nvec].iov_base = "["; |
|
+ iov[nvec].iov_len = 1; |
|
+ nvec++; |
|
+ } |
|
+ iov[nvec].iov_base = (void *)r->hostname; |
|
+ iov[nvec].iov_len = strlen(r->hostname); |
|
+ nvec++; |
|
+ if (is_v6literal) { |
|
+ iov[nvec].iov_base = "]"; |
|
+ iov[nvec].iov_len = 1; |
|
+ nvec++; |
|
+ } |
|
+ if (r->parsed_uri.port_str) { |
|
+ iov[nvec].iov_base = ":"; |
|
+ iov[nvec].iov_len = 1; |
|
+ nvec++; |
|
+ iov[nvec].iov_base = r->parsed_uri.port_str; |
|
+ iov[nvec].iov_len = strlen(r->parsed_uri.port_str); |
|
+ nvec++; |
|
+ } |
|
+ return apr_pstrcatv(r->pool, iov, nvec, NULL); |
|
+} |
|
|
|
AP_DECLARE(void) ap_update_vhost_from_headers(request_rec *r) |
|
{ |
|
- /* must set this for HTTP/1.1 support */ |
|
- if (r->hostname || (r->hostname = apr_table_get(r->headers_in, "Host"))) { |
|
- fix_hostname(r); |
|
- if (r->status != HTTP_OK) |
|
- return; |
|
+ core_server_config *conf = ap_get_core_module_config(r->server->module_config); |
|
+ const char *host_header = apr_table_get(r->headers_in, "Host"); |
|
+ int is_v6literal = 0; |
|
+ int have_hostname_from_url = 0; |
|
+ |
|
+ if (r->hostname) { |
|
+ /* |
|
+ * If there was a host part in the Request-URI, ignore the 'Host' |
|
+ * header. |
|
+ */ |
|
+ have_hostname_from_url = 1; |
|
+ is_v6literal = fix_hostname(r, NULL, conf->http_conformance); |
|
+ } |
|
+ else if (host_header != NULL) { |
|
+ is_v6literal = fix_hostname(r, host_header, conf->http_conformance); |
|
+ } |
|
+ if (r->status != HTTP_OK) |
|
+ return; |
|
+ |
|
+ if (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE) { |
|
+ /* |
|
+ * If we have both hostname from an absoluteURI and a Host header, |
|
+ * we must ignore the Host header (RFC 2616 5.2). |
|
+ * To enforce this, we reset the Host header to the value from the |
|
+ * request line. |
|
+ */ |
|
+ if (have_hostname_from_url && host_header != NULL) { |
|
+ const char *repl = construct_host_header(r, is_v6literal); |
|
+ apr_table_set(r->headers_in, "Host", repl); |
|
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02417) |
|
+ "Replacing host header '%s' with host '%s' given " |
|
+ "in the request uri", host_header, repl); |
|
+ } |
|
} |
|
+ |
|
/* check if we tucked away a name_chain */ |
|
if (r->connection->vhost_lookup_data) { |
|
if (r->hostname)
|
|
|